#!/usr/bin/env python # coding: utf-8 # ===================================================== # Dashboard - Testes de Hipóteses com AmesHousing (Tarefa 4) # ===================================================== import streamlit as st import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import plotly.express as px import numpy as np from scipy import stats from scipy.stats import shapiro, levene, kruskal from statsmodels.formula.api import ols import statsmodels.api as sm from statsmodels.stats.diagnostic import het_breuschpagan from statsmodels.stats.outliers_influence import variance_inflation_factor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score # ----------------------------------------------------- # Configuração da Página # ----------------------------------------------------- st.set_page_config( page_title="Dashboard - Testes de Hipóteses com AmesHousing", layout="wide", initial_sidebar_state="expanded" ) st.markdown("

Simulador de Testes de Hipótese

", unsafe_allow_html=True) st.markdown("

Análise do Dataset AmesHousing

", unsafe_allow_html=True) st.markdown("---") # ----------------------------------------------------- # Abas do Dashboard # ----------------------------------------------------- tabs = st.tabs(["Simulações Teóricas", "Análise AmesHousing"]) # mantém as abas existentes # ----------------------------------------------------- # Aba 1: Simulações Teóricas (inalterada) # ----------------------------------------------------- with tabs[0]: st.subheader("Teste de Hipótese para Proporção de Testes Positivos de COVID-19") st.sidebar.markdown("### Parâmetros do Teste (Proporção)") p_pop = st.sidebar.slider("Proporção populacional (H0)", 0.0, 1.0, 0.1, 0.01, key="p_pop") p_sample = st.sidebar.slider("Proporção amostral", 0.0, 1.0, 0.12, 0.01, key="p_sample") n = st.sidebar.slider("Tamanho da amostra", 100, 10000, 1000, 10, key="n_sample") alpha_prop = st.sidebar.slider("Nível de significância (α)", 0.01, 0.10, 0.05, 0.01, key="alpha_prop") se = np.sqrt(p_pop*(1-p_pop)/n) z = (p_sample - p_pop)/se p_value = 2*(1 - stats.norm.cdf(abs(z))) st.write(f"**Z** = {z:.4f}") st.write(f"**p-valor** = {p_value:.4f}") if p_value < alpha_prop: st.write("**Rejeitamos H0**: diferença significativa.") else: st.write("**Não rejeitamos H0**: sem diferença significativa.") # ----------------------------------------------------- # Aba 2: Análise AmesHousing + (NOVO) Regressão para Tarefa 4 # ----------------------------------------------------- with tabs[1]: st.subheader("Análise de Variância - AmesHousing Dataset") st.markdown("---") # Leitura do CSV — ajustado para usar o arquivo enviado com o app @st.cache_data def carregar_dados(): paths_tentativa = [ "AmesHousing.csv", # mesma pasta do app "/mnt/data/AmesHousing.csv", # caminho do ambiente atual "../Dados/AmesHousing.csv", # caminho original do código ] last_err = None for p in paths_tentativa: try: df = pd.read_csv(p) return df except Exception as e: last_err = e continue raise RuntimeError(f"Não foi possível carregar o AmesHousing.csv. Último erro: {last_err}") casa_data = carregar_dados() casa_data.columns = casa_data.columns.str.strip().str.replace(" ", "_") # ----------------------------- # Amostragem (mantém comportamento anterior) # ----------------------------- n_amostra = st.session_state.get("n_sample", len(casa_data)) if n_amostra < len(casa_data): dados = casa_data.sample(n=n_amostra, random_state=42) else: dados = casa_data.copy() # ----------------------------- # Filtro interativo no sidebar # ----------------------------- st.sidebar.markdown("### Filtros AmesHousing") bairros = st.sidebar.multiselect( "Selecione bairros", options=sorted(dados["Neighborhood"].dropna().unique()), default=None ) # Aplicar filtro dados_filtrados = dados.copy() if bairros: dados_filtrados = dados_filtrados[dados_filtrados["Neighborhood"].isin(bairros)] # ------------------------------------------------- # Análise Exploratória (existente) # ------------------------------------------------- st.markdown("### Distribuição do Preço de Venda") if not dados_filtrados.empty: fig, ax = plt.subplots(figsize=(8,5)) sns.histplot(dados_filtrados['SalePrice'], kde=True, ax=ax) ax.set_title("Distribuição do Preço de Venda") st.pyplot(fig) else: st.warning("Nenhum dado disponível com os filtros aplicados.") # Boxplots (existente) st.markdown("### Boxplots das Variáveis Selecionadas") variavel = st.selectbox( "Escolha a variável categórica para comparar preços:", ["Neighborhood","Garage_Type","Fireplaces"] ) if not dados_filtrados.empty: if len(dados_filtrados[variavel].dropna().unique()) > 1: fig2, ax2 = plt.subplots(figsize=(12,6)) sns.boxplot(x=variavel, y="SalePrice", data=dados_filtrados, ax=ax2) plt.xticks(rotation=90) ax2.set_title(f"Preço de Venda por {variavel}") st.pyplot(fig2) else: st.warning(f"Não é possível gerar boxplot: apenas uma categoria em {variavel} após os filtros.") # Scatter interativo (média de preço por bairro) — existente st.markdown("### Preço Médio de Venda por Bairro") if not dados_filtrados.empty: bairro_grouped = dados_filtrados.groupby('Neighborhood').agg( count=('SalePrice','size'), mean_price=('SalePrice','mean') ).reset_index() bairro_filtered = bairro_grouped[bairro_grouped['count'] >= 5] if not bairro_filtered.empty: fig3 = px.scatter( bairro_filtered, x='mean_price', y='Neighborhood', size='count', color='Neighborhood', title='Preço Médio de Venda vs Bairro (Ames, Iowa)', labels={'mean_price': 'Preço Médio de Venda', 'Neighborhood':'Bairro'}, opacity=0.8 ) st.plotly_chart(fig3, use_container_width=True) else: st.warning("Não há bairros suficientes após filtros para gerar o gráfico.") # ------------------------------------------------- # ANOVA — existente # ------------------------------------------------- st.markdown("### ANOVA para Neighborhood, Garage_Type e Fireplaces") alpha = st.sidebar.slider( "Nível de significância (α) - ANOVA AmesHousing", 0.01,0.10,0.05,0.01, key="alpha_ames" ) if not dados_filtrados.empty: for nome in ["Neighborhood", "Garage_Type", "Fireplaces"]: categorias = dados_filtrados[nome].dropna().unique() if len(categorias) < 2: st.warning(f"ANOVA não pôde ser realizada para **{nome}** (menos de 2 grupos após os filtros).") continue modelo = ols(f'SalePrice ~ C({nome})', data=dados_filtrados).fit() st.markdown(f"#### ANOVA - {nome}") anova = sm.stats.anova_lm(modelo, typ=2) st.dataframe(anova) # ------------------------------------------------- # Validação dos Pressupostos — existente # ------------------------------------------------- st.markdown("### Validação dos Pressupostos da ANOVA") st.markdown("#### Teste de Normalidade (Shapiro-Wilk)") for nome in ["Neighborhood","Garage_Type","Fireplaces"]: categorias = dados_filtrados[nome].dropna().unique() if len(categorias) < 2: st.warning(f"Shapiro-Wilk não pôde ser realizado para **{nome}** (menos de 2 grupos após os filtros).") continue modelo = ols(f'SalePrice ~ C({nome})', data=dados_filtrados).fit() residuos = modelo.resid stat, p = shapiro(residuos.dropna()) st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f} " + ("resíduos normais" if p >= alpha else "violação de normalidade")) st.markdown("#### Teste de Homocedasticidade (Levene)") for nome in ["Neighborhood","Garage_Type","Fireplaces"]: grupos = [grupo["SalePrice"].dropna() for _, grupo in dados_filtrados.groupby(nome)] if len(grupos) < 2: st.warning(f"Levene não pôde ser realizado para **{nome}** (menos de 2 grupos após os filtros).") continue stat, p = levene(*grupos) st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f} " + ("variâncias iguais" if p >= alpha else "variâncias diferentes")) # ------------------------------------------------- # Kruskal-Wallis — existente # ------------------------------------------------- st.markdown("### Teste não-paramétrico (Kruskal-Wallis)") for nome in ["Neighborhood","Garage_Type","Fireplaces"]: grupos = [grupo["SalePrice"].dropna() for _, grupo in dados_filtrados.groupby(nome)] if len(grupos) < 2: st.warning(f"Kruskal-Wallis não pôde ser realizado para **{nome}** (menos de 2 grupos após os filtros).") continue stat, p = kruskal(*grupos) st.write(f"{nome}: estatística={stat:.3f}, p={p:.3f} " + ("diferenças significativas" if p < alpha else "sem diferença significativa")) # ================================================= # (NOVO) Regressão Linear — atende à Tarefa 4 (itens a–d) # ================================================= st.markdown("---") st.subheader("Regressão Linear — Tarefa 4 (PPCA/UnB)") st.markdown("Selecione variáveis para modelagem do **SalePrice** (alvo).") # Sugestões de variáveis comumente utilizadas candidatos_numericas = [ c for c in [ 'Bedroom_AbvGr','Full_Bath','Half_Bath','TotRms_AbvGrd','Gr_Liv_Area', 'Garage_Cars','Garage_Area','Overall_Qual','Overall_Cond','Year_Built','Lot_Area', 'Fireplaces' ] if c in dados_filtrados.columns ] candidatos_categoricas = [c for c in ['Neighborhood','House_Style','Bldg_Type','Garage_Type','Kitchen_Qual'] if c in dados_filtrados.columns] with st.expander("Seleção de variáveis"): cols = st.columns(2) with cols[0]: feats_num = st.multiselect("Numéricas", options=candidatos_numericas, default=['Bedroom_AbvGr','Garage_Cars','Gr_Liv_Area'] if 'Garage_Cars' in candidatos_numericas else candidatos_numericas[:2]) with cols[1]: feats_cat = st.multiselect("Categóricas", options=candidatos_categoricas, default=['Neighborhood'] if 'Neighborhood' in candidatos_categoricas else []) interagir = st.checkbox("Adicionar interação entre duas variáveis", value=False) inter_1 = inter_2 = None if interagir: inter_1 = st.selectbox("Variável 1 (para interação)", options=feats_num + feats_cat, index=0 if feats_num else 0) inter_2 = st.selectbox("Variável 2 (para interação)", options=[v for v in feats_num + feats_cat if v != inter_1], index=0) usar_logy = st.checkbox("Aplicar transformação log(SalePrice) caso pressuspostos sejam violados", value=False) teste_size = st.slider("Proporção de teste (holdout)", 0.1, 0.5, 0.2, 0.05) alpha_reg = st.slider("Nível de significância (α) — Regressão", 0.01, 0.10, 0.05, 0.01) # Construção da fórmula patsy def construir_formula(y, feats_num, feats_cat, inter_1=None, inter_2=None): termos = [] termos += feats_num termos += [f"C({c})" for c in feats_cat] if inter_1 and inter_2: a = f"C({inter_1})" if inter_1 in feats_cat else inter_1 b = f"C({inter_2})" if inter_2 in feats_cat else inter_2 termos.append(f"{a}:{b}") # interação rhs = " + ".join(termos) if termos else "1" return f"{y} ~ {rhs}" if st.button("Ajustar modelo"): # Pré-seleção de colunas necessárias cols_necessarias = ['SalePrice'] + feats_num + feats_cat if interagir and inter_1 and inter_2: cols_necessarias += [inter_1, inter_2] df_modelo = dados_filtrados[cols_necessarias].dropna().copy() if df_modelo.empty: st.error("Sem dados suficientes após remoção de NAs nas variáveis selecionadas.") else: y_col = 'SalePrice' if usar_logy: df_modelo['SalePrice'] = np.log(df_modelo['SalePrice'].astype(float)) y_col = 'SalePrice' formula = construir_formula(y_col, feats_num, feats_cat, inter_1 if interagir else None, inter_2 if interagir else None) # Split treino/teste df_treino, df_teste = train_test_split(df_modelo, test_size=teste_size, random_state=42) # Ajuste usando a fórmula (statsmodels cuida de dummies de C()) model = ols(formula, data=df_treino).fit() st.markdown("#### Especificação do Modelo") st.code(formula) st.markdown("#### Coeficientes e Inferência") st.dataframe(model.summary2().tables[1]) # Interpretação simples (sinal e significância) sig = model.pvalues < alpha_reg interpretacoes = [] for nome, beta in model.params.items(): if nome == 'Intercept': continue tag = "significativo" if sig.get(nome, False) else "não significativo" direcao = "aumenta" if beta > 0 else "reduz" interpretacoes.append(f"• {nome}: {tag}; coef. {beta:.3f} ⇒ {direcao} o preço esperado, ceteris paribus.") if interpretacoes: st.markdown("**Leitura rápida dos coeficientes:**\n" + "\n".join(interpretacoes)) # Predição e métricas y_true = df_teste['SalePrice'] y_pred = model.predict(df_teste) if usar_logy: # volta à escala original y_true_exp = np.exp(y_true) y_pred_exp = np.exp(y_pred) R2 = r2_score(y_true_exp, y_pred_exp) RMSE = mean_squared_error(y_true_exp, y_pred_exp, squared=False) MAE = mean_absolute_error(y_true_exp, y_pred_exp) else: R2 = r2_score(y_true, y_pred) RMSE = mean_squared_error(y_true, y_pred, squared=False) MAE = mean_absolute_error(y_true, y_pred) met_df = pd.DataFrame({ 'Métrica': ['R²', 'RMSE', 'MAE'], 'Valor': [R2, RMSE, MAE] }) st.markdown("#### Avaliação do Modelo (holdout)") st.dataframe(met_df) # Resíduos para pressupostos residuos = model.resid fitted = model.fittedvalues cols = st.columns(3) with cols[0]: st.markdown("**Resíduos vs Ajustados** (homocedasticidade)") fig_r, ax_r = plt.subplots(figsize=(4,3)) ax_r.scatter(fitted, residuos, alpha=0.5) ax_r.axhline(0, color='red', linestyle='--') ax_r.set_xlabel('Ajustados') ax_r.set_ylabel('Resíduos') st.pyplot(fig_r) with cols[1]: st.markdown("**QQ-Plot** (normalidade)") fig_q = sm.qqplot(residuos, line='45', fit=True) st.pyplot(fig_q) with cols[2]: st.markdown("**Histograma dos Resíduos**") fig_h, ax_h = plt.subplots(figsize=(4,3)) sns.histplot(residuos, kde=True, ax=ax_h) st.pyplot(fig_h) # Testes formais — Normalidade (Shapiro) e Homocedasticidade (Breusch-Pagan) sh_stat, sh_p = shapiro(residuos) bp_stat, bp_p, _, _ = het_breuschpagan(residuos, model.model.exog) st.markdown("#### Testes de Pressupostos (Regressão)") 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")) st.write(f"Breusch-Pagan: estatística={bp_stat:.3f}, p={bp_p:.3f} → " + ("homocedasticidade OK" if bp_p>=alpha_reg else "heterocedasticidade detectada")) # Multicolinearidade — VIF somente para variáveis numéricas (dummies já expandidas pelo modelo) try: design = model.model.exog nomes = model.model.exog_names vif_vals = [] for i in range(design.shape[1]): try: vif_vals.append(variance_inflation_factor(design, i)) except Exception: vif_vals.append(np.nan) vif_df = pd.DataFrame({'Variável': nomes, 'VIF': vif_vals}) st.markdown("#### VIF (Multicolinearidade)") st.dataframe(vif_df) except Exception as e: st.info(f"Não foi possível calcular VIF: {e}") # Orientações conforme pressupostos avisos = [] if sh_p < alpha_reg: avisos.append("Normalidade violada — considere log-transformar o alvo (marque a opção log) ou técnicas robustas.") if bp_p < alpha_reg: avisos.append("Heterocedasticidade — avalie transformação ou erros-padrão robustos (HC).") if (pd.Series(vif_vals).dropna() > 10).any(): avisos.append("VIF > 10 em alguma variável — possível multicolinearidade; reavalie/features ou regularização.") if avisos: st.warning("\n".join(avisos)) # Interação — destaque se foi significativa if interagir and inter_1 and inter_2: termo = f"C({inter_1}):C({inter_2})" if (inter_1 in feats_cat and inter_2 in feats_cat) else ( f"C({inter_1}):{inter_2}" if inter_1 in feats_cat else ( f"{inter_1}:C({inter_2})" if inter_2 in feats_cat else f"{inter_1}:{inter_2}")) if termo in model.pvalues: p_int = model.pvalues[termo] st.info(f"Interação {termo} — p={p_int:.4f} → " + ("**significativa**" if p_int < alpha_reg else "não significativa")) st.success("Modelagem concluída.")