File size: 3,404 Bytes
d7f258e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""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)