fix(eda): keep-together de grafico+titulo+descripcion en 5 capitulos
modelos, timeseries, geospatial, agregacion y missingness (bloque de ranking) emitian Heading+Figure sueltos, de modo que el paginador podia dejar el titulo y la descripcion de una columna/par en una pagina y su grafico en la siguiente. Se envuelve cada unidad (Heading + descripcion/tablas + Figure) en un model.Group, la unidad keep-together que ambos renderers (PDF/PPTX) miden entera y mueven en bloque cuando no cabe, siguiendo el patron ya usado por num_distr y correlacion. Orden y contenido de bloques identicos: solo se envuelven. La degradacion honesta se conserva (una figura None nunca queda dentro de un Group vacio). Los tests que asertaban figuras sueltas se ajustaron para comprobar la Figure DENTRO del Group, sin relajar ningun assert. Bump CHAPTER_VERSION PATCH (1.0.0->1.0.1) en los 5 capitulos. El heatmap de co-ocurrencia de missingness ya iba agrupado y no se toca.
This commit is contained in:
@@ -45,7 +45,10 @@ from __future__ import annotations
|
||||
|
||||
from .. import model
|
||||
|
||||
CHAPTER_VERSION = "1.0.0"
|
||||
# 1.0.1 — keep-together: cada gráfico (scree PCA, scatter KMeans) se envuelve con
|
||||
# su Heading + su Markdown introductorio en un model.Group para que el paginador
|
||||
# no separe el gráfico de su título/descripción.
|
||||
CHAPTER_VERSION = "1.0.1"
|
||||
CHAPTER_ID = "modelos"
|
||||
CHAPTER_TITLE = "Modelos"
|
||||
|
||||
@@ -326,7 +329,6 @@ def _pca_section(pca: dict, gloss=None, mark_term: bool = False) -> list:
|
||||
if not _is_dict(pca) or not pca.get("explained_variance_ratio"):
|
||||
return []
|
||||
_register(gloss, "pca")
|
||||
blocks = [model.Heading(text="PCA — varianza explicada", level=2)]
|
||||
|
||||
n_used = pca.get("n_rows_used")
|
||||
n_feat = pca.get("n_features")
|
||||
@@ -337,12 +339,20 @@ def _pca_section(pca: dict, gloss=None, mark_term: bool = False) -> list:
|
||||
"muestra cuánta varianza aporta cada componente y su acumulado: un "
|
||||
"codo marca cuántos componentes bastan."
|
||||
)
|
||||
blocks.append(model.Markdown(text=intro))
|
||||
|
||||
# Keep-together: the heading, its intro and the scree figure ride together on
|
||||
# the same page/slide (the renderers measure the whole Group and move it whole
|
||||
# if it does not fit), so the scree never gets stranded from its title. The
|
||||
# variance/loadings tables (split-safe) flow after the group.
|
||||
unit = [model.Heading(text="PCA — varianza explicada", level=2),
|
||||
model.Markdown(text=intro)]
|
||||
scree = _make_scree(pca)
|
||||
if scree is not None:
|
||||
blocks.append(model.Figure(
|
||||
unit.append(model.Figure(
|
||||
make=scree, caption="Varianza explicada y acumulada por componente."))
|
||||
blocks = [model.Group(blocks=unit)]
|
||||
else:
|
||||
blocks = list(unit)
|
||||
|
||||
evr = pca.get("explained_variance_ratio") or []
|
||||
cum = pca.get("cumulative") or []
|
||||
@@ -390,8 +400,6 @@ def _kmeans_section(kmeans: dict, projection: dict, titles,
|
||||
_register(gloss, "kmeans")
|
||||
_register(gloss, "silhouette")
|
||||
|
||||
blocks = [model.Heading(text="Segmentación (KMeans)", level=2)]
|
||||
|
||||
best_k = (projection or {}).get("best_k") or (kmeans or {}).get("best_k")
|
||||
sil = (projection or {}).get("silhouette")
|
||||
if sil is None:
|
||||
@@ -404,26 +412,31 @@ def _kmeans_section(kmeans: dict, projection: dict, titles,
|
||||
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))
|
||||
head = model.Heading(text="Segmentación (KMeans)", level=2)
|
||||
intro_md = model.Markdown(text=intro)
|
||||
|
||||
if has_proj:
|
||||
scatter = _make_cluster_scatter(projection)
|
||||
if scatter is not None:
|
||||
blocks.append(model.Figure(
|
||||
scatter = _make_cluster_scatter(projection) if has_proj else None
|
||||
if scatter is not None:
|
||||
# Keep-together: heading + intro + the cluster scatter on one page/slide.
|
||||
blocks = [model.Group(blocks=[
|
||||
head, intro_md,
|
||||
model.Figure(
|
||||
make=scatter,
|
||||
caption="Cada punto es una fila coloreada por su segmento "
|
||||
"KMeans; las «X» son los centroides."))
|
||||
else:
|
||||
blocks.append(model.Note(
|
||||
"Proyección de clusters no dibujable (puntos y etiquetas "
|
||||
"desalineados)."))
|
||||
"KMeans; las «X» son los centroides.")])]
|
||||
elif has_proj:
|
||||
# Points present but not drawable: honest note, kept flat (never a Group
|
||||
# wrapping a missing figure).
|
||||
blocks = [head, intro_md, model.Note(
|
||||
"Proyección de clusters no dibujable (puntos y etiquetas "
|
||||
"desalineados).")]
|
||||
else:
|
||||
# We have kmeans stats but no aligned points+labels to colour by.
|
||||
blocks.append(model.Note(
|
||||
blocks = [head, intro_md, model.Note(
|
||||
"Scatter coloreado por segmento no disponible: el perfil no incluye "
|
||||
"la proyección con etiquetas alineadas (pásala en "
|
||||
"ctx['cluster_projection'] o las columnas crudas en "
|
||||
"ctx['raw_numeric'] para colorear el plano PCA)."))
|
||||
"ctx['raw_numeric'] para colorear el plano PCA).")]
|
||||
|
||||
# Cluster sizes table.
|
||||
sizes = (projection or {}).get("cluster_sizes") or (kmeans or {}).get("cluster_sizes") or []
|
||||
|
||||
Reference in New Issue
Block a user