---
id: "0173"
title: "EDA: bugs críticos de correctitud estadística (outlier_pct ×100, distribution_type por-skew)"
status: completado
type: bugfix
domain:
  - registry-quality
scope: registry-only
priority: alta
depends: []
blocks: []
related: ["0174", "0175", "0176", "0177", "0068"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, profile_table, render_eda_markdown, describe_numeric, benchmark]
---
# 0173 — EDA: bugs críticos de correctitud estadística

## Contexto

Un benchmark adversarial del workflow `/eda` sobre 12 datasets reales (29/06/2026,
`temp/eda_benchmark/EVALUATION.md`) detectó que los estadísticos descriptivos base son
correctos, pero el **porcentaje de outliers que el report markdown muestra es imposible**
(supera el 100%, hasta 336%), engañando a un lector no experto con apariencia de autoridad.

Hallazgos cubiertos por este issue:

| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H1 — `outlier_pct` por-columna >100% en el report markdown | crítico | wine-red `chlorides` 193.87%, `density` 112.57% (skew 0.07); titanic `SibSp` 336.70%, `Fare` 224.47%; seattle `precipitation` 253.25% |
| H11 — `distribution_type` por-skew etiqueta mal discretas/ordinales/multimodales | bajo | wine `quality` (6 valores) → "normal-ish"; precios BTC multimodales → "normal-ish" (skew 0.45) |

### Causa raíz de H1 (verificada en código, READ-ONLY)

`EVALUATION.md` propuso "corregir la fórmula en `describe_numeric`". **Eso es incorrecto.** Al
leer el código:

- `python/functions/datascience/describe_numeric.py:113` calcula
  `outlier_pct = 100.0 * n_outliers / n` — ya en escala 0-100 y acotado a [0,100]. **Está bien.**
- `python/functions/datascience/render_eda_markdown.py:203-204` renderiza ese valor con
  `_fmt_pct(val)`, y `_fmt_pct` (líneas 31-44) hace `num * 100` porque **asume que su input es
  una fracción 0-1**. Resultado: **doble ×100** (un 1.94 real se muestra como 193.87%).
- El PDF (`render_eda_pdf.py:296`) usa `_fmt_num(outlier_pct, 1) + "%"` sin multiplicar — por eso
  el PDF muestra el outlier_pct correcto y el markdown no. El bug es **exclusivo del renderer
  markdown**.

El factor "19-40×" que observó el evaluador se debe a que comparaba contra outliers IQR (3-10%),
mientras `describe_numeric` usa z-score (umbral 3.0, da menos outliers); pero el mecanismo del bug
es el doble ×100, no la fórmula.

## Tareas

1. **H1 (fix de 1 línea):** en `python/functions/datascience/render_eda_markdown.py:203-204`,
   sustituir `_fmt_pct(val)` por un formateo que NO multiplique (p.ej. `f"{_fmt_num(val, 2)}%"`),
   porque `numeric.outlier_pct` ya viene en escala 0-100. **No tocar** `describe_numeric.py` (su
   fórmula es correcta).
2. Auditar el resto de `render_eda_markdown.py` por si otro campo en escala 0-100 pasa por
   `_fmt_pct` (los `*_pct` del perfil base sí son fracciones 0-1 y deben seguir con `_fmt_pct`;
   solo `numeric.outlier_pct` está en escala 0-100). Documentar en el docstring de `describe_numeric`
   que `outlier_pct` está en 0-100 para evitar la confusión a futuro.
3. **H11:** en `python/functions/datascience/detect_distribution_type.py`, no etiquetar por skew
   solamente: usar también nº de modos / cardinalidad y, cuando esté disponible, el test de
   normalidad Jarque-Bera (`normality_tests.py`, ya expuesto en `models.normality` vía
   `run_eda_models`). Una variable discreta/ordinal/multimodal no debe salir "normal-ish".
4. Añadir/extender tests unitarios: `describe_numeric_test.py` (outlier_pct en [0,100]),
   `render_eda_markdown_test.py` (un perfil con `outlier_pct=7.0` renderiza `"7.00%"`, no `"700%"`),
   y un test de `detect_distribution_type` (discreta de 6 valores no se etiqueta "normal-ish"). Nota:
   hoy NO existe `detect_distribution_type_test.py` en `python/functions/datascience/` — hay que
   crearlo (a confirmar el nombre canónico al implementar; el resto de tests citados sí existen).

## Definition of Done

| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: outlier_pct en rango | e2e | re-correr `profile_table` sobre `temp/eda_benchmark/datasets/.../wine-red` y leer el `.md` | `chlorides`/`density` muestran `outlier_pct` en [0,100]% (no 193.87% / 112.57%) |
| Edge: skew alto real | unit | `describe_numeric_test.py` con datos de cola fuerte | `outlier_pct` ≤ 100 y coherente con n_outliers/n |
| Edge: discreta ordinal | unit | `detect_distribution_type_test.py` con 6 valores discretos | NO etiqueta "normal-ish" |
| Error: input vacío/no numérico | unit | `describe_numeric([])` | claves None, sin crash (contrato actual preservado) |
| Mecánica | — | `./fn run describe_numeric_py_datascience`, `./fn run render_eda_markdown_py_datascience` | tests verdes; `fn index` limpio |

Re-correr el benchmark sobre wine-red y titanic y confirmar que ningún `outlier_pct` supera 100%.

## Notas

Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md` (consolidación del benchmark). H1 es el fix de
mayor ratio impacto/esfuerzo del lote (una línea elimina los números imposibles que más minan la
confianza del report). Hermanos: 0174 (series), 0175 (relational), 0176 (render), 0177 (tipos).


## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: caf8c25d. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
