From fd6326144465774a4c74e0e907ef9167bf49dace Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Tue, 30 Jun 2026 19:15:24 +0200 Subject: [PATCH] =?UTF-8?q?refactor(eda):=20quitar=20definiciones=20inline?= =?UTF-8?q?=20redundantes=20con=20el=20glosario=20en=205=20cap=C3=ADtulos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ahora que el AutomaticEDA tiene un capítulo GLOSARIO con las definiciones de los términos técnicos (enganchados como links clicables desde el cuerpo), los capítulos calidad/correlacion/modelos/agregacion/relaciones ya no repiten inline esas explicaciones largas: se deja el TÉRMINO marcado (clicable, sigue saltando al glosario) y se elimina el párrafo/oración de definición redundante. Los HALLAZGOS y datos concretos del análisis se mantienen intactos; solo se quitan las definiciones generales que el glosario ya cubre. - calidad: _criteria_intro pasa de un bullet-list con las definiciones de completitud/validez/unicidad/calidad + fórmula renormalizada + párrafo de outliers a una frase que nombra las dimensiones, sus pesos (60/40) y el principio de outliers; los 4 términos siguen marcados. - modelos: la nota de normalización deja de explicar la fórmula del z-score; la intro de PCA ya no define "componentes ortogonales ordenados por varianza"; la de KMeans quita "rango −1 a 1: cuanto más alto..." (silhouette); la sección de Isolation Forest quita la descripción de árboles/cortes/umbral. Términos marcados intactos. - correlacion: la intro deja de describir cada método y consolida la duplicación signo/dirección; los 4 métodos + FDR siguen marcados. - agregacion: la intro quita la definición de pivot ("cruzan dos categóricas sobre una medida") y abrevia la selección de claves; groupby y pivot marcados. - relaciones: la intro y la sección de candidatas/inter-tabla quitan las definiciones de PK ("identifica cada fila"), FK ("referencian a otra tabla") y containment ("valores contenidos en la clave de otra"); pk/fk/cardinalidad/ containment siguen marcados. Verificado sobre el EDA de titanic (run_models + run_llm, 48 págs): los 23 link annotations término→glosario se conservan (PyMuPDF), el glosario mantiene las 20 definiciones, y el texto visible de los 5 capítulos baja un 34.7% en conjunto (calidad −67%, modelos −33%, relaciones −19%, agregacion −15%, correlacion −8%). Tests actualizados (calidad_test asertaba el texto viejo). Suite EDA + pipeline verde (118 passed). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../automatic_eda/chapters/agregacion.py | 12 +++-- .../automatic_eda/chapters/calidad.py | 45 ++++++++----------- .../automatic_eda/chapters/calidad_test.py | 8 ++-- .../automatic_eda/chapters/correlacion.py | 11 +++-- .../automatic_eda/chapters/modelos.py | 44 ++++++++---------- .../automatic_eda/chapters/relaciones.py | 35 +++++++-------- 6 files changed, 69 insertions(+), 86 deletions(-) diff --git a/python/functions/datascience/automatic_eda/chapters/agregacion.py b/python/functions/datascience/automatic_eda/chapters/agregacion.py index c6eafcf8..ca29a4e3 100644 --- a/python/functions/datascience/automatic_eda/chapters/agregacion.py +++ b/python/functions/datascience/automatic_eda/chapters/agregacion.py @@ -561,13 +561,11 @@ def _intro_blocks(gloss=None, mark_term: bool = False) -> list: t_groupby = _term(mark_term, "groupby", "**por grupos** (split-apply-combine)") t_pivot = _term(mark_term, "pivot_table", "**tablas dinámicas** (pivot)") text = ( - f"Este capítulo analiza la tabla {t_groupby}: " - "elige las columnas categóricas más informativas — por su cardinalidad " - "y relevancia, no todas contra todas, para no inflar comparaciones " - "espurias — y resume las variables numéricas dentro de cada grupo " - f"(conteo, media, mediana, desviación). Las {t_pivot} " - "cruzan dos categóricas sobre una medida, y los **gráficos de barras** " - "(siempre desde cero) comparan los grupos de un vistazo." + f"Este capítulo analiza la tabla {t_groupby}: elige las columnas " + "categóricas más informativas (por cardinalidad y relevancia, no todas " + "contra todas) y resume las variables numéricas dentro de cada grupo " + f"(conteo, media, mediana, desviación). Se añaden {t_pivot} y " + "**gráficos de barras** (siempre desde cero) para comparar los grupos." ) return [model.Heading(text=CHAPTER_TITLE, level=1), model.Markdown(text=text)] diff --git a/python/functions/datascience/automatic_eda/chapters/calidad.py b/python/functions/datascience/automatic_eda/chapters/calidad.py index 93ef9423..aaaebcaf 100644 --- a/python/functions/datascience/automatic_eda/chapters/calidad.py +++ b/python/functions/datascience/automatic_eda/chapters/calidad.py @@ -3,12 +3,13 @@ Builds the quality chapter from a ``TableProfile`` of the ``eda`` group. The chapter implements the quality model of report 2046: -1. **En qué se basa la calidad** — an intro paragraph explaining the two scored +1. **En qué se basa la calidad** — a concise intro naming the two scored dimensions and their weights (completitud 60%, validez 40%) plus the - table-level row uniqueness, BEFORE any number, and stating explicitly that - outliers are reported as observations and do **not** lower the score. The - criteria terms (calidad de datos, completitud, validez, unicidad de registro) - are hooked into the shared glossary as clickable jumps. + table-level row uniqueness, BEFORE any number, and stating that outliers are + reported as observations and do **not** lower the score. The criteria terms + (calidad de datos, completitud, validez, unicidad de registro) are hooked + into the shared glossary as clickable jumps; their full definitions live in + the GLOSARIO chapter, not inline here. 2. **Scores por columna** — a table with, per column, the total quality score and its breakdown into completeness / validity (no consistency dimension). 3. **Problemas de calidad** — a table listing ONLY real quality defects @@ -309,30 +310,22 @@ def _term(key: str, label: str, mark: bool) -> str: def _criteria_intro(mark: bool) -> str: - """Intro paragraph explaining the two scored dimensions and the principle.""" + """Intro: how the score is composed, with every term marked clickable. + + Concise on purpose: the definitions of each term (calidad de datos, + completitud, validez, unicidad de registro) now live in the GLOSARIO + chapter, so the body no longer repeats them — it only states how the score + is composed and keeps each term marked so it stays a clickable jump. + """ calidad = _term("calidad_datos", "calidad de datos", mark) - completitud = _term("completitud", "Completitud (peso 60%)", mark) - validez = _term("validez", "Validez (peso 40%, cuando es medible)", mark) + completitud = _term("completitud", "completitud", mark) + validez = _term("validez", "validez", mark) unicidad = _term("unicidad_registro", "unicidad de registro", mark) return ( - f"La {calidad} de cada columna es un score de 0 a 100 que combina solo " - "dimensiones medibles desde el perfil de la tabla, sin fuente externa " - "de verdad:\n\n" - f"- {completitud}: proporción de valores presentes (1 − % de nulos; en " - "texto, las celdas vacías cuentan como faltantes). Los nulos y vacíos " - "bajan el score.\n" - f"- {validez}: proporción de valores que encajan con su tipo o formato " - "(un número que parsea, una fecha legible, un email con forma de email). " - "Si una columna es texto libre sin formato esperado, la validez no se " - "mide y el score se basa solo en la completitud.\n\n" - f"Score de columna = 100 × (0,6·completitud + 0,4·validez), " - "renormalizado cuando la validez no aplica. A nivel de tabla se añade " - f"la {unicidad} (1 − % de filas duplicadas).\n\n" - "**Los valores atípicos (outliers) NO bajan la calidad.** Un valor " - "extremo puede ser real y correcto; detectar atípicos es parte del " - "análisis de la distribución, no un juicio de corrección. Por eso, junto " - "con las columnas constantes y los identificadores, se listan aparte " - "como **observaciones analíticas** que no afectan al score." + f"La {calidad} de cada columna es un score de 0 a 100 que combina " + f"{completitud} (peso 60%) y {validez} (peso 40%, cuando es medible); " + f"a nivel de tabla se añade la {unicidad}. Los valores atípicos no " + "bajan el score: se listan aparte como **observaciones analíticas**." ) diff --git a/python/functions/datascience/automatic_eda/chapters/calidad_test.py b/python/functions/datascience/automatic_eda/chapters/calidad_test.py index 5dc623ee..5a31b497 100644 --- a/python/functions/datascience/automatic_eda/chapters/calidad_test.py +++ b/python/functions/datascience/automatic_eda/chapters/calidad_test.py @@ -72,14 +72,16 @@ def test_golden_chapter_estructura_y_version(): assert "markdown" in kinds and "kv_table" in kinds and "data_table" in kinds -def test_golden_intro_explica_dos_dimensiones_y_pesos(): +def test_golden_intro_nombra_dos_dimensiones_y_pesos(): + # La intro nombra las dos dimensiones, sus pesos y la unicidad, pero ya NO + # repite sus definiciones largas: estas viven ahora en el capítulo GLOSARIO. ch = build_calidad(_profile(), {}) intro = [b for b in ch.blocks if b.kind == "markdown"][0].text - for needle in ("Completitud", "Validez", "60%", "40%", + for needle in ("completitud", "validez", "60%", "40%", "unicidad de registro"): assert needle in intro, f"falta {needle!r} en la intro de criterios" # El principio: los outliers NO bajan la calidad. - assert "atípicos" in intro and "NO bajan" in intro + assert "atípicos" in intro and "no bajan" in intro # Ya no se menciona la dimensión consistencia eliminada. assert "20%" not in intro diff --git a/python/functions/datascience/automatic_eda/chapters/correlacion.py b/python/functions/datascience/automatic_eda/chapters/correlacion.py index 0c906cc9..cd559323 100644 --- a/python/functions/datascience/automatic_eda/chapters/correlacion.py +++ b/python/functions/datascience/automatic_eda/chapters/correlacion.py @@ -356,12 +356,11 @@ def build_correlacion(profile: dict, ctx: dict): t_cramers = _term(mark_term, "cramers_v", "Cramér's V") t_corr_ratio = _term(mark_term, "correlation_ratio", "razón de correlación") blocks.append(model.Markdown(text=( - "Asociación entre columnas. Cada par se evalúa con la métrica adecuada a " - f"sus tipos ({t_pearson}/{t_spearman} entre numéricas — con **signo**; " - f"{t_cramers} entre categóricas; {t_corr_ratio} num-categórica; " - "información mutua como medida común no lineal). Sólo las correlaciones " - "**num-num** tienen dirección: por eso los pares **negativos** son siempre " - "num-num."))) + "Asociación entre columnas. Cada par se evalúa con la métrica adecuada " + f"a sus tipos: {t_pearson}/{t_spearman} (numéricas), {t_cramers} " + f"(categóricas), {t_corr_ratio} (num-categórica) e información mutua. " + "Sólo las correlaciones **num-num** llevan **signo** (dirección): por " + "eso los pares **negativos** son siempre num-num."))) # 1) Association matrix (heatmap). labels, trimmed = _ordered_labels(pairs) diff --git a/python/functions/datascience/automatic_eda/chapters/modelos.py b/python/functions/datascience/automatic_eda/chapters/modelos.py index 1ddf78ee..77fc54b4 100644 --- a/python/functions/datascience/automatic_eda/chapters/modelos.py +++ b/python/functions/datascience/automatic_eda/chapters/modelos.py @@ -6,15 +6,16 @@ normality}``). It renders, as structured markdown/tables/figures that the core paginator never cuts: 1. **Normalization note** — every multivariate model below standardizes the - columns with z-score first; the chapter explains why (different scales would - otherwise dominate distance/variance). + columns with z-score first (the term is marked clickable; its definition + lives in the GLOSARIO chapter, not inline). 2. **PCA** — a scree plot (explained + cumulative variance, single Y axis) plus variance and top-loadings tables. 3. **KMeans segments** — a PCA scatter **coloured by cluster** (its own page/slide), the cluster-size table, and a per-cluster LLM micro-analysis with a title for each segment. -4. **Isolation Forest outliers** — a short explanation of how anomalous rows are - isolated multivariately and how the threshold is chosen, plus the counts. +4. **Isolation Forest outliers** — the multivariate anomaly counts and decision + threshold (the method is marked clickable; its definition lives in the + GLOSARIO chapter, not inline). 5. **Normality** — per-column Jarque-Bera / D'Agostino / Shapiro verdicts. The raw numeric data needed to colour the cluster scatter is **not** in the @@ -314,12 +315,8 @@ def _normalization_intro(gloss=None, mark_term: bool = False) -> list: text = ( "Estos modelos son **no supervisados**: buscan estructura latente sin " "una variable objetivo. Antes de aplicarlos, todas las columnas " - f"numéricas se {zscore} (cada valor menos la media, dividido por la " - "desviación típica). Sin esta normalización, una variable con escala " - "grande (p.ej. ingresos en euros) dominaría las distancias y la varianza " - "frente a otra de escala pequeña (p.ej. un ratio entre 0 y 1), sesgando " - "tanto el PCA como el KMeans. Tras la estandarización todas las variables " - "pesan por igual." + f"numéricas se {zscore}, para que todas pesen por igual con " + "independencia de su escala." ) return [model.Heading(text="Modelos no supervisados", level=1), model.Markdown(text=text)] @@ -334,11 +331,11 @@ def _pca_section(pca: dict, gloss=None, mark_term: bool = False) -> list: n_used = pca.get("n_rows_used") n_feat = pca.get("n_features") intro = ( - f"El {_term(mark_term, 'pca', 'PCA')} resume {_fmt_num(n_feat)} variables " - "numéricas en componentes ortogonales ordenados por la varianza que " - f"capturan ({_fmt_num(n_used)} filas usadas tras eliminar nulos). El " - "gráfico de sedimentación (scree) muestra cuánta varianza aporta cada " - "componente y su acumulado: un codo marca cuántos componentes bastan." + f"El {_term(mark_term, 'pca', 'PCA')} se aplica sobre " + f"{_fmt_num(n_feat)} variables numéricas ({_fmt_num(n_used)} filas " + "usadas tras eliminar nulos). El gráfico de sedimentación (scree) " + "muestra cuánta varianza aporta cada componente y su acumulado: un " + "codo marca cuántos componentes bastan." ) blocks.append(model.Markdown(text=intro)) @@ -403,9 +400,8 @@ def _kmeans_section(kmeans: dict, projection: dict, titles, t_sil = _term(mark_term, "silhouette", "*silhouette*") intro = ( f"{t_kmeans} agrupa las filas en **{_fmt_num(best_k)} segmentos** " - f"elegidos automáticamente maximizando el coeficiente de {t_sil} " - f"(**{_fmt_num(sil)}**, rango −1 a 1: cuanto más alto, segmentos más " - "compactos y separados). Los segmentos se proyectan sobre el plano de " + f"elegidos automáticamente por el coeficiente de {t_sil} " + f"(**{_fmt_num(sil)}**). Los segmentos se proyectan sobre el plano de " "los dos primeros componentes principales para visualizarlos." ) blocks.append(model.Markdown(text=intro)) @@ -469,14 +465,10 @@ def _outliers_section(outliers: dict, gloss=None, mark_term: bool = False) -> li level=2)] isof = _term(mark_term, "isolation_forest", "**Isolation Forest**") explain = ( - f"{isof} detecta filas anómalas de forma *multivariante*: " - "construye árboles que parten el espacio con cortes aleatorios y mide " - "cuántos cortes hacen falta para aislar cada fila. Las filas raras " - "(combinaciones de valores poco frecuentes considerando **todas las " - "columnas a la vez**, no una sola) se aíslan con muy pocos cortes y " - "obtienen un score bajo. El **umbral** de decisión separa las filas " - "normales de las anómalas según la contaminación esperada del modelo: " - "una fila es outlier cuando su score queda por debajo de ese umbral." + f"{isof} marca filas anómalas de forma *multivariante*: combinaciones " + "de valores poco frecuentes considerando **todas las columnas a la " + "vez**, no una sola. La tabla resume cuántas se detectaron y el umbral " + "de decisión empleado." ) blocks.append(model.Markdown(text=explain)) blocks.append(model.KVTable(rows=[ diff --git a/python/functions/datascience/automatic_eda/chapters/relaciones.py b/python/functions/datascience/automatic_eda/chapters/relaciones.py index eba05f76..7e593a96 100644 --- a/python/functions/datascience/automatic_eda/chapters/relaciones.py +++ b/python/functions/datascience/automatic_eda/chapters/relaciones.py @@ -256,14 +256,14 @@ def _pk_candidates_section(profile: dict, mark: bool) -> list: pk = ("[[term:pk]]**clave primaria**[[/term]]" if mark else "**clave primaria**") intro = ( - f"Estas columnas son **candidatas a {pk}**: su " - "[[term:cardinalidad]]cardinalidad[[/term]] iguala al número de filas y no " - "tienen nulos, así que cada valor identifica una fila distinta. Son " - "candidatas, no una clave declarada: la base no las marca como tal." + f"Columnas **candidatas a {pk}**: su " + "[[term:cardinalidad]]cardinalidad[[/term]] iguala al número de filas y " + "no tienen nulos. Son candidatas, no una clave declarada: la base no " + "las marca como tal." if mark else - "Estas columnas son **candidatas a clave primaria**: su cardinalidad " - "iguala al número de filas y no tienen nulos, así que cada valor " - "identifica una fila distinta.") + "Columnas **candidatas a clave primaria**: su cardinalidad iguala al " + "número de filas y no tienen nulos. Son candidatas, no una clave " + "declarada.") rows = [] for name in keys: @@ -320,10 +320,10 @@ def _inter_table_section(db_path: str, tables: list, mark: bool) -> list: blocks = [ model.Heading(text="Claves foráneas candidatas (inter-tabla)", level=2), model.Markdown(text=( - f"La fuente tiene varias tablas. Estas {fk_term} candidatas se infieren " - f"por señal de nombre y por {containment}: una columna de una tabla cuyos " - "valores están contenidos en la clave de otra. No están declaradas por " - "la base; son la relación más probable según los datos.")), + f"La fuente tiene varias tablas. Estas {fk_term} candidatas se " + f"infieren por señal de nombre y por {containment}. No están " + "declaradas por la base; son la relación más probable según los " + "datos.")), ] shown = candidates[:MAX_FK_ROWS] @@ -441,13 +441,12 @@ def _intro_blocks(mark: bool) -> list: pk = "[[term:pk]]clave primaria[[/term]]" if mark else "clave primaria" fk = "[[term:fk]]clave foránea[[/term]]" if mark else "clave foránea" text = ( - f"Este capítulo analiza las **relaciones de clave** de la tabla: qué columna " - f"identifica cada fila (la {pk}) y qué columnas referencian a otra tabla (las " - f"{fk}). Cuando la base las **declara** como restricciones del esquema, se " - "muestran tal cual; cuando no, se proponen las más probables a partir de los " - "datos —por inclusión de valores entre tablas (containment) o, en una sola " - "tabla, por una heurística de nombre y cardinalidad— siempre marcadas como " - "candidatas, nunca como hechos.") + f"Este capítulo analiza las **relaciones de clave** de la tabla: cuál es " + f"la {pk} y cuáles son las {fk}. Cuando la base las **declara** como " + "restricciones del esquema, se muestran tal cual; cuando no, se proponen " + "las más probables a partir de los datos —por containment entre tablas o, " + "en una sola tabla, por una heurística de nombre y cardinalidad— siempre " + "marcadas como candidatas, nunca como hechos.") return [model.Heading(text=CHAPTER_TITLE, level=1), model.Markdown(text=text)]