105e56cf05
Añade el capítulo `text_distr` al motor AutomaticEDA: perfila columnas de texto libre largo (reseñas, descripciones, comentarios) que la distribución categórica no resume bien. Sigue el patrón de cat_distr/num_distr (build_text_distr(profile, ctx) -> Chapter | None) y se registra en CHAPTER_ORDER tras cat_distr. Activación en dos fases: gate barato desde el perfil (columna no numérica con len_mean >= 50 chars) + confirmación con muestra cruda (mediana de palabras >= 20). Un dataset sin texto largo (p.ej. titanic) devuelve None sin tocar el informe. Bloques por columna (Group con page_break): resumen (longitudes, vocabulario con TTR y % hapax, idioma dominante, % duplicados, legibilidad), histograma de longitudes, top términos (tabla + barras), bigramas/trigramas, idiomas detectados y nube de palabras opcional. Términos ttr/hapax enganchados al glosario clicable. Lógica delegada a 7 funciones nuevas del registry (datascience, tag eda), estilo dict-no-throw: - extract_text_sample (impura, push-down SQL DuckDB/Postgres) - compute_text_length_stats, compute_vocabulary_stats, compute_top_ngrams (puras, stdlib) - detect_corpus_language (langdetect opcional), compute_text_readability (textstat opcional), compute_text_duplicates (hash + datasketch opcional) Versión barata sin modelos pesados: las piezas que dependen de una librería opcional (langdetect, textstat, wordcloud, datasketch) degradan a omitidas sin lanzar. Añade langdetect y textstat (ligeras) al pyproject + uv.lock. Verificado: golden sobre dataset de reviews multi-idioma (capítulo presente en PDF+PPTX+MD con métricas reales), titanic sin capítulo (None), degradación sin libs, suite automatic_eda + pipeline verde (128 passed), fn index OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
"""Tests para compute_vocabulary_stats."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(
|
|
0, os.path.join(os.path.dirname(__file__), "..", "..", "functions")
|
|
)
|
|
|
|
from datascience.compute_vocabulary_stats import compute_vocabulary_stats
|
|
|
|
|
|
def test_basico():
|
|
# Corpus con repeticiones y hapax. Stopwords desactivadas para controlar
|
|
# exactamente que tokens entran.
|
|
texts = ["gato gato perro", "perro perro raton", "elefante"]
|
|
r = compute_vocabulary_stats(texts, top_k=10, remove_stopwords=False)
|
|
|
|
# n_types < n_tokens cuando hay repeticiones.
|
|
assert r["n_types"] < r["n_tokens"]
|
|
assert r["n_tokens"] == 7
|
|
assert r["n_types"] == 4 # gato, perro, raton, elefante
|
|
|
|
# ttr en (0, 1].
|
|
assert 0 < r["ttr"] <= 1
|
|
assert r["ttr"] == round(4 / 7, 4)
|
|
|
|
# top_terms ordenado por count descendente.
|
|
counts = [t["count"] for t in r["top_terms"]]
|
|
assert counts == sorted(counts, reverse=True)
|
|
assert r["top_terms"][0]["term"] == "perro"
|
|
assert r["top_terms"][0]["count"] == 3
|
|
|
|
# hapax: raton y elefante aparecen exactamente una vez.
|
|
assert r["n_hapax"] == 2
|
|
assert r["hapax_pct"] == round(2 / 4 * 100, 2)
|
|
|
|
# pct coherente con count/n_tokens.
|
|
assert r["top_terms"][0]["pct"] == round(3 / 7 * 100, 2)
|
|
|
|
|
|
def test_vacio():
|
|
# Sin documentos validos -> ceros / None / [].
|
|
for arg in ([], None, [None, 123, ""], ["123 456"]):
|
|
r = compute_vocabulary_stats(arg)
|
|
assert r["n_tokens"] == 0
|
|
assert r["n_types"] == 0
|
|
assert r["ttr"] is None
|
|
assert r["n_hapax"] == 0
|
|
assert r["hapax_pct"] is None
|
|
assert r["top_terms"] == []
|
|
|
|
|
|
def test_stopwords_quitadas():
|
|
texts = ["the gato the perro", "de la casa azul"]
|
|
r = compute_vocabulary_stats(texts, remove_stopwords=True)
|
|
terms = {t["term"] for t in r["top_terms"]}
|
|
# Stopwords ES+EN no deben aparecer.
|
|
assert "the" not in terms
|
|
assert "de" not in terms
|
|
assert "la" not in terms
|
|
# Palabras de contenido si.
|
|
assert "gato" in terms
|
|
assert "casa" in terms
|
|
|
|
|
|
def test_stopwords_conservadas():
|
|
texts = ["the gato the perro", "de la casa azul"]
|
|
r = compute_vocabulary_stats(texts, remove_stopwords=False)
|
|
terms = {t["term"] for t in r["top_terms"]}
|
|
# Con el filtro desactivado, las stopwords se conservan.
|
|
assert "the" in terms
|
|
assert "de" in terms
|
|
assert "la" in terms
|