| """Exportación del análisis completo a un informe Markdown descargable.""" |
|
|
| from __future__ import annotations |
|
|
| from datetime import date |
|
|
| import pandas as pd |
|
|
| from . import profiler |
|
|
|
|
| def _table_md(df: pd.DataFrame) -> str: |
| if df.empty: |
| return "*(sin datos)*\n" |
| return df.to_markdown(index=False) + "\n" |
|
|
|
|
| def build_report( |
| df: pd.DataFrame, |
| dataset_name: str, |
| alerts: list[dict], |
| target: str | None = None, |
| insights: str | None = None, |
| ) -> str: |
| ov = profiler.overview(df) |
|
|
| parts = [ |
| f"# Informe exploratorio — {dataset_name}", |
| f"*Generado el {date.today().isoformat()} con EDA Express*", |
| "", |
| "## Visión general", |
| f"- **Filas:** {ov['filas']:,} | **Columnas:** {ov['columnas']} " |
| f"({ov['numericas']} numéricas, {ov['categoricas']} categóricas)", |
| f"- **Memoria:** {ov['memoria_mb']} MB", |
| f"- **Faltantes:** {ov['faltantes_total']:,} celdas ({ov['faltantes_pct']}%)", |
| f"- **Filas duplicadas:** {ov['filas_duplicadas']:,} ({ov['duplicadas_pct']}%)", |
| "", |
| "## Alertas de calidad", |
| ] |
|
|
| if alerts: |
| for a in alerts: |
| icon = {"critico": "[CRITICO]", "aviso": "[AVISO]", "info": "[INFO]"}[a["nivel"]] |
| parts.append(f"- {icon} **{a['columna']}** — {a['mensaje']}. {a['recomendacion']}") |
| else: |
| parts.append("- Sin alertas: el dataset pasa todas las comprobaciones.") |
|
|
| parts += ["", "## Columnas", _table_md(profiler.column_table(df))] |
|
|
| missing = profiler.missing_table(df) |
| if not missing.empty: |
| parts += ["## Valores faltantes", _table_md(missing)] |
|
|
| numeric = profiler.numeric_table(df) |
| if not numeric.empty: |
| parts += ["## Variables numéricas", _table_md(numeric)] |
|
|
| categorical = profiler.categorical_table(df) |
| if not categorical.empty: |
| parts += ["## Variables categóricas", _table_md(categorical)] |
|
|
| top_corr = profiler.top_correlations(profiler.correlation_matrix(df)) |
| if not top_corr.empty: |
| parts += ["## Correlaciones más fuertes", _table_md(top_corr)] |
|
|
| pca = profiler.pca_summary(df) |
| if pca: |
| variance = pca["varianza_explicada"] |
| parts += [ |
| "## PCA", |
| f"Las dos primeras componentes explican el {100 * (variance[0] + variance[1]):.0f}% " |
| f"de la varianza (PC1: {variance[0]:.0%}, PC2: {variance[1]:.0%}).", |
| "", |
| ] |
|
|
| if target: |
| parts += [f"## Análisis respecto a '{target}'"] |
| if profiler.target_kind(df, target) == "categorico": |
| parts += ["### Balance de clases", _table_md(profiler.class_balance(df, target))] |
| corr_target = profiler.correlations_with_target(df, target) |
| if not corr_target.empty: |
| parts += ["### Correlación con el objetivo", _table_md(corr_target)] |
| if profiler.target_kind(df, target) == "categorico": |
| eta = profiler.eta_squared_table(df, target) |
| if not eta.empty: |
| parts += ["### Eta cuadrado (varianza explicada por las clases)", _table_md(eta)] |
|
|
| if insights: |
| parts += ["## Conclusiones (generadas con LLM)", insights, ""] |
|
|
| parts += [ |
| "---", |
| "*Los resultados son descriptivos: las decisiones (imputar, eliminar, " |
| "transformar) requieren conocer el contexto del dato.*", |
| ] |
| return "\n".join(parts) |
|
|