Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +30 -25
src/streamlit_app.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
# Objetivo: permitir escolher ALVO e PREDITORES e produzir INFERÊNCIA (item e),
|
| 6 |
# usando Logit (alvo binário) ou OLS (alvo contínuo).
|
| 7 |
# Se o alvo for categórico com >2 classes, permite one-vs-rest.
|
|
|
|
| 8 |
# -------------------------------------------------------------------
|
| 9 |
import os
|
| 10 |
import numpy as np
|
|
@@ -58,13 +59,11 @@ def coerce_numeric_series(s: pd.Series) -> pd.Series:
|
|
| 58 |
"""Tenta converter strings numéricas para float (lida com vírgula decimal)."""
|
| 59 |
if np.issubdtype(s.dtype, np.number):
|
| 60 |
return s.astype(float)
|
| 61 |
-
# troca vírgula decimal por ponto, remove separadores comuns
|
| 62 |
tmp = s.astype(str).str.replace(r"[.\s]", "", regex=True).str.replace(",", ".", regex=False)
|
| 63 |
-
|
| 64 |
-
return coerced
|
| 65 |
|
| 66 |
def engineer_features(df: pd.DataFrame) -> pd.DataFrame:
|
| 67 |
-
"""Engenharia minimalista
|
| 68 |
out = df.copy()
|
| 69 |
|
| 70 |
# Tenure (dias desde Dt_Customer)
|
|
@@ -191,27 +190,39 @@ df_eng = engineer_features(df_raw)
|
|
| 191 |
with st.sidebar:
|
| 192 |
st.markdown("**Alvo (variável dependente):**")
|
| 193 |
all_cols = df_eng.columns.tolist()
|
| 194 |
-
#
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
default_target = c; break
|
| 200 |
-
if default_target is None:
|
| 201 |
for c in all_cols:
|
| 202 |
-
if
|
| 203 |
-
|
|
|
|
|
|
|
| 204 |
target_col = st.selectbox("Alvo (y)", options=all_cols, index=all_cols.index(default_target) if default_target in all_cols else 0)
|
| 205 |
|
| 206 |
# Variáveis explicativas
|
| 207 |
exclude = [target_col]
|
| 208 |
num_cols_all, cat_cols_all = split_num_cat(df_eng, exclude=exclude)
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
with st.sidebar:
|
| 211 |
st.markdown("**Variáveis explicativas (X):**")
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
test_size = st.slider("Proporção de teste", 0.1, 0.4, 0.2, 0.05)
|
| 217 |
random_state = st.number_input("Random seed", value=42, step=1)
|
|
@@ -233,11 +244,9 @@ is_bin = is_binary_series(y_raw)
|
|
| 233 |
# 2) se não binário, tenta numérico (coerção segura)
|
| 234 |
y_numeric_try = coerce_numeric_series(y_raw) if not is_bin else None
|
| 235 |
is_numeric_ok = False
|
| 236 |
-
if not is_bin:
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
conv_rate = y_numeric_try.notna().mean()
|
| 240 |
-
is_numeric_ok = conv_rate >= 0.8
|
| 241 |
|
| 242 |
# 3) se não binário e não numérico, vira categórico multi-classe → one-vs-rest
|
| 243 |
with st.sidebar:
|
|
@@ -250,20 +259,16 @@ with st.sidebar:
|
|
| 250 |
|
| 251 |
# ---------- Montagem de y conforme os casos ----------
|
| 252 |
if is_bin:
|
| 253 |
-
# Se binário não-numérico → mapear para {0,1} em ordem alfabética
|
| 254 |
if not np.issubdtype(y_raw.dtype, np.number):
|
| 255 |
uniq = sorted(pd.unique(y_raw.dropna()).tolist(), key=lambda x: str(x))
|
| 256 |
y = y_raw.replace({uniq[0]: 0, uniq[1]: 1}).astype(int)
|
| 257 |
else:
|
| 258 |
y = y_raw.astype(int)
|
| 259 |
model_type = "logit"
|
| 260 |
-
|
| 261 |
elif is_numeric_ok:
|
| 262 |
y = y_numeric_try.astype(float)
|
| 263 |
model_type = "ols"
|
| 264 |
-
|
| 265 |
else:
|
| 266 |
-
# one-vs-rest
|
| 267 |
y = (y_raw == positive_class).astype(int)
|
| 268 |
model_type = "logit"
|
| 269 |
|
|
|
|
| 5 |
# Objetivo: permitir escolher ALVO e PREDITORES e produzir INFERÊNCIA (item e),
|
| 6 |
# usando Logit (alvo binário) ou OLS (alvo contínuo).
|
| 7 |
# Se o alvo for categórico com >2 classes, permite one-vs-rest.
|
| 8 |
+
# Alvo padrão: Response. X padrão alinhado ao Colab.
|
| 9 |
# -------------------------------------------------------------------
|
| 10 |
import os
|
| 11 |
import numpy as np
|
|
|
|
| 59 |
"""Tenta converter strings numéricas para float (lida com vírgula decimal)."""
|
| 60 |
if np.issubdtype(s.dtype, np.number):
|
| 61 |
return s.astype(float)
|
|
|
|
| 62 |
tmp = s.astype(str).str.replace(r"[.\s]", "", regex=True).str.replace(",", ".", regex=False)
|
| 63 |
+
return pd.to_numeric(tmp, errors="coerce")
|
|
|
|
| 64 |
|
| 65 |
def engineer_features(df: pd.DataFrame) -> pd.DataFrame:
|
| 66 |
+
"""Engenharia minimalista para o dataset padrão do Kaggle."""
|
| 67 |
out = df.copy()
|
| 68 |
|
| 69 |
# Tenure (dias desde Dt_Customer)
|
|
|
|
| 190 |
with st.sidebar:
|
| 191 |
st.markdown("**Alvo (variável dependente):**")
|
| 192 |
all_cols = df_eng.columns.tolist()
|
| 193 |
+
# Alvo padrão fixo: Response (se existir). Caso contrário, mesma lógica de fallback.
|
| 194 |
+
if "Response" in all_cols:
|
| 195 |
+
default_target = "Response"
|
| 196 |
+
else:
|
| 197 |
+
default_target = None
|
|
|
|
|
|
|
| 198 |
for c in all_cols:
|
| 199 |
+
if is_binary_series(df_eng[c]): default_target = c; break
|
| 200 |
+
if default_target is None:
|
| 201 |
+
for c in all_cols:
|
| 202 |
+
if np.issubdtype(df_eng[c].dtype, np.number): default_target = c; break
|
| 203 |
target_col = st.selectbox("Alvo (y)", options=all_cols, index=all_cols.index(default_target) if default_target in all_cols else 0)
|
| 204 |
|
| 205 |
# Variáveis explicativas
|
| 206 |
exclude = [target_col]
|
| 207 |
num_cols_all, cat_cols_all = split_num_cat(df_eng, exclude=exclude)
|
| 208 |
|
| 209 |
+
# X padrão alinhado ao Colab (só incluir se existir na base)
|
| 210 |
+
preferred_defaults = [
|
| 211 |
+
"Income", "Recency", "Education", "Marital_Status",
|
| 212 |
+
"TenureDays", "TotalMnt", "TotalPurchases",
|
| 213 |
+
"OnlineShare", "PromoShare", "AvgTicket", "BasketDiversity",
|
| 214 |
+
"NumWebVisitsMonth"
|
| 215 |
+
]
|
| 216 |
+
default_X = [c for c in preferred_defaults if c in (num_cols_all + cat_cols_all)]
|
| 217 |
+
|
| 218 |
with st.sidebar:
|
| 219 |
st.markdown("**Variáveis explicativas (X):**")
|
| 220 |
+
# Se nada dos preferidos existir, cai no fallback antigo (algumas num + categ)
|
| 221 |
+
if not default_X:
|
| 222 |
+
engineered_first = [c for c in ["TenureDays","TotalMnt","TotalPurchases","OnlineShare","PromoShare","AvgTicket","BasketDiversity"] if c in num_cols_all]
|
| 223 |
+
default_X = engineered_first + [c for c in num_cols_all if c not in engineered_first][:5] + cat_cols_all[:3]
|
| 224 |
+
|
| 225 |
+
selected_feats = st.multiselect("Selecione X", options=(num_cols_all + cat_cols_all), default=default_X)
|
| 226 |
|
| 227 |
test_size = st.slider("Proporção de teste", 0.1, 0.4, 0.2, 0.05)
|
| 228 |
random_state = st.number_input("Random seed", value=42, step=1)
|
|
|
|
| 244 |
# 2) se não binário, tenta numérico (coerção segura)
|
| 245 |
y_numeric_try = coerce_numeric_series(y_raw) if not is_bin else None
|
| 246 |
is_numeric_ok = False
|
| 247 |
+
if not is_bin and y_numeric_try is not None:
|
| 248 |
+
conv_rate = y_numeric_try.notna().mean()
|
| 249 |
+
is_numeric_ok = conv_rate >= 0.8
|
|
|
|
|
|
|
| 250 |
|
| 251 |
# 3) se não binário e não numérico, vira categórico multi-classe → one-vs-rest
|
| 252 |
with st.sidebar:
|
|
|
|
| 259 |
|
| 260 |
# ---------- Montagem de y conforme os casos ----------
|
| 261 |
if is_bin:
|
|
|
|
| 262 |
if not np.issubdtype(y_raw.dtype, np.number):
|
| 263 |
uniq = sorted(pd.unique(y_raw.dropna()).tolist(), key=lambda x: str(x))
|
| 264 |
y = y_raw.replace({uniq[0]: 0, uniq[1]: 1}).astype(int)
|
| 265 |
else:
|
| 266 |
y = y_raw.astype(int)
|
| 267 |
model_type = "logit"
|
|
|
|
| 268 |
elif is_numeric_ok:
|
| 269 |
y = y_numeric_try.astype(float)
|
| 270 |
model_type = "ols"
|
|
|
|
| 271 |
else:
|
|
|
|
| 272 |
y = (y_raw == positive_class).astype(int)
|
| 273 |
model_type = "logit"
|
| 274 |
|