Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +52 -18
src/streamlit_app.py
CHANGED
|
@@ -35,7 +35,13 @@ def load_data_anova():
|
|
| 35 |
if df is None:
|
| 36 |
return None, None, [], []
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
coluna_preco_nome = None
|
| 41 |
if 'saleprice' in df.columns:
|
|
@@ -69,11 +75,18 @@ def load_data_reg():
|
|
| 69 |
try:
|
| 70 |
df = pd.read_csv(fixed_url)
|
| 71 |
url_carregada = fixed_url
|
| 72 |
-
except Exception
|
| 73 |
return None, None, [], [], []
|
| 74 |
|
| 75 |
st.success(f"Dataset carregado com sucesso de: {url_carregada} (Shape: {df.shape})")
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
coluna_preco_nome = None
|
| 79 |
possible_price_cols = ['saleprice', 'sale_price', 'price']
|
|
@@ -126,7 +139,9 @@ def load_data_reg():
|
|
| 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(
|
|
|
|
|
|
|
| 130 |
|
| 131 |
return df, coluna_preco_nome, colunas_categoricas_potenciais, colunas_continuas_potenciais, df.columns.tolist()
|
| 132 |
|
|
@@ -138,6 +153,7 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 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,19 +162,23 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 146 |
results["error"] = "Dados insuficientes ou poucos níveis após limpeza."
|
| 147 |
return results
|
| 148 |
|
| 149 |
-
|
|
|
|
|
|
|
| 150 |
try:
|
| 151 |
modelo = ols(formula, data=df_var).fit()
|
| 152 |
results["anova_table"] = sm.stats.anova_lm(modelo, typ=2)
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
|
|
|
| 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,7 +199,8 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 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],
|
|
|
|
| 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,9 +208,10 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 187 |
plt.tight_layout()
|
| 188 |
results["plots"]["normalidade"] = fig_norm
|
| 189 |
|
| 190 |
-
# Teste de
|
| 191 |
homocedasticidade_ok = False
|
| 192 |
-
grupos = [df_var[col_preco][df_var[var_cat] == categoria].dropna()
|
|
|
|
| 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,7 +220,7 @@ def perform_anova_for_variable(df_analysis, var_cat, col_preco):
|
|
| 198 |
homocedasticidade_ok = True
|
| 199 |
results["homocedasticidade_ok"] = homocedasticidade_ok
|
| 200 |
|
| 201 |
-
#
|
| 202 |
if not normalidade_ok or not homocedasticidade_ok:
|
| 203 |
if len(grupos_validos) >= 2:
|
| 204 |
stat_kruskal, p_kruskal = kruskal(*grupos_validos)
|
|
@@ -654,28 +676,42 @@ elif st.session_state.page == 'REGRESSAO':
|
|
| 654 |
model_summary_obj = output_reg.get('model_summary_obj')
|
| 655 |
if model_summary_obj:
|
| 656 |
st.markdown("##### Sumário Geral do Modelo:")
|
| 657 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
st.dataframe(sum_table1.style.format({
|
| 664 |
-
"coef": "{:.4f}", "std err": "{:.4f}",
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 679 |
st.table(metrics_df.style.format("{:.4f}"))
|
| 680 |
st.markdown("""
|
| 681 |
* **R-squared / R-squared Ajustado:** Variância explicada pelo modelo.
|
|
@@ -705,5 +741,3 @@ elif st.session_state.page == 'REGRESSAO':
|
|
| 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 |
-
|
|
|
|
| 35 |
if df is None:
|
| 36 |
return None, None, [], []
|
| 37 |
|
| 38 |
+
# Normalizar nomes de coluna: sem espaços, minúsculas, sem caracteres especiais
|
| 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 |
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 |
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 |
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 |
results["error"] = "Dados insuficientes ou poucos níveis após limpeza."
|
| 163 |
return results
|
| 164 |
|
| 165 |
+
# Usar backticks para nomes de coluna na fórmula
|
| 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 |
+
key = f'C(`{var_cat}`)'
|
| 173 |
+
if key in results["anova_table"].index:
|
| 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 |
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 |
plt.tight_layout()
|
| 209 |
results["plots"]["normalidade"] = fig_norm
|
| 210 |
|
| 211 |
+
# 2. Homocedasticidade (Teste de Levene)
|
| 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 |
homocedasticidade_ok = True
|
| 221 |
results["homocedasticidade_ok"] = homocedasticidade_ok
|
| 222 |
|
| 223 |
+
# 3. Kruskal-Wallis (se necessário)
|
| 224 |
if not normalidade_ok or not homocedasticidade_ok:
|
| 225 |
if len(grupos_validos) >= 2:
|
| 226 |
stat_kruskal, p_kruskal = kruskal(*grupos_validos)
|
|
|
|
| 676 |
model_summary_obj = output_reg.get('model_summary_obj')
|
| 677 |
if model_summary_obj:
|
| 678 |
st.markdown("##### Sumário Geral do Modelo:")
|
| 679 |
+
# Usar flavor='bs4' para evitar a dependência de lxml
|
| 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, flavor='bs4')[0]
|
| 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 |
else:
|
| 742 |
if not colunas_categoricas_reg and not colunas_continuas_reg:
|
| 743 |
st.error("Nenhuma coluna adequada identificada para regressão.")
|
|
|
|
|
|