vsalgs commited on
Commit
45e81a9
·
verified ·
1 Parent(s): ba1df54

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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
- 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,11 +75,18 @@ def load_data_reg():
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,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(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,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
- 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,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], 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,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 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,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
- # 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,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
- 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,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.")