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

12 KiB
Raw Blame History

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):

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:

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:

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).