# eda — Exploratory Data Analysis por tabla Grupo de capacidad para perfilar tablas y entender datasets nuevos rápido, repetible y sin reinventar lógica. Motor **DuckDB SQL push-down**: los agregados (`SUMMARIZE`, `COUNT DISTINCT`, 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, outliers). El orquestador one-shot es `profile_table_py_pipelines`: "hazme un EDA de esta tabla" → un `TableProfile` completo + report markdown + JSON sidecar en `reports/`. > 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 | ID | Pureza | Qué hace | |---|---|---| | `summarize_table_duckdb_py_datascience` | impure | Corazón: `SUMMARIZE` push-down → esqueleto del `TableProfile` con perfil base por columna (tipo inferido, nulls, distinct exacto ≤200k filas, flags). Reusa `duckdb_query_readonly`. | | `describe_numeric_py_datascience` | pure | Bloque `numeric` sobre una muestra: min/max/mean/median/mode/std/cv, percentiles p1-p99, IQR, skew, kurtosis, outliers, %zeros/%neg, tipo de distribución, histograma. | | `summarize_categorical_py_datascience` | pure | Bloque `categorical`: top-k frecuencias, mode, distinct, entropía de Shannon (bits), imbalance, longitudes. | | `infer_semantic_type_py_datascience` | pure | Tipo semántico por regex (email/url/ip/uuid/iban/currency/datetime/integer/decimal/...) sin LLM. Primera pasada barata. | | `column_quality_score_py_datascience` | pure | Score de calidad 0-100 (completeness/validity/consistency) + issues legibles para un `ColumnProfile`. | | `render_eda_markdown_py_datascience` | pure | `TableProfile` → report markdown autosuficiente (Overview, Columnas, Numéricas con sparkline ASCII, Categóricas, Calidad). | | `summary_stats_py_datascience` | pure | Descriptiva mínima (n, mean, median, p25, p75) de una lista de floats. | | `profile_table_py_pipelines` | pipeline | Orquestador end-to-end: compone todo lo anterior, promueve tipos VARCHAR→numeric/datetime por contenido, y emite `TableProfile` + report markdown + JSON. | ## Contrato de datos Todas las funciones producen/consumen el mismo shape (dict JSON), lo que desacopla cálculo, render y (futuro) LLM: ``` TableProfile = { table, source, profiled_at, n_rows, n_cols, size_bytes, duplicate_rows, duplicate_pct, constant_cols:[str], all_null_cols:[str], null_cell_pct, type_breakdown:{numeric,categorical,datetime,text,boolean}, columns:[ColumnProfile], correlations, key_candidates:[str], quality_score, llm, models } ColumnProfile = { name, physical_type, inferred_type, # numeric|categorical|datetime|boolean|text|id semantic_type, count, n_rows, null_count, null_pct, empty_count, empty_pct, distinct_count, unique_pct, # *_pct son FRACCIONES 0-1; el render las muestra ×100 flags:[constant|possible_id|high_cardinality|mostly_null], quality_score, numeric: {min,max,mean,median,mode,std,variance,cv,p1,p5,p25,p50,p75,p95,p99,iqr, skew,kurtosis,n_outliers,outlier_pct,zero_pct,negative_pct,distribution_type, histogram:[{lo,hi,count}]} | None, categorical: {top:[{value,count,pct}],mode,mode_pct,n_distinct,entropy,imbalance, len_mean,len_min,len_max} | None, datetime: {min,max,range_days,granularity,n_gaps,future_pct,monotonic} | None } ``` ## Ejemplo canónico EDA de una tabla DuckDB en una línea (escribe `reports/eda__.md` + `.json`): ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from pipelines.profile_table import profile_table r = profile_table(os.path.expanduser("~/.fn_freelance/freelance.duckdb"), "freelance_projects") print(r["status"], r["report_md_path"]) prof = r["profile"] print(prof["type_breakdown"], "key_candidates:", prof["key_candidates"], "calidad:", prof["quality_score"]) ``` La promoción de tipo por contenido resuelve el caso típico de scrapers/CSV donde los números y fechas llegan como `VARCHAR`: `bids` ('10','20') se detecta `integer` y se perfila como numérica (mean/median/percentiles); `scraped_at` se detecta `datetime_iso`. ## Fronteras - **NO carga la tabla entera a RAM**: solo metadata SQL + una muestra (`sample`, default 5000) por columna. Para distribución exacta de una columna enorme, sube `sample` o consulta SQL directa. - **Distinct exacto solo hasta 200k filas**; por encima usa aproximado (HyperLogLog) capado a nº de filas. - **Solo DuckDB** por ahora (CSV/Parquet/Excel entran gratis vía `read_csv_auto`/`read_parquet`/`read_xlsx` cargándolos antes a DuckDB). PostgreSQL y BigQuery requieren adaptador (pendiente). - **No es estadística inferencial ni modelado**: es perfilado descriptivo. Correlaciones, modelos baratos (PCA/KMeans/IsolationForest) y capa LLM son fases siguientes del grupo. ## Roadmap (fases siguientes) - **Correlación / asociación**: Spearman, Cramér's V, Theil's U, correlation ratio η², Mutual Information, VIF → `correlations` del `TableProfile`. - **Relaciones inter-tabla**: FK inference por containment, cardinalidad de relación, join graph (mermaid), star-schema hints → `profile_database`. - **Modelos baratos** (flag `--models`, sklearn/scipy): PCA 2D, KMeans + silhouette, Isolation Forest, feature importance, tests de normalidad, tendencia temporal. - **Capa LLM** (flag `--llm`, grupo `claude-direct`): data dictionary, resumen ejecutivo (qué es 1 fila + granularidad), flag PII/RGPD, limpieza sugerida, análisis sugeridos. - **Entrega notebook**: analysis Jupyter auto-generado y ejecutado en el navegador colaborativo.