feat(eda): engancha glosario clicable en correlacion/modelos/agregacion
Fase 4b — extiende el glosario clicable de AutomaticEDA (mecanismo ya probado end-to-end con `entropia` en cat_distr) a tres capítulos más, siguiendo el contrato sección 11 (glossary.add(key,label,def) + span [[term:KEY]]texto[[/term]]): - correlacion: Pearson, Spearman, Cramér's V, razón de correlación (η) y la corrección por comparaciones múltiples (FDR). Los métodos se marcan en el intro (siempre presente); FDR se registra y marca solo cuando se emite su resumen, para no dejar entradas de glosario sin aparición que las referencie. - modelos: PCA, KMeans, coeficiente de silueta (silhouette), Isolation Forest y la estandarización z-score. Cada término se registra dentro de la sección que lo usa (tras su early-return), de modo que un término solo entra al glosario cuando su sección realmente se renderiza. - agregacion: agrupación (split-apply-combine / groupby) y tabla dinámica (pivot), ambos en el intro siempre presente. Solo se añaden los enganches de glosario: ningún cambio en la lógica de datos. El texto visible es idéntico con o sin marcador (los renderers lo eliminan), así que el layout de línea no cambia. Sin colector en ctx (render suelto) los capítulos degradan y no marcan nada. Tests: un test de glosario por capítulo verifica registro + marcado y la degradación sin colector. Suite AutomaticEDA + render pipeline: 87 passed. Golden titanic (run_models+series+llm): los 12 términos aparecen como entradas del glosario en PDF (16 link annotations GOTO) y PPTX (15 saltos hlinksldjump). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,35 @@ _DEF_MAX_CARD = 20
|
||||
_DEF_MAX_MEASURES = 4
|
||||
_DEF_TOP_N = 12
|
||||
|
||||
# Glossary terms this chapter explains. Both appear in the always-rendered intro,
|
||||
# so they are registered and marked clickable whenever a collector is in ctx —
|
||||
# the canonical two-step pattern (see ``cat_distr``): ``glossary.add(key, label,
|
||||
# definition)`` + the inline span ``[[term:KEY]]texto[[/term]]`` in a Markdown
|
||||
# block. Mapping key -> (label, definition).
|
||||
_TERM_DEFS = {
|
||||
"groupby": (
|
||||
"Agrupación (split-apply-combine)",
|
||||
"Operación de agrupación (group by): parte la tabla en grupos según los "
|
||||
"valores de una columna categórica, aplica un cálculo (conteo, media, "
|
||||
"mediana…) dentro de cada grupo y combina los resultados en una tabla "
|
||||
"resumen. Es el patrón split-apply-combine."),
|
||||
"pivot_table": (
|
||||
"Tabla dinámica (pivot)",
|
||||
"Tabla dinámica que cruza dos variables categóricas — una en las filas y "
|
||||
"otra en las columnas — y rellena cada celda con un agregado (media, "
|
||||
"suma…) de una medida numérica. Resume de un vistazo cómo interactúan las "
|
||||
"dos categóricas sobre esa medida."),
|
||||
}
|
||||
|
||||
|
||||
def _term(mark: bool, key: str, text: str) -> str:
|
||||
"""Wrap ``text`` as a clickable glossary span when ``mark`` is True.
|
||||
|
||||
The visible text is identical with or without the marker (the renderers strip
|
||||
it), so wrapping never changes line layout — it only adds the link.
|
||||
"""
|
||||
return f"[[term:{key}]]{text}[[/term]]" if mark else text
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Formatting helpers (mirror the other chapters' defensive style).
|
||||
@@ -525,13 +554,18 @@ def _sections_live(profile: dict, ctx: dict, candidates: dict) -> list:
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Entry point.
|
||||
# --------------------------------------------------------------------------- #
|
||||
def _intro_blocks() -> list:
|
||||
def _intro_blocks(gloss=None, mark_term: bool = False) -> list:
|
||||
if gloss is not None:
|
||||
for key, (label, definition) in _TERM_DEFS.items():
|
||||
gloss.add(key, label, definition)
|
||||
t_groupby = _term(mark_term, "groupby", "**por grupos** (split-apply-combine)")
|
||||
t_pivot = _term(mark_term, "pivot_table", "**tablas dinámicas** (pivot)")
|
||||
text = (
|
||||
"Este capítulo analiza la tabla **por grupos** (split-apply-combine): "
|
||||
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 "
|
||||
"(conteo, media, mediana, desviación). Las **tablas dinámicas** (pivot) "
|
||||
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."
|
||||
)
|
||||
@@ -556,13 +590,21 @@ def build_agregacion(profile: dict, ctx: dict):
|
||||
if not isinstance(profile, dict):
|
||||
return None
|
||||
|
||||
# Shared glossary collector: groupby + pivot_table live in the always-present
|
||||
# intro, so they are registered + marked there. Degrades silently (mark_term
|
||||
# False) when no collector is in ctx (standalone render).
|
||||
glossary = ctx.get("glossary")
|
||||
gloss = glossary if isinstance(glossary, model.GlossaryCollector) else None
|
||||
mark_term = gloss is not None
|
||||
|
||||
# Pre-computed results take precedence (offline / tests / forward-compat).
|
||||
pre = ctx.get("aggregations")
|
||||
if _is_dict(pre) and (pre.get("groupby") or pre.get("pivots")):
|
||||
sections = _sections_from_precomputed(pre)
|
||||
if not sections:
|
||||
return None
|
||||
blocks = _intro_blocks() + sections + _insights_section(ctx)
|
||||
blocks = (_intro_blocks(gloss, mark_term) + sections
|
||||
+ _insights_section(ctx))
|
||||
return model.Chapter(id=CHAPTER_ID, title=CHAPTER_TITLE,
|
||||
version=CHAPTER_VERSION, blocks=blocks)
|
||||
|
||||
@@ -583,10 +625,11 @@ def build_agregacion(profile: dict, ctx: dict):
|
||||
"crudos. Pasa ctx['db_path'] + ctx['table'] (para el cálculo "
|
||||
"push-down en DuckDB) o ctx['aggregations'] ya precalculado. "
|
||||
f"Columnas categóricas candidatas: {keys or '—'}.")
|
||||
blocks = _intro_blocks() + [note] + _insights_section(ctx)
|
||||
blocks = (_intro_blocks(gloss, mark_term) + [note]
|
||||
+ _insights_section(ctx))
|
||||
return model.Chapter(id=CHAPTER_ID, title=CHAPTER_TITLE,
|
||||
version=CHAPTER_VERSION, blocks=blocks)
|
||||
|
||||
blocks = _intro_blocks() + sections + _insights_section(ctx)
|
||||
blocks = _intro_blocks(gloss, mark_term) + sections + _insights_section(ctx)
|
||||
return model.Chapter(id=CHAPTER_ID, title=CHAPTER_TITLE,
|
||||
version=CHAPTER_VERSION, blocks=blocks)
|
||||
|
||||
Reference in New Issue
Block a user