File size: 5,849 Bytes
e820479
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import io
import time
import pandas as pd
import numpy as np
import streamlit as st
try:
    from sdv.evaluation.single_table import evaluate
except Exception:
    evaluate = None

st.set_page_config(page_title="Data Synthesizer Studio (Upload Only)", layout="wide")

st.title("🧪 Data Synthesizer Studio — Upload Only (CPU)")
st.write("""
**Genera datos sintéticos** a partir de tu CSV.  
Esta versión **no carga archivos de ejemplo**: debes **subir un archivo** para continuar.
""")

with st.expander("ℹ️ Instrucciones"):
    st.markdown("""
    1. Sube un **CSV** pequeño (≤ 2–3 MB) con columnas numéricas y/o categóricas.  
    2. Elige el **modelo** (CTGAN/TVAE/ GaussianCopula).  
    3. Ajusta parámetros ligeros y presiona **Entrenar**.  
    4. Compara distribuciones y **descarga** el CSV sintético.
    """)

# =============== Helpers ===============
def get_model(model_name: str, epochs: int, batch_size: int):
    """
    Importación perezosa para evitar requerir torch si se usa GaussianCopula.
    """
    name = model_name.lower()
    if name == "ctgan":
        from sdv.tabular import CTGAN  # requiere torch
        return CTGAN(epochs=epochs, batch_size=batch_size, verbose=True)
    elif name == "tvae":
        from sdv.tabular import TVAE   # requiere torch
        return TVAE(epochs=epochs, batch_size=batch_size, verbose=True)
    else:
        from sdv.tabular import GaussianCopula  # no requiere torch
        return GaussianCopula()

# =============== Layout ===============
left, right = st.columns([1, 1])

with left:
    st.subheader("📥 1) Sube tu CSV")
    up = st.file_uploader("Selecciona un archivo .csv", type=["csv"], key="csv_uploader")

    df_real = None  # se mantiene None hasta que haya archivo
    if up is not None:
        file_bytes = up.getvalue()
        try:
            # Autodetección de separador
            df_real = pd.read_csv(io.BytesIO(file_bytes), sep=None, engine="python")
        except Exception:
            # Fallbacks comunes
            try:
                df_real = pd.read_csv(io.BytesIO(file_bytes), sep=";")
            except Exception:
                df_real = pd.read_csv(io.BytesIO(file_bytes), sep=",")

        st.success(f"✅ Cargado: {df_real.shape[0]} filas × {df_real.shape[1]} columnas")
        st.dataframe(df_real.head())

        # Limpieza mínima
        df_real = df_real.dropna(axis=1, how="all")
        if df_real.shape[1] > 25:
            df_real = df_real.iloc[:, :25]
            st.info("Se detectaron muchas columnas. Para este demo se usan solo las primeras 25.")
    else:
        st.info("Sube un CSV para continuar.")

with right:
    st.subheader("⚙️ 2) Modelo y parámetros")
    model_name = st.selectbox("Modelo", ["CTGAN", "TVAE", "GaussianCopula"])
    epochs = st.slider("Epochs (demo CPU)", 1, 25, 10, 1)
    batch_size = st.select_slider("Batch size", options=[32, 64, 128, 256], value=64)
    sample_size = st.slider("Filas sintéticas a generar", 100, 5000, 1000, 100)
    st.caption("💡 Sugerencia: mantén `epochs` bajos en CPU para evitar timeouts.")

# =============== Train ===============
st.subheader("🚀 3) Entrenamiento y generación")

train_btn = st.button("Entrenar modelo y generar datos sintéticos")

if train_btn:
    # Guardrails
    df_train = df_real.copy()
    if df_train.shape[1] >= 40 and sample_size > 2000:
        sample_size = 2000
        st.warning("Muchas columnas detectadas; se limita sample_size a 2000.")

    # Construir modelo
    try:
        model = get_model(model_name, epochs=epochs, batch_size=batch_size)
    except Exception as e:
        st.error("No se pudo inicializar el modelo. Posible falta de dependencias (torch para CTGAN/TVAE).")
        st.code(str(e))
        st.stop()

    with st.spinner("Entrenando modelo…"):
        t0 = time.time()
        model.fit(df_train)
        dt = time.time() - t0
    st.success(f"✅ Entrenamiento completado en {dt:.1f} s")

    with st.spinner("Generando datos sintéticos…"):
        df_syn = model.sample(sample_size)

    st.write("### 📤 Muestra de datos sintéticos")
    st.dataframe(df_syn.head())

    st.write("### 📊 Evaluación de calidad (SDV evaluate)")
    if evaluate is None:
        st.info("`evaluate` no disponible en esta versión de SDV; omitiendo evaluación.")
    else:
        try:
            score = evaluate(df_syn, df_train)
            st.metric("Puntaje de similitud", f"{score:.3f}", help="0–1 (1 = distribuciones idénticas)")
        except Exception as e:
            st.warning(f"No se pudo calcular `evaluate`: {e}")

    st.write("### 🔎 Comparación univariante (hasta 6 columnas)")
    cols = df_train.columns[:6]
    tabs = st.tabs([str(c) for c in cols])
    for i, c in enumerate(cols):
        with tabs[i]:
            c1, c2 = st.columns(2)
            with c1:
                st.write("Real")
                if pd.api.types.is_numeric_dtype(df_train[c]):
                    st.bar_chart(df_train[c].dropna().value_counts(bins=20))
                else:
                    st.bar_chart(df_train[c].astype(str).value_counts().head(30))
            with c2:
                st.write("Sintético")
                if c in df_syn.columns:
                    if pd.api.types.is_numeric_dtype(df_syn[c]):
                        st.bar_chart(df_syn[c].dropna().value_counts(bins=20))
                    else:
                        st.bar_chart(df_syn[c].astype(str).value_counts().head(30))
                else:
                    st.info("Columna no generada.")

    st.write("### 💾 Descargar CSV sintético")
    buf = io.StringIO()
    df_syn.to_csv(buf, index=False)
    st.download_button(
        "Descargar synthetic.csv",
        data=buf.getvalue().encode("utf-8"),
        file_name="synthetic.csv",
        mime="text/csv"
    )