Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +18 -52
src/streamlit_app.py
CHANGED
|
@@ -35,13 +35,7 @@ def load_data_anova():
|
|
| 35 |
if df is None:
|
| 36 |
return None, None, [], []
|
| 37 |
|
| 38 |
-
|
| 39 |
-
df.columns = (
|
| 40 |
-
df.columns
|
| 41 |
-
.str.strip()
|
| 42 |
-
.str.lower()
|
| 43 |
-
.str.replace('[^0-9a-z_]+', '', regex=True)
|
| 44 |
-
)
|
| 45 |
|
| 46 |
coluna_preco_nome = None
|
| 47 |
if 'saleprice' in df.columns:
|
|
@@ -75,18 +69,11 @@ def load_data_reg():
|
|
| 75 |
try:
|
| 76 |
df = pd.read_csv(fixed_url)
|
| 77 |
url_carregada = fixed_url
|
| 78 |
-
except Exception:
|
| 79 |
return None, None, [], [], []
|
| 80 |
|
| 81 |
st.success(f"Dataset carregado com sucesso de: {url_carregada} (Shape: {df.shape})")
|
| 82 |
-
|
| 83 |
-
# Normalizar nomes de coluna: sem espaços, minúsculas, sem caracteres especiais
|
| 84 |
-
df.columns = (
|
| 85 |
-
df.columns
|
| 86 |
-
.str.strip()
|
| 87 |
-
.str.lower()
|
| 88 |
-
.str.replace('[^0-9a-z_]+', '', regex=True)
|
| 89 |
-
)
|
| 90 |
|
| 91 |
coluna_preco_nome = None
|
| 92 |
possible_price_cols = ['saleprice', 'sale_price', 'price']
|
|
@@ -139,9 +126,7 @@ def load_data_reg():
|
|
| 139 |
col not in colunas_categoricas_potenciais or col in vars_sempre_continuas_para_reg
|
| 140 |
) and col != coluna_preco_nome
|
| 141 |
]
|
| 142 |
-
colunas_continuas_potenciais = sorted(
|
| 143 |
-
list(set(col for col in colunas_continuas_potenciais if col in df.columns))
|
| 144 |
-
)
|
| 145 |
|
| 146 |
return df, coluna_preco_nome, colunas_categoricas_potenciais, colunas_continuas_potenciais, df.columns.tolist()
|
| 147 |
|
|
@@ -153,7 +138,6 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 153 |
results = {"var_cat": var_cat, "plots": {}}
|
| 154 |
df_var = df_analysis[[var_cat, col_preco]].copy()
|
| 155 |
|
| 156 |
-
# Garantir que a variável categórica seja do tipo category
|
| 157 |
if df_var[var_cat].dtype != 'object' and not pd.api.types.is_categorical_dtype(df_var[var_cat]):
|
| 158 |
df_var[var_cat] = df_var[var_cat].astype('category')
|
| 159 |
|
|
@@ -162,23 +146,19 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 162 |
results["error"] = "Dados insuficientes ou poucos níveis após limpeza."
|
| 163 |
return results
|
| 164 |
|
| 165 |
-
|
| 166 |
-
formula = f'{col_preco} ~ C(`{var_cat}`)'
|
| 167 |
-
|
| 168 |
try:
|
| 169 |
modelo = ols(formula, data=df_var).fit()
|
| 170 |
results["anova_table"] = sm.stats.anova_lm(modelo, typ=2)
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
results["p_valor_anova"] = results["anova_table"].loc[key, 'PR(>F)']
|
| 175 |
else:
|
| 176 |
results["p_valor_anova"] = results["anova_table"]['PR(>F)'].iloc[0]
|
| 177 |
|
| 178 |
residuos = modelo.resid
|
| 179 |
results["residuos_count"] = len(residuos)
|
| 180 |
|
| 181 |
-
# 1. Normalidade dos resíduos
|
| 182 |
normalidade_ok = False
|
| 183 |
if len(residuos) >= 3:
|
| 184 |
if len(residuos) <= 5000:
|
|
@@ -199,8 +179,7 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 199 |
if len(residuos) > 1:
|
| 200 |
sns.histplot(residuos, kde=True, ax=ax_norm[0], stat="density", bins=30)
|
| 201 |
ax_norm[0].set_title(f'Histograma Resíduos ({var_cat})', fontsize=10)
|
| 202 |
-
sm.qqplot(residuos, line='s', ax=ax_norm[1],
|
| 203 |
-
markerfacecolor="skyblue", markeredgecolor="dodgerblue", alpha=0.7)
|
| 204 |
ax_norm[1].set_title(f'Q-Q Plot Resíduos ({var_cat})', fontsize=10)
|
| 205 |
else:
|
| 206 |
ax_norm[0].text(0.5, 0.5, "Poucos dados", ha='center', va='center')
|
|
@@ -208,10 +187,9 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 208 |
plt.tight_layout()
|
| 209 |
results["plots"]["normalidade"] = fig_norm
|
| 210 |
|
| 211 |
-
#
|
| 212 |
homocedasticidade_ok = False
|
| 213 |
-
grupos = [df_var[col_preco][df_var[var_cat] == categoria].dropna()
|
| 214 |
-
for categoria in df_var[var_cat].unique()]
|
| 215 |
grupos_validos = [g for g in grupos if len(g) >= 2]
|
| 216 |
if len(grupos_validos) >= 2:
|
| 217 |
stat_levene, p_levene = levene(*grupos_validos)
|
|
@@ -220,7 +198,7 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 220 |
homocedasticidade_ok = True
|
| 221 |
results["homocedasticidade_ok"] = homocedasticidade_ok
|
| 222 |
|
| 223 |
-
#
|
| 224 |
if not normalidade_ok or not homocedasticidade_ok:
|
| 225 |
if len(grupos_validos) >= 2:
|
| 226 |
stat_kruskal, p_kruskal = kruskal(*grupos_validos)
|
|
@@ -676,42 +654,28 @@ elif st.session_state.page == 'REGRESSAO':
|
|
| 676 |
model_summary_obj = output_reg.get('model_summary_obj')
|
| 677 |
if model_summary_obj:
|
| 678 |
st.markdown("##### Sumário Geral do Modelo:")
|
| 679 |
-
|
| 680 |
-
sum_table0 = pd.read_html(
|
| 681 |
-
model_summary_obj.tables[0].as_html(),
|
| 682 |
-
header=None,
|
| 683 |
-
index_col=None,
|
| 684 |
-
flavor='bs4'
|
| 685 |
-
)[0]
|
| 686 |
st.table(sum_table0.iloc[:, :2].rename(columns={0: "Métrica", 1: "Valor"}))
|
| 687 |
st.table(sum_table0.iloc[:, 2:].rename(columns={2: "Métrica", 3: "Valor"}))
|
| 688 |
|
| 689 |
st.markdown("##### Coeficientes do Modelo:")
|
| 690 |
-
sum_table1 = pd.read_html(
|
| 691 |
-
model_summary_obj.tables[1].as_html(),
|
| 692 |
-
header=0,
|
| 693 |
-
index_col=0,
|
| 694 |
-
flavor='bs4'
|
| 695 |
-
)[0]
|
| 696 |
st.dataframe(sum_table1.style.format({
|
| 697 |
-
"coef": "{:.4f}", "std err": "{:.4f}",
|
| 698 |
-
"t": "{:.3f}", "P>|t|": "{:.3e}",
|
| 699 |
"[0.025": "{:.4f}", "0.975]": "{:.4f}"
|
| 700 |
}))
|
| 701 |
|
| 702 |
if len(model_summary_obj.tables) > 2:
|
| 703 |
st.markdown("##### Outras Estatísticas e Notas:")
|
| 704 |
notes_html = model_summary_obj.tables[2].as_html()
|
| 705 |
-
notes_df = pd.read_html(notes_html, header=None, index_col=None
|
| 706 |
for i in range(len(notes_df)):
|
| 707 |
line = notes_df.iloc[i].tolist()
|
| 708 |
st.text(" ".join([str(x) for x in line if pd.notna(x)]))
|
| 709 |
|
| 710 |
st.subheader("Métricas de Desempenho")
|
| 711 |
if 'performance_metrics' in output_reg:
|
| 712 |
-
metrics_df = pd.DataFrame.from_dict(
|
| 713 |
-
output_reg['performance_metrics'], orient='index', columns=['Valor']
|
| 714 |
-
)
|
| 715 |
st.table(metrics_df.style.format("{:.4f}"))
|
| 716 |
st.markdown("""
|
| 717 |
* **R-squared / R-squared Ajustado:** Variância explicada pelo modelo.
|
|
@@ -741,3 +705,5 @@ elif st.session_state.page == 'REGRESSAO':
|
|
| 741 |
else:
|
| 742 |
if not colunas_categoricas_reg and not colunas_continuas_reg:
|
| 743 |
st.error("Nenhuma coluna adequada identificada para regressão.")
|
|
|
|
|
|
|
|
|
| 35 |
if df is None:
|
| 36 |
return None, None, [], []
|
| 37 |
|
| 38 |
+
df.columns = df.columns.str.replace('[^A-Za-z0-9_]+', '', regex=True).str.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
coluna_preco_nome = None
|
| 41 |
if 'saleprice' in df.columns:
|
|
|
|
| 69 |
try:
|
| 70 |
df = pd.read_csv(fixed_url)
|
| 71 |
url_carregada = fixed_url
|
| 72 |
+
except Exception as e:
|
| 73 |
return None, None, [], [], []
|
| 74 |
|
| 75 |
st.success(f"Dataset carregado com sucesso de: {url_carregada} (Shape: {df.shape})")
|
| 76 |
+
df.columns = df.columns.str.replace('[^A-Za-z0-9_]+', '', regex=True).str.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
coluna_preco_nome = None
|
| 79 |
possible_price_cols = ['saleprice', 'sale_price', 'price']
|
|
|
|
| 126 |
col not in colunas_categoricas_potenciais or col in vars_sempre_continuas_para_reg
|
| 127 |
) and col != coluna_preco_nome
|
| 128 |
]
|
| 129 |
+
colunas_continuas_potenciais = sorted(list(set(col for col in colunas_continuas_potenciais if col in df.columns)))
|
|
|
|
|
|
|
| 130 |
|
| 131 |
return df, coluna_preco_nome, colunas_categoricas_potenciais, colunas_continuas_potenciais, df.columns.tolist()
|
| 132 |
|
|
|
|
| 138 |
results = {"var_cat": var_cat, "plots": {}}
|
| 139 |
df_var = df_analysis[[var_cat, col_preco]].copy()
|
| 140 |
|
|
|
|
| 141 |
if df_var[var_cat].dtype != 'object' and not pd.api.types.is_categorical_dtype(df_var[var_cat]):
|
| 142 |
df_var[var_cat] = df_var[var_cat].astype('category')
|
| 143 |
|
|
|
|
| 146 |
results["error"] = "Dados insuficientes ou poucos níveis após limpeza."
|
| 147 |
return results
|
| 148 |
|
| 149 |
+
formula = f'{col_preco} ~ C({var_cat})'
|
|
|
|
|
|
|
| 150 |
try:
|
| 151 |
modelo = ols(formula, data=df_var).fit()
|
| 152 |
results["anova_table"] = sm.stats.anova_lm(modelo, typ=2)
|
| 153 |
|
| 154 |
+
if f'C({var_cat})' in results["anova_table"].index:
|
| 155 |
+
results["p_valor_anova"] = results["anova_table"].loc[f'C({var_cat})', 'PR(>F)']
|
|
|
|
| 156 |
else:
|
| 157 |
results["p_valor_anova"] = results["anova_table"]['PR(>F)'].iloc[0]
|
| 158 |
|
| 159 |
residuos = modelo.resid
|
| 160 |
results["residuos_count"] = len(residuos)
|
| 161 |
|
|
|
|
| 162 |
normalidade_ok = False
|
| 163 |
if len(residuos) >= 3:
|
| 164 |
if len(residuos) <= 5000:
|
|
|
|
| 179 |
if len(residuos) > 1:
|
| 180 |
sns.histplot(residuos, kde=True, ax=ax_norm[0], stat="density", bins=30)
|
| 181 |
ax_norm[0].set_title(f'Histograma Resíduos ({var_cat})', fontsize=10)
|
| 182 |
+
sm.qqplot(residuos, line='s', ax=ax_norm[1], markerfacecolor="skyblue", markeredgecolor="dodgerblue", alpha=0.7)
|
|
|
|
| 183 |
ax_norm[1].set_title(f'Q-Q Plot Resíduos ({var_cat})', fontsize=10)
|
| 184 |
else:
|
| 185 |
ax_norm[0].text(0.5, 0.5, "Poucos dados", ha='center', va='center')
|
|
|
|
| 187 |
plt.tight_layout()
|
| 188 |
results["plots"]["normalidade"] = fig_norm
|
| 189 |
|
| 190 |
+
# Teste de homocedasticidade (Levene)
|
| 191 |
homocedasticidade_ok = False
|
| 192 |
+
grupos = [df_var[col_preco][df_var[var_cat] == categoria].dropna() for categoria in df_var[var_cat].unique()]
|
|
|
|
| 193 |
grupos_validos = [g for g in grupos if len(g) >= 2]
|
| 194 |
if len(grupos_validos) >= 2:
|
| 195 |
stat_levene, p_levene = levene(*grupos_validos)
|
|
|
|
| 198 |
homocedasticidade_ok = True
|
| 199 |
results["homocedasticidade_ok"] = homocedasticidade_ok
|
| 200 |
|
| 201 |
+
# Teste de Kruskal-Wallis (se necessário)
|
| 202 |
if not normalidade_ok or not homocedasticidade_ok:
|
| 203 |
if len(grupos_validos) >= 2:
|
| 204 |
stat_kruskal, p_kruskal = kruskal(*grupos_validos)
|
|
|
|
| 654 |
model_summary_obj = output_reg.get('model_summary_obj')
|
| 655 |
if model_summary_obj:
|
| 656 |
st.markdown("##### Sumário Geral do Modelo:")
|
| 657 |
+
sum_table0 = pd.read_html(model_summary_obj.tables[0].as_html(), header=None, index_col=None)[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
st.table(sum_table0.iloc[:, :2].rename(columns={0: "Métrica", 1: "Valor"}))
|
| 659 |
st.table(sum_table0.iloc[:, 2:].rename(columns={2: "Métrica", 3: "Valor"}))
|
| 660 |
|
| 661 |
st.markdown("##### Coeficientes do Modelo:")
|
| 662 |
+
sum_table1 = pd.read_html(model_summary_obj.tables[1].as_html(), header=0, index_col=0)[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
st.dataframe(sum_table1.style.format({
|
| 664 |
+
"coef": "{:.4f}", "std err": "{:.4f}", "t": "{:.3f}", "P>|t|": "{:.3e}",
|
|
|
|
| 665 |
"[0.025": "{:.4f}", "0.975]": "{:.4f}"
|
| 666 |
}))
|
| 667 |
|
| 668 |
if len(model_summary_obj.tables) > 2:
|
| 669 |
st.markdown("##### Outras Estatísticas e Notas:")
|
| 670 |
notes_html = model_summary_obj.tables[2].as_html()
|
| 671 |
+
notes_df = pd.read_html(notes_html, header=None, index_col=None)[0]
|
| 672 |
for i in range(len(notes_df)):
|
| 673 |
line = notes_df.iloc[i].tolist()
|
| 674 |
st.text(" ".join([str(x) for x in line if pd.notna(x)]))
|
| 675 |
|
| 676 |
st.subheader("Métricas de Desempenho")
|
| 677 |
if 'performance_metrics' in output_reg:
|
| 678 |
+
metrics_df = pd.DataFrame.from_dict(output_reg['performance_metrics'], orient='index', columns=['Valor'])
|
|
|
|
|
|
|
| 679 |
st.table(metrics_df.style.format("{:.4f}"))
|
| 680 |
st.markdown("""
|
| 681 |
* **R-squared / R-squared Ajustado:** Variância explicada pelo modelo.
|
|
|
|
| 705 |
else:
|
| 706 |
if not colunas_categoricas_reg and not colunas_continuas_reg:
|
| 707 |
st.error("Nenhuma coluna adequada identificada para regressão.")
|
| 708 |
+
|
| 709 |
+
|