ricardoadriano commited on
Commit
24717d8
·
verified ·
1 Parent(s): 8804675

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +23 -112
src/streamlit_app.py CHANGED
@@ -2,7 +2,7 @@
2
  # coding: utf-8
3
 
4
  # =====================================================
5
- # Dashboard - AmesHousing (Tarefa 4)
6
  # =====================================================
7
 
8
  import streamlit as st
@@ -28,6 +28,7 @@ st.set_page_config(
28
  layout="wide",
29
  initial_sidebar_state="expanded"
30
  )
 
31
 
32
  st.markdown("<h1 style='text-align:center;color:#003366;'>Análise do Dataset AmesHousing</h1>", unsafe_allow_html=True)
33
  st.markdown("---")
@@ -106,7 +107,7 @@ if not dados_filtrados.empty:
106
  fig, ax = plt.subplots(figsize=(8,5))
107
  sns.histplot(dados_filtrados['SalePrice'], kde=True, ax=ax)
108
  ax.set_title("Distribuição do Preço de Venda")
109
- st.pyplot(fig)
110
  else:
111
  st.warning("Nenhum dado disponível com os filtros aplicados.")
112
 
@@ -123,18 +124,21 @@ if not dados_filtrados.empty:
123
  sns.boxplot(x=variavel, y="SalePrice", data=dados_filtrados, ax=ax2)
124
  plt.xticks(rotation=90)
125
  ax2.set_title(f"Preço de Venda por {variavel}")
126
- st.pyplot(fig2)
127
  else:
128
  st.warning(f"Não é possível gerar boxplot: apenas uma categoria em {variavel} após os filtros.")
129
 
130
  # Scatter interativo
131
  st.subheader("Preço Médio de Venda por Bairro")
132
- if not dados_filtrados.empty:
133
- bairro_grouped = dados_filtrados.groupby('Neighborhood').agg(
 
134
  count=('SalePrice','size'),
135
  mean_price=('SalePrice','mean')
136
  ).reset_index()
137
 
 
 
138
  bairro_filtered = bairro_grouped[bairro_grouped['count'] >= 5]
139
  if not bairro_filtered.empty:
140
  fig3 = px.scatter(
@@ -147,7 +151,7 @@ if not dados_filtrados.empty:
147
  labels={'mean_price': 'Preço Médio de Venda', 'Neighborhood':'Bairro'},
148
  opacity=0.8
149
  )
150
- st.plotly_chart(fig3, use_container_width=True)
151
  else:
152
  st.warning("Não há bairros suficientes após filtros para gerar o gráfico.")
153
 
@@ -163,18 +167,13 @@ if not dados_filtrados.empty:
163
  for nome in ["Neighborhood", "Garage_Type", "Fireplaces"]:
164
  categorias = dados_filtrados[nome].dropna().unique()
165
  if len(categorias) < 2:
166
- st.warning(f"ANOVA não pôde ser realizada para **{nome}** (menos de 2 grupos após os filtros).")
167
  continue
168
-
169
  modelo = ols(f'SalePrice ~ C({nome})', data=dados_filtrados).fit()
170
  st.markdown(f"#### ANOVA - {nome}")
171
  anova = sm.stats.anova_lm(modelo, typ=2)
172
  st.dataframe(anova)
173
 
174
- # Validação dos Pressupostos
175
  st.markdown("### Validação dos Pressupostos da ANOVA")
176
-
177
- st.markdown("#### Teste de Normalidade (Shapiro-Wilk)")
178
  for nome in ["Neighborhood","Garage_Type","Fireplaces"]:
179
  categorias = dados_filtrados[nome].dropna().unique()
180
  if len(categorias) < 2:
@@ -182,26 +181,7 @@ if not dados_filtrados.empty:
182
  modelo = ols(f'SalePrice ~ C({nome})', data=dados_filtrados).fit()
183
  residuos = modelo.resid
184
  stat, p = shapiro(residuos.dropna())
185
- st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f} "
186
- + ("resíduos normais" if p >= alpha else "violação de normalidade"))
187
-
188
- st.markdown("#### Teste de Homocedasticidade (Levene)")
189
- for nome in ["Neighborhood","Garage_Type","Fireplaces"]:
190
- grupos = [grupo["SalePrice"].dropna() for _, grupo in dados_filtrados.groupby(nome)]
191
- if len(grupos) < 2:
192
- continue
193
- stat, p = levene(*grupos)
194
- st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f} "
195
- + ("variâncias iguais" if p >= alpha else "variâncias diferentes"))
196
-
197
- st.markdown("### Teste não-paramétrico (Kruskal-Wallis)")
198
- for nome in ["Neighborhood","Garage_Type","Fireplaces"]:
199
- grupos = [grupo["SalePrice"].dropna() for _, grupo in dados_filtrados.groupby(nome)]
200
- if len(grupos) < 2:
201
- continue
202
- stat, p = kruskal(*grupos)
203
- st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f} "
204
- + ("diferenças significativas" if p < alpha else "sem diferença significativa"))
205
 
206
  # =================================================
207
  # Regressão Linear — Tarefa 4
@@ -232,107 +212,38 @@ if st.button("Ajustar modelo"):
232
  y_col = 'SalePrice'
233
 
234
  formula = construir_formula(y_col, feats_num, feats_cat, inter_1 if interagir else None, inter_2 if interagir else None)
235
-
236
  df_treino, df_teste = train_test_split(df_modelo, test_size=teste_size, random_state=42)
237
  model = ols(formula, data=df_treino).fit()
238
 
239
  st.markdown("#### Especificação do Modelo")
240
  st.code(formula)
241
-
242
  st.markdown("#### Coeficientes e Inferência")
243
  st.dataframe(model.summary2().tables[1])
244
 
245
- sig = model.pvalues < alpha_reg
246
- interpretacoes = []
247
- for nome, beta in model.params.items():
248
- if nome == 'Intercept':
249
- continue
250
- tag = "significativo" if sig.get(nome, False) else "não significativo"
251
- direcao = "aumenta" if beta > 0 else "reduz"
252
- interpretacoes.append(f"• {nome}: {tag}; coef. {beta:.3f} ⇒ {direcao} o preço esperado, ceteris paribus.")
253
- if interpretacoes:
254
- st.markdown("**Leitura rápida dos coeficientes:**\n" + "\n".join(interpretacoes))
255
-
256
  y_true = df_teste['SalePrice']
257
  y_pred = model.predict(df_teste)
258
  if usar_logy:
259
- y_true_exp = np.exp(y_true)
260
- y_pred_exp = np.exp(y_pred)
261
- R2 = r2_score(y_true_exp, y_pred_exp)
262
- RMSE = mean_squared_error(y_true_exp, y_pred_exp, squared=False)
263
- MAE = mean_absolute_error(y_true_exp, y_pred_exp)
264
- else:
265
- R2 = r2_score(y_true, y_pred)
266
- RMSE = mean_squared_error(y_true, y_pred, squared=False)
267
- MAE = mean_absolute_error(y_true, y_pred)
268
-
269
- met_df = pd.DataFrame({
270
- 'Métrica': ['R²', 'RMSE', 'MAE'],
271
- 'Valor': [R2, RMSE, MAE]
272
- })
273
- st.markdown("#### Avaliação do Modelo (holdout)")
274
- st.dataframe(met_df)
275
-
276
  residuos = model.resid
277
  fitted = model.fittedvalues
278
-
279
  cols = st.columns(3)
280
  with cols[0]:
281
- st.markdown("**Resíduos vs Ajustados** (homocedasticidade)")
282
  fig_r, ax_r = plt.subplots(figsize=(4,3))
283
  ax_r.scatter(fitted, residuos, alpha=0.5)
284
  ax_r.axhline(0, color='red', linestyle='--')
285
- ax_r.set_xlabel('Ajustados')
286
- ax_r.set_ylabel('Resíduos')
287
- st.pyplot(fig_r)
288
  with cols[1]:
289
- st.markdown("**QQ-Plot** (normalidade)")
290
  fig_q = sm.qqplot(residuos, line='45', fit=True)
291
- st.pyplot(fig_q)
292
  with cols[2]:
293
- st.markdown("**Histograma dos Resíduos**")
294
  fig_h, ax_h = plt.subplots(figsize=(4,3))
295
  sns.histplot(residuos, kde=True, ax=ax_h)
296
- st.pyplot(fig_h)
297
-
298
- sh_stat, sh_p = shapiro(residuos)
299
- bp_stat, bp_p, _, _ = het_breuschpagan(residuos, model.model.exog)
300
- st.markdown("#### Testes de Pressupostos (Regressão)")
301
- st.write(f"Shapiro-Wilk: estatística={sh_stat:.3f}, p={sh_p:.3f} → " + ("normalidade OK" if sh_p>=alpha_reg else "violação de normalidade"))
302
- st.write(f"Breusch-Pagan: estatística={bp_stat:.3f}, p={bp_p:.3f} → " + ("homocedasticidade OK" if bp_p>=alpha_reg else "heterocedasticidade detectada"))
303
-
304
- try:
305
- design = model.model.exog
306
- nomes = model.model.exog_names
307
- vif_vals = []
308
- for i in range(design.shape[1]):
309
- try:
310
- vif_vals.append(variance_inflation_factor(design, i))
311
- except Exception:
312
- vif_vals.append(np.nan)
313
- vif_df = pd.DataFrame({'Variável': nomes, 'VIF': vif_vals})
314
- st.markdown("#### VIF (Multicolinearidade)")
315
- st.dataframe(vif_df)
316
- except Exception as e:
317
- st.info(f"Não foi possível calcular VIF: {e}")
318
-
319
- avisos = []
320
- if sh_p < alpha_reg:
321
- avisos.append("Normalidade violada — considere log-transformar o alvo ou técnicas robustas.")
322
- if bp_p < alpha_reg:
323
- avisos.append("Heterocedasticidade — avalie transformação ou erros-padrão robustos (HC).")
324
- if (pd.Series(vif_vals).dropna() > 10).any():
325
- avisos.append("VIF > 10 em alguma variável — possível multicolinearidade.")
326
- if avisos:
327
- st.warning("\n".join(avisos))
328
-
329
- if interagir and inter_1 and inter_2:
330
- termo = f"C({inter_1}):C({inter_2})" if (inter_1 in feats_cat and inter_2 in feats_cat) else (
331
- f"C({inter_1}):{inter_2}" if inter_1 in feats_cat else (
332
- f"{inter_1}:C({inter_2})" if inter_2 in feats_cat else f"{inter_1}:{inter_2}"))
333
- if termo in model.pvalues:
334
- p_int = model.pvalues[termo]
335
- st.info(f"Interação {termo} — p={p_int:.4f} → " + ("**significativa**" if p_int < alpha_reg else "não significativa"))
336
-
337
- st.success("Modelagem concluída.")
338
-
 
2
  # coding: utf-8
3
 
4
  # =====================================================
5
+ # Dashboard - AmesHousing (Tarefa 4) - versão otimizada
6
  # =====================================================
7
 
8
  import streamlit as st
 
28
  layout="wide",
29
  initial_sidebar_state="expanded"
30
  )
31
+ st.experimental_set_query_params() # mantém posição estável na página
32
 
33
  st.markdown("<h1 style='text-align:center;color:#003366;'>Análise do Dataset AmesHousing</h1>", unsafe_allow_html=True)
34
  st.markdown("---")
 
107
  fig, ax = plt.subplots(figsize=(8,5))
108
  sns.histplot(dados_filtrados['SalePrice'], kde=True, ax=ax)
109
  ax.set_title("Distribuição do Preço de Venda")
110
+ st.pyplot(fig, clear_figure=True, use_container_width=False)
111
  else:
112
  st.warning("Nenhum dado disponível com os filtros aplicados.")
113
 
 
124
  sns.boxplot(x=variavel, y="SalePrice", data=dados_filtrados, ax=ax2)
125
  plt.xticks(rotation=90)
126
  ax2.set_title(f"Preço de Venda por {variavel}")
127
+ st.pyplot(fig2, clear_figure=True, use_container_width=False)
128
  else:
129
  st.warning(f"Não é possível gerar boxplot: apenas uma categoria em {variavel} após os filtros.")
130
 
131
  # Scatter interativo
132
  st.subheader("Preço Médio de Venda por Bairro")
133
+ @st.cache_data
134
+ def agrupar_bairros(df):
135
+ return df.groupby('Neighborhood').agg(
136
  count=('SalePrice','size'),
137
  mean_price=('SalePrice','mean')
138
  ).reset_index()
139
 
140
+ if not dados_filtrados.empty:
141
+ bairro_grouped = agrupar_bairros(dados_filtrados)
142
  bairro_filtered = bairro_grouped[bairro_grouped['count'] >= 5]
143
  if not bairro_filtered.empty:
144
  fig3 = px.scatter(
 
151
  labels={'mean_price': 'Preço Médio de Venda', 'Neighborhood':'Bairro'},
152
  opacity=0.8
153
  )
154
+ st.plotly_chart(fig3, use_container_width=False)
155
  else:
156
  st.warning("Não há bairros suficientes após filtros para gerar o gráfico.")
157
 
 
167
  for nome in ["Neighborhood", "Garage_Type", "Fireplaces"]:
168
  categorias = dados_filtrados[nome].dropna().unique()
169
  if len(categorias) < 2:
 
170
  continue
 
171
  modelo = ols(f'SalePrice ~ C({nome})', data=dados_filtrados).fit()
172
  st.markdown(f"#### ANOVA - {nome}")
173
  anova = sm.stats.anova_lm(modelo, typ=2)
174
  st.dataframe(anova)
175
 
 
176
  st.markdown("### Validação dos Pressupostos da ANOVA")
 
 
177
  for nome in ["Neighborhood","Garage_Type","Fireplaces"]:
178
  categorias = dados_filtrados[nome].dropna().unique()
179
  if len(categorias) < 2:
 
181
  modelo = ols(f'SalePrice ~ C({nome})', data=dados_filtrados).fit()
182
  residuos = modelo.resid
183
  stat, p = shapiro(residuos.dropna())
184
+ st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
  # =================================================
187
  # Regressão Linear — Tarefa 4
 
212
  y_col = 'SalePrice'
213
 
214
  formula = construir_formula(y_col, feats_num, feats_cat, inter_1 if interagir else None, inter_2 if interagir else None)
 
215
  df_treino, df_teste = train_test_split(df_modelo, test_size=teste_size, random_state=42)
216
  model = ols(formula, data=df_treino).fit()
217
 
218
  st.markdown("#### Especificação do Modelo")
219
  st.code(formula)
 
220
  st.markdown("#### Coeficientes e Inferência")
221
  st.dataframe(model.summary2().tables[1])
222
 
223
+ # Métricas
 
 
 
 
 
 
 
 
 
 
224
  y_true = df_teste['SalePrice']
225
  y_pred = model.predict(df_teste)
226
  if usar_logy:
227
+ y_true = np.exp(y_true)
228
+ y_pred = np.exp(y_pred)
229
+ R2 = r2_score(y_true, y_pred)
230
+ RMSE = mean_squared_error(y_true, y_pred, squared=False)
231
+ MAE = mean_absolute_error(y_true, y_pred)
232
+ st.dataframe(pd.DataFrame({'Métrica':['R²','RMSE','MAE'],'Valor':[R2,RMSE,MAE]}))
233
+
234
+ # Gráficos diagnósticos fixos
 
 
 
 
 
 
 
 
 
235
  residuos = model.resid
236
  fitted = model.fittedvalues
 
237
  cols = st.columns(3)
238
  with cols[0]:
 
239
  fig_r, ax_r = plt.subplots(figsize=(4,3))
240
  ax_r.scatter(fitted, residuos, alpha=0.5)
241
  ax_r.axhline(0, color='red', linestyle='--')
242
+ st.pyplot(fig_r, clear_figure=True, use_container_width=False)
 
 
243
  with cols[1]:
 
244
  fig_q = sm.qqplot(residuos, line='45', fit=True)
245
+ st.pyplot(fig_q, clear_figure=True, use_container_width=False)
246
  with cols[2]:
 
247
  fig_h, ax_h = plt.subplots(figsize=(4,3))
248
  sns.histplot(residuos, kde=True, ax=ax_h)
249
+ st.pyplot(fig_h, clear_figure=True, use_container_width=False)