Files
fn_registry/docs/capabilities/eda.md
T
Egutierrez 7ac69ab4fb feat(eda): series temporales + rigor anti-data-mining + PDF movil + /eda + benchmark issues
Bloque del grupo eda (sesion ausente EDA-benchmark):
- 8 funciones nuevas: adf_kpss_stationarity, acf_pacf, stl_decompose, to_returns,
  fdr_correction, suggest_reexpression, exploratory_caveats, render_eda_pdf
- integracion: profile_table (run_series, emit_pdf), association_matrix (FDR Benjamini-Hochberg),
  render_eda_markdown (secciones series/reexpresion/caveats)
- slash commands /eda y /capitulos
- issues 0173-0177: mejoras del /eda derivadas del benchmark sobre 12 datasets reales
  (outlier_pct x100, periodo estacional, FK inference, render models, tipos id-like)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 03:34:01 +02:00

170 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# eda — Exploratory Data Analysis por tabla y base
Grupo de capacidad para perfilar tablas y bases de datos completas y entender datasets nuevos rápido, repetible y sin reinventar lógica. Motor **DuckDB SQL push-down**: los agregados (`SUMMARIZE`, `COUNT DISTINCT`, `corr()`, percentiles) se calculan en SQL sin traer las filas a RAM; solo una muestra pequeña baja a Python para lo estadístico fino (skew, kurtosis, histograma, correlación mixta, modelos).
Orquestadores one-shot:
- `profile_table_py_pipelines` — "hazme un EDA de esta tabla" → `TableProfile` completo + report markdown + JSON (+ PDF móvil con `emit_pdf`). Flags `run_models` (modelos baratos), `run_llm` (interpretación LLM), `run_series` (análisis de serie temporal por columna numérica) y `emit_pdf` (PDF vertical legible en móvil). Re-expresión sugerida por columna y avisos exploratorios se añaden siempre.
- `profile_database_py_pipelines` — "hazme un EDA de esta base" → perfila todas las tablas + infiere FK + join graph (mermaid).
> Cuando Enmanuel pide un EDA, el flujo acordado es: perfilar con este grupo, escribir el report, y **generar un analysis Jupyter lanzado en el navegador colaborativo y ejecutado por Claude** para verlo en vivo. Ver la memoria `eda-workflow-registry` y la regla `notebook_collaboration.md`.
## Funciones
### Perfilado base (tabla y columna)
| ID | Pureza | Qué hace |
|---|---|---|
| `summarize_table_duckdb_py_datascience` | impure | Corazón (DuckDB): `SUMMARIZE` push-down + `COUNT DISTINCT` exacto (≤200k filas) → esqueleto del `TableProfile`. |
| `summarize_table_pg_py_datascience` | impure | Adaptador PostgreSQL: mismo esqueleto `TableProfile` vía SQL push-down (information_schema + count/distinct/min/max/avg/stddev/percentile_cont). |
| `describe_numeric_py_datascience` | pure | Bloque numérico: min/max/mean/median/std/cv, p1-p99, IQR, skew, kurtosis, outliers, distribución, histograma. |
| `summarize_categorical_py_datascience` | pure | top-k frecuencias, mode, distinct, entropía, imbalance, longitudes. |
| `infer_semantic_type_py_datascience` | pure | Tipo semántico por regex (email/url/ip/uuid/iban/currency/datetime/...). |
| `column_quality_score_py_datascience` | pure | Score 0-100 (completeness/validity/consistency) + issues. |
| `render_eda_markdown_py_datascience` | pure | `TableProfile` → report markdown con sparklines ASCII. |
| `summary_stats_py_datascience` | pure | Descriptiva mínima (n, mean, median, p25, p75). |
### Correlación / asociación
| ID | Pureza | Qué hace |
|---|---|---|
| `pearson_py_datascience` | pure | Correlación lineal num↔num (preexistente). |
| `spearman_corr_py_datascience` | pure | Correlación de rangos (monotónica no lineal) num↔num. |
| `cramers_v_py_datascience` | pure | Asociación simétrica cat↔cat (corrección Bergsma-Wicher). |
| `theils_u_py_datascience` | pure | Asociación direccional U(a\|b) cat↔cat. |
| `correlation_ratio_py_datascience` | pure | η: cuánto explica una categórica a una numérica. |
| `mutual_info_columns_py_datascience` | pure | Información mutua (no lineal, general) entre cualquier par. |
| `association_matrix_py_datascience` | pure | Matriz unificada: elige métrica por par de tipos + pares fuertes. |
| `correlation_matrix_duckdb_py_datascience` | impure | Matriz Pearson push-down (`corr()` SQL) para muchas filas. |
### Relaciones inter-tabla
| ID | Pureza | Qué hace |
|---|---|---|
| `infer_fk_containment_duckdb_py_datascience` | impure | Infiere FK candidatas por containment de valores (inclusion coefficient). |
| `build_join_graph_py_datascience` | pure | FK candidates → grafo (roles fact/dimension) + diagrama Mermaid. |
### Modelos baratos (flag `run_models`)
| ID | Pureza | Qué hace |
|---|---|---|
| `pca_explained_py_datascience` | pure | PCA: varianza explicada + loadings + proyección. |
| `kmeans_segments_py_datascience` | pure | Segmentos naturales, auto-k por silhouette. |
| `isolation_forest_outliers_py_datascience` | pure | Outliers multivariante (filas anómalas). |
| `normality_tests_py_datascience` | pure | Jarque-Bera + D'Agostino + Shapiro → ¿normal? |
| `trend_slope_py_datascience` | pure | Tendencia de una serie (up/down/flat) por regresión lineal. |
| `run_eda_models_py_datascience` | pure | Wrapper: compone PCA + KMeans + IsolationForest + normalidad → bloque `models`. |
### Series temporales (flag `run_series`)
| ID | Pureza | Qué hace |
|---|---|---|
| `adf_kpss_stationarity_py_datascience` | pure | Estacionariedad por consenso ADF + KPSS (hipótesis nulas opuestas) → veredicto `stationary`/`non_stationary`/`inconclusive` + aviso de correlación espuria. |
| `acf_pacf_py_datascience` | pure | ACF + PACF con bandas de confianza + lags significativos + Ljung-Box (¿ruido blanco?). Detecta autocorrelación que infla los p-valores OLS. |
| `stl_decompose_py_datascience` | pure | Descomposición STL (tendencia/estacional/resto) + fuerza de tendencia y estacional de Hyndman. Auto-infiere el periodo por autocorrelación. |
| `to_returns_py_datascience` | pure | Convierte una serie de niveles (precios) a retornos log/simples. Los niveles no son estacionarios; los retornos sí (unidad correcta para correlacionar/modelar). |
### Rigor y disciplina exploratoria
| ID | Pureza | Qué hace |
|---|---|---|
| `fdr_correction_py_datascience` | pure | Corrige p-valores por comparaciones múltiples (Benjamini-Hochberg FDR / Bonferroni FWER) → controla el data-mining bias. Ya integrada en `association_matrix`. |
| `suggest_reexpression_py_datascience` | pure | Escalera de potencias de Tukey: qué transformación (log/sqrt/Yeo-Johnson/...) simetriza mejor una columna numérica según su skew y dominio. No la ejecuta, la sugiere. |
| `exploratory_caveats_py_datascience` | pure | Genera las advertencias de que el EDA es exploratorio (correlación≠causalidad, overfitting in-sample, comparaciones múltiples, outliers, muestra pequeña, MNAR) según lo que el perfil realmente contiene. |
### Capa LLM y entrega
| ID | Pureza | Qué hace |
|---|---|---|
| `eda_llm_insights_py_datascience` | impure | 1 call LLM sobre el perfil agregado (no filas crudas): data dictionary, resumen, granularidad de fila, PII/RGPD, limpieza, análisis sugeridos. |
| `build_eda_notebook_py_datascience` | impure | Genera un `.ipynb` (nbformat v4) que perfila la tabla, listo para lanzar en Jupyter colaborativo. |
| `render_eda_pdf_py_datascience` | impure | Renderiza el `TableProfile` a un PDF multipágina **vertical (A5), legible en móvil** (estilo Tufte: histogramas como small multiples, top-k, heatmap de asociación). 4ª salida del workflow, junto a JSON/Markdown/notebook. |
### Orquestadores (pipelines)
| ID | Qué hace |
|---|---|
| `profile_table_py_pipelines` | EDA de una tabla end-to-end, `backend="duckdb"` (default) o `"postgres"` (base + correlación con FDR + `run_models` + `run_llm` + `run_series` + re-expresión + caveats) → JSON + markdown (+ PDF móvil con `emit_pdf`). |
| `profile_database_py_pipelines` | EDA de una base entera: todas las tablas + FK + join graph. |
## Contrato de datos
```
TableProfile = {table, source, profiled_at, n_rows, n_cols, size_bytes,
duplicate_rows, duplicate_pct, constant_cols, all_null_cols, null_cell_pct,
type_breakdown:{numeric,categorical,datetime,text,boolean},
columns:[ColumnProfile], correlations, key_candidates, quality_score, llm, models,
series:{<col>:SeriesBlock}|None, # solo con run_series
caveats:{n, caveats:[{id,topic,message,reference}], note}} # siempre
ColumnProfile = {name, physical_type, inferred_type, semantic_type, count, n_rows,
null_count, null_pct, empty_count, empty_pct, distinct_count, unique_pct,
flags:[constant|possible_id|high_cardinality|mostly_null], quality_score,
numeric:{...}|None, categorical:{...}|None, datetime:{...}|None,
reexpression:{recommended,ladder_power,reason,alternatives,skew}|None, # cols numéricas
series:SeriesBlock|None} # solo con run_series
# *_pct son FRACCIONES 0-1; el render las muestra ×100
SeriesBlock = {order_col, ordered, n, stationarity:{adf,kpss,verdict,warning},
acf_pacf:{acf,pacf,significant_acf_lags,ljung_box,is_autocorrelated},
stl:{period,trend_strength,seasonal_strength,...},
to_returns:{...}|absent, levels_suggested:bool}
correlations = {pairs:[{a,b,a_type,b_type,method,value,extra,p_value,
p_value_adjusted,significant}], strong:[...], methods_legend,
multiple_testing:{method,alpha,n_tests,n_rejected}} # p-valores corregidos por FDR
models = {n_numeric_cols, pca, kmeans, outliers, normality, note}
llm = {summary, row_meaning, dictionary:[{column,description,business_meaning,unit}],
pii:[{column,kind,severity}], cleaning:[str], analyses:[str]}
```
## Ejemplo canónico
EDA completo de una tabla (estadística + correlación + modelos + LLM + report):
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from pipelines.profile_table import profile_table
r = profile_table(
"/ruta/datos.duckdb", "clientes",
run_models=True, run_llm=True, run_series=True, emit_pdf=True,
)
prof = r["profile"]
print(r["report_md_path"]) # reports/eda_clientes_<ts>.md
print(r["pdf_path"]) # reports/eda_clientes_<ts>.pdf (móvil)
print(prof["correlations"]["strong"]) # pares fuertes Y significativos tras FDR
print(prof["models"]["kmeans"]["best_k"]) # segmentos
print(prof["series"]["precio"]["stationarity"]["verdict"]) # ¿serie estacionaria?
print(prof["columns"][0]["reexpression"]["recommended"]) # transformación sugerida
print(prof["caveats"]["caveats"][0]["message"]) # aviso exploratorio general
print(prof["llm"]["row_meaning"]) # qué representa 1 fila
```
EDA de una base entera con relaciones:
```python
from pipelines.profile_database import profile_database
r = profile_database("/ruta/datos.duckdb") # todas las tablas
print(r["db_profile"]["join_graph"]["mermaid"]) # diagrama de relaciones FK
```
Notebook ejecutable:
```python
from datascience import build_eda_notebook
build_eda_notebook("/ruta/datos.duckdb", "clientes", "/tmp/eda.ipynb", run_models=True)
```
## Fronteras
- **NO carga la tabla entera a RAM**: metadata SQL + muestra por columna/filas (`sample`, default 5000).
- **Distinct exacto hasta 200k filas**; por encima aproximado capado.
- **Correlación de tabla** se calcula sobre la muestra de filas alineadas; excluye columnas id-like (alta cardinalidad) para evitar asociación espuria. `correlation_matrix_duckdb` ofrece Pearson push-down exacto a escala si hace falta.
- **Modelos** (`run_models`) requieren ≥2 columnas numéricas para PCA/KMeans/IsolationForest; normalidad funciona con 1.
- **LLM** (`run_llm`) hace 1 llamada (haiku) y envía solo el perfil agregado, nunca filas crudas; requiere token OAuth de Claude.
- **Series** (`run_series`) trata cada columna numérica como serie temporal: si hay una columna datetime se ordena por ella, si no por el orden físico de filas. Necesita ≥8 puntos válidos por columna; STL exige ≥2 periodos. La sugerencia de retornos (`to_returns`) solo aparece en columnas estrictamente positivas y no claramente estacionarias (series de niveles/precios).
- **PDF** (`emit_pdf`) genera un PDF A5 vertical legible en móvil junto al report markdown vía `render_eda_pdf` (matplotlib `PdfPages`, sin dependencias nuevas).
- **Correlaciones**: los p-valores de cada par se corrigen por comparaciones múltiples (FDR Benjamini-Hochberg) dentro de `association_matrix`; un par solo entra en `strong` si supera el umbral de magnitud Y es significativo tras la corrección.
- **Fuentes**: DuckDB nativo (CSV/Parquet/Excel cargándolos antes a DuckDB) y **PostgreSQL** (`backend="postgres"`, DSN vía `resolve_pg_dsn`). BigQuery pendiente. `profile_database` (multi-tabla + FK) es solo DuckDB por ahora.
## Estado
Implementado y validado end-to-end (152 tests verdes): perfilado base, correlación/asociación (Pearson/Spearman/Cramér's V/Theil's U/η/MI), relaciones inter-tabla (FK + join graph), modelos baratos (PCA/KMeans/IsolationForest/normalidad/tendencia), capa LLM y generación de notebook.
Validado sobre PostgreSQL real (tablas del Metabase local del proyecto captacion_clientes).
Pendiente: adaptador BigQuery; `profile_database` multi-tabla para PostgreSQL (hoy solo DuckDB); perfil fino de columnas datetime (`profile_datetime`); excluir columnas numéricas `possible_id` de la matriz de asociación (hoy solo se excluyen las categóricas id-like).