---
id: compute_vocabulary_stats_py_datascience
name: compute_vocabulary_stats
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def compute_vocabulary_stats(texts: list, top_k: int = 20, remove_stopwords: bool = True) -> dict"
description: "Profiles the vocabulary of a text corpus for EDA: tokenises a list of documents, counts term frequencies and derives lexical-richness measures — total tokens, unique types, type-token ratio (TTR), hapax legomena and the top-k most frequent terms. Pure, stdlib only (re + collections.Counter); no nltk, no sklearn. Inline ES+EN stopword list, opt-out via remove_stopwords. Never raises: empty/degenerate input returns the zeroed result."
tags: [eda, datascience, text, nlp, vocabulary, ttr, hapax, pure, python]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [re, collections]
example: |
  from datascience.compute_vocabulary_stats import compute_vocabulary_stats
  result = compute_vocabulary_stats(["el gato y el perro", "gato veloz"], top_k=5)
tested: true
tests:
  - "test_basico"
  - "test_vacio"
  - "test_stopwords_quitadas"
  - "test_stopwords_conservadas"
test_file_path: "python/functions/datascience/compute_vocabulary_stats_test.py"
file_path: "python/functions/datascience/compute_vocabulary_stats.py"
params:
  - name: texts
    desc: "List of documents (strings) forming the corpus. Entries that are None or not a str are silently discarded. Tokens are extracted per document with re.findall(r'\\w+', doc.lower(), re.UNICODE); purely numeric tokens (tok.isdigit()) are dropped."
  - name: top_k
    desc: "Maximum number of most-frequent terms to return in top_terms. Default 20. Does not affect n_tokens/n_types/ttr/hapax — only the length of the top_terms list."
  - name: remove_stopwords
    desc: "When True (default) common Spanish+English stopwords from the inline _STOPWORDS set (~120 entries) are removed from the token stream before any counting. Set False to keep every word (raw lexical profile)."
output: "Dict with the exact keys n_tokens (int), n_types (int), ttr (float|None, n_types/n_tokens rounded to 4 dp), n_hapax (int, terms occurring exactly once), hapax_pct (float|None, n_hapax/n_types*100 rounded to 2 dp) and top_terms (list of {term, count, pct} sorted by count descending, pct = count/n_tokens*100 rounded to 2 dp). For an empty corpus (no tokens after filtering): n_tokens=0, n_types=0, ttr=None, n_hapax=0, hapax_pct=None, top_terms=[]. Any exception degrades to that same empty result — the function never throws."
---

## Ejemplo

```python
from datascience.compute_vocabulary_stats import compute_vocabulary_stats

compute_vocabulary_stats(
    ["el gato y el perro", "gato veloz corre", "perro perro perro"],
    top_k=5,
)
# {
#   "n_tokens": 6,        # stopwords (el, y) eliminadas por defecto
#   "n_types": 3,         # gato, perro, veloz, corre -> tras quitar stopwords
#   "ttr": 0.5,           # n_types / n_tokens
#   "n_hapax": 2,         # veloz, corre (1 aparicion cada uno)
#   "hapax_pct": 50.0,    # n_hapax / n_types * 100
#   "top_terms": [
#     {"term": "perro", "count": 4, "pct": 44.44},
#     {"term": "gato",  "count": 2, "pct": 22.22},
#     ...
#   ],
# }

# Perfil lexico crudo (sin filtrar stopwords):
compute_vocabulary_stats(["the cat and the dog"], remove_stopwords=False)
```

## Cuando usarla

Úsala al perfilar una columna o corpus de texto libre en un EDA del grupo `eda`:
cuando necesites medir la riqueza léxica (cuántos tokens y cuántas palabras
distintas, type-token ratio, porcentaje de palabras que solo aparecen una vez) y
ver qué términos dominan el vocabulario (top-k frecuencias). Pásale la lista de
documentos crudos (filas de la columna); `None` y valores no-string se ignoran
solos. Es el equivalente para texto largo de `summarize_categorical`, que perfila
categorías cortas.

## Gotchas

- Función pura y stdlib-only, pero el resultado depende del **idioma**: la lista
  `_STOPWORDS` cubre español e inglés. Para otros idiomas pon
  `remove_stopwords=False` o filtra fuera, o el perfil mezclará stopwords no
  reconocidas en `top_terms`.
- La tokenización es `\w+` con `re.UNICODE`: separa por puntuación y conserva
  acentos/ñ, pero NO hace stemming ni lematización — "gato" y "gatos" cuentan
  como tipos distintos. Tampoco hace stripping de acentos, así que "más" (con
  tilde) y "mas" son tokens diferentes (ambos están en la stoplist).
- Los tokens **puramente numéricos** (`"123"`) se descartan siempre; un token
  alfanumérico mixto (`"covid19"`) se conserva.
- `ttr` baja artificialmente en corpus grandes (más texto, más repetición): no
  compares TTR entre corpus de tamaños muy distintos sin normalizar.
- Nunca lanza: entrada vacía, `None`, o cualquier excepción interna devuelven el
  resultado con ceros/`None`/`[]`. Comprueba `n_tokens == 0` para detectar el
  caso degenerado.
