Files
fn_registry/python/functions/datascience/build_boxplots_figure.md
T
egutierrez 6f88f184f1 feat(eda): capítulo OUTLIERS — valores atípicos univariantes + multivariantes
Nuevo capítulo dedicado `outliers` para el motor AutomaticEDA que reúne y
profundiza en un solo sitio el análisis de valores atípicos, hoy disperso entre
`num_distr` (conteo por columna) y `modelos` (IsolationForest). Se registra en
`chapters_registry.py` entre `missingness` y `correlacion` (bloque de calidad de
datos: calidad → missingness → outliers).

Contenido del capítulo:
- Resumen univariante por columna: nº y % de atípicos por Tukey (1.5·IQR) y por
  z-score (|z| > 3), con vallas inferior/superior y valores extremos. Ordenado
  por contaminación y marcando las columnas más afectadas. Reusa las funciones
  del registry `build_boxplot_stats` (vallas desde los percentiles del profile)
  y `detect_outliers` (regla z-score sobre la muestra cruda de `ctx`).
- Boxplots de Tukey de las columnas más contaminadas (caja, bigotes y puntos
  atípicos), delegados a la función nueva `build_boxplots_figure`.
- Multivariante: filas anómalas considerando todas las columnas a la vez con
  `isolation_forest_outliers` — nº y % de filas, las más anómalas con su score y
  las dimensiones que las hacen raras (top columnas por |z|, vía la función nueva
  `summarize_outlier_dims`). El detector se corre en vivo sobre `raw_numeric`
  para que el indexado de filas coincida exactamente con el de las dimensiones;
  cae al bloque precomputado del perfil cuando no hay muestra cruda (preset lite).
- Interpretación exploratoria: un atípico no es necesariamente un error
  (distingue error de dato vs dato real extremo) y recomendaciones (revisar,
  winsorizar o re-expresar, enlazando con la re-expresión de Tukey del perfil).

Términos clicables registrados en el glosario compartido: `outlier`,
`tukey_fence`, `zscore`, `isolation_forest`.

Funciones nuevas del registry (dominio datascience, grupo eda):
- `build_boxplots_figure_py_datascience` (figure helper, impura)
- `summarize_outlier_dims_py_datascience` (pura)

El capítulo se activa con ≥1 columna numérica y devuelve None en su ausencia;
lee todo defensivo y nunca lanza. Tests: capítulo (golden + edges + error path +
render PDF/PPTX) y ambas funciones nuevas. Suite de no-regresión de AutomaticEDA
verde. Verificado end-to-end con el dataset Titanic (Fare/Parch/SibSp como las
columnas más contaminadas).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:12:40 +02:00

8.1 KiB

id, name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, example, tested, tests, test_file_path, file_path, params, output
id name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports example tested tests test_file_path file_path params output
build_boxplots_figure_py_datascience build_boxplots_figure function py datascience 1.0.0 impure def build_boxplots_figure(boxes: list, title: str = "", max_boxes: int = 12) -> "matplotlib.figure.Figure" Construye una unica figura matplotlib con boxplots de Tukey HORIZONTALES (uno por columna) usando ax.bxp: caja Q1-Q3, bigotes hasta 1.5*IQR, linea de mediana y puntos atipicos. Consume la salida de build_boxplot_stats (un dict box por columna, leido con .get) mas una lista opcional de outliers crudos por columna; si vienen los dibuja como puntos (showfliers), si no marca solo box[min]/box[max] cuando hay outliers de cola (igual que num_distr). Dibuja como mucho max_boxes cajas (las primeras, ya ordenadas por contaminacion por el caller) y avisa de la truncacion con (mostrando N de M). Backend Agg sin pyplot global; alto adaptativo al nº de cajas. Defensiva: omite entradas invalidas y NUNCA lanza — sin cajas validas devuelve una figura placeholder (sin boxplots). Es la version small-multiples del capitulo num_distr para responder que columnas tienen mas outliers de un vistazo.
eda
outliers
boxplot
tukey
iqr
bxp
matplotlib
figure
visualization
small-multiples
datascience
impure
false error_go_core
matplotlib
from datascience.build_boxplot_stats import build_boxplot_stats from datascience.build_boxplots_figure import build_boxplots_figure boxes = [ {"name": "ingresos", "box": build_boxplot_stats({"min": 1.0, "max": 9e3, "p25": 1e3, "median": 2e3, "p75": 3e3, "n_outliers": 7}), "fliers": None}, {"name": "edad", "box": build_boxplot_stats({"min": 0.0, "max": 99.0, "p25": 25.0, "median": 38.0, "p75": 52.0}), "fliers": None}, ] fig = build_boxplots_figure(boxes, title="Outliers por columna", max_boxes=12) true
test_returns_figure_with_axes
test_empty_list_returns_placeholder_figure
test_invalid_box_is_skipped_not_raised
test_all_invalid_returns_placeholder
test_raw_fliers_are_drawn
test_max_boxes_truncates_and_does_not_raise
python/functions/datascience/build_boxplots_figure_test.py python/functions/datascience/build_boxplots_figure.py
name desc
boxes Lista de dicts, cada uno {"name": str, "box": dict, "fliers": list|None}. box es EXACTAMENTE la salida de build_boxplot_stats (claves leidas con .get: q1, median, q3, whisker_lo, whisker_hi, min, max, has_low_outliers, has_high_outliers, lower_fence, upper_fence, n_outliers). fliers es la lista opcional de outliers crudos: si viene se dibuja como puntos; si es None/ausente solo se marcan los extremos box[min]/box[max] cuando hay outliers de cola. Entradas que no son dict, sin box dict, o sin q1/median/q3 se omiten. El caller las pasa ya ordenadas por contaminacion (la mayor primera).
name desc
title Titulo de la figura (fig.suptitle, alineado a la izquierda). Vacio => sin titulo. Si len(boxes) > max_boxes se le anade una nota "(mostrando N de M)" para que la truncacion no sea silenciosa. Default "".
name desc
max_boxes Numero maximo de cajas a dibujar (las primeras de la lista). Default 12. Un valor no entero o <= 0 cae a 12. Si la lista trae mas entradas, las sobrantes se descartan pero se reporta en el titulo con (mostrando N de M).
Un matplotlib.figure.Figure (figsize 7.0 x alto adaptativo = max(2.0, 0.5*n + 1.0), dpi 150) con un unico Axes que apila boxplots horizontales de Tukey (ax.bxp, orientation=horizontal con fallback vert=False), uno por columna valida, de arriba a abajo en el orden recibido. Cada caja: relleno #9ec6df, borde/bigotes/caps #5b8aa6, mediana #2e8b57, atipicos #c0392b. Etiquetas del eje Y = nombres de columna; eje X etiquetado "valor". Outliers dibujados desde fliers crudos (showfliers) o, si faltan, marcados en box[min]/box[max] segun has_low/high_outliers. Si no queda ninguna caja valida (lista vacia o todas invalidas) devuelve una Figure placeholder con texto centrado "(sin boxplots)"; cualquier error inesperado se captura y devuelve una Figure con el mensaje de error. NUNCA lanza. El caller rasteriza/cierra la figura; la funcion no la muestra ni la guarda.

Ejemplo

import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from datascience.build_boxplot_stats import build_boxplot_stats
from datascience.build_boxplots_figure import build_boxplots_figure

# Un `box` por columna numérica, derivado del sub-bloque `numeric` del profile
# (salida de describe_numeric). El caller los pasa ya ordenados por outlier_pct.
boxes = [
    {
        "name": "ingresos",
        "box": build_boxplot_stats({
            "min": 1.0, "max": 9000.0,
            "p25": 1000.0, "median": 2000.0, "p75": 3000.0,
            "n_outliers": 7,
        }),
        "fliers": None,  # valores crudos desconocidos -> se marca solo el extremo.
    },
    {
        "name": "edad",
        "box": build_boxplot_stats({
            "min": 0.0, "max": 99.0,
            "p25": 25.0, "median": 38.0, "p75": 52.0,
        }),
        "fliers": [88.0, 95.0, 99.0],  # outliers crudos -> se dibujan como puntos.
    },
]

fig = build_boxplots_figure(boxes, title="Outliers por columna", max_boxes=12)

# El renderer del informe lo rasteriza; aquí solo persistimos para inspección.
fig.savefig("/tmp/boxplots.png")

Cuando usarla

Úsala en el capítulo de outliers de un informe EDA cuando quieras comparar de un vistazo qué columnas están más contaminadas por valores atípicos: a diferencia de num_distr (que dibuja un histograma+boxplot por columna en figuras separadas), aquí apilas todos los boxplots horizontales en una sola figura (small multiples). Primero deriva el box de cada columna con build_boxplot_stats, ordénalas por outlier_pct descendente, envuélvelas como {"name", "box", "fliers"} y pásaselas. Si tienes los valores crudos fuera de las vallas, métele la lista fliers y se dibujarán como puntos; si no, la función marca solo los extremos min/max cuando hay cola.

Gotchas

  • Impura por matplotlib. Toca la maquinaria de render. Usa el backend Agg y la API orientada a objetos Figure/add_subplot — NUNCA pyplot.* aquí, para no tocar el estado global ni filtrar figuras entre llamadas. pyplot NO es thread-safe; esta función construye el Figure directamente, así que es segura de llamar en bucle desde el renderer.
  • El caller cierra la figura. Devuelve el Figure pero no lo muestra ni lo guarda. Quien la consume debe rasterizarla y luego liberarla (matplotlib.pyplot.close(fig)) para no acumular memoria en lotes grandes.
  • fliers opcional, semántica distinta. Si pasas la lista de outliers crudos se dibujan todos como puntos (showfliers=True). Si es None/ausente los valores son desconocidos y solo se marca un punto en box["min"] / box["max"] cuando has_low_outliers / has_high_outliers — mismo criterio que num_distr. No inventes fliers a partir del profile: el box no trae los valores crudos, solo si los extremos superan las vallas.
  • API de orientación de ax.bxp. matplotlib reciente usa orientation="horizontal"; las versiones antiguas usan vert=False. La función prueba la primera y cae a la segunda en except TypeError, así que funciona en ambas. Si bxp falla del todo, el Axes degrada a un texto "(boxplot no disponible)" en vez de propagar.
  • Truncación visible. max_boxes (default 12) limita el nº de cajas para que ninguna se solape; si la lista trae más, las sobrantes se descartan pero se avisa en el título con "(mostrando N de M)". Pasa las columnas ya ordenadas por contaminación para que las descartadas sean las menos relevantes.
  • Defensiva, nunca lanza. Lista vacía, entradas no-dict, sin box, o sin q1/median/q3 se omiten sin propagar; sin cajas válidas devuelve un placeholder "(sin boxplots)" y cualquier error inesperado se captura en una figura con el texto del error. No envuelvas la llamada en try/except por miedo a un raise — no lo hay.