# Requisitos: # pip install streamlit pandas numpy scipy statsmodels scikit-posthocs matplotlib seaborn import warnings warnings.filterwarnings("ignore") import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats import statsmodels.api as sm from statsmodels.formula.api import ols from statsmodels.stats.multicomp import pairwise_tukeyhsd from statsmodels.stats.oneway import anova_oneway # Necessário para Welch ANOVA import scikit_posthocs as sp # Necessário para Dunn Post-hoc # 1. Configuração da Página e Título st.set_page_config(layout="wide") st.title("🏡 Análise Comparativa de Preços de Imóveis (Ames Housing)") st.markdown(""" Dashboard interativo para explorar a relação entre variáveis categóricas (Bairro, Qualidade, Lareiras) e o **SalePrice** (Preço de Venda) usando Análise de Variância (ANOVA) e testes não-paramétricos. """) # 2. Definição das Colunas/Caminhos Y_COL = "SalePrice" CAT_COLS = ["Neighborhood", "OverallQual", "Fireplaces"] # ATENÇÃO: Substitua o caminho abaixo pelo caminho correto do seu arquivo CSV DATA_PATH = r"C:\Users\felip\Downloads\AmesHousing.csv.csv" # ----------------------------------------------------------- # 1) Carregar e Pré-processar Dataset com Caching # ----------------------------------------------------------- @st.cache_data # Crucial para performance! def load_and_preprocess_data(file_path, y_col, cat_cols): """Carrega, pré-processa e retorna o DataFrame.""" try: df = pd.read_csv(file_path, low_memory=False) except FileNotFoundError: st.error(f"Erro: Arquivo não encontrado no caminho: {file_path}") return pd.DataFrame() # Seu código original de pré-processamento if "Id" in df.columns: df = df.drop(columns=["Id"]) # Verifica a existência das colunas missing_cols = [c for c in [y_col] + cat_cols if c not in df.columns] if missing_cols: st.error(f"Colunas esperadas não encontradas no CSV: {', '.join(missing_cols)}") return pd.DataFrame() # Seleção de colunas, remoção de NA e conversão de tipos df = df[[y_col] + cat_cols].copy() df = df.dropna(subset=[y_col] + cat_cols) df["Neighborhood"] = df["Neighborhood"].astype(str) df["Fireplaces"] = df["Fireplaces"].astype(str) df["OverallQual"] = df["OverallQual"].astype(str) # Tratar como string para ANOVA return df # --- Chamada da função de carregamento --- df = load_and_preprocess_data(DATA_PATH, Y_COL, CAT_COLS) if df.empty: st.stop() # ----------------------------------------------------------- # 3) Funções auxiliares de teste (Mantidas) # ----------------------------------------------------------- def check_normality_by_group(data, group_col, value_col, alpha=0.05): """Shapiro-Wilk por grupo; retorna DataFrame com p-values e decisão""" results = [] groups = data[group_col].unique() for g in sorted(groups): vals = data.loc[data[group_col]==g, value_col].values # Shapiro requer n between 3 and 5000 if len(vals) < 3: p = np.nan stat = np.nan decision = "n<3" else: if len(vals) > 5000: vals_sample = np.random.choice(vals, 5000, replace=False) else: vals_sample = vals stat, p = stats.shapiro(vals_sample) decision = "normal" if p > alpha else "not_normal" results.append({"group": g, "n": len(vals), "shapiro_stat": stat, "shapiro_p": p, "decision": decision}) return pd.DataFrame(results) def levene_test(data, group_col, value_col): """Levene test para homocedasticidade.""" groups = [data.loc[data[group_col]==g, value_col].values for g in data[group_col].unique()] stat, p = stats.levene(*groups, center='median') return stat, p def anderson_darling_overall(data, value_col): """Anderson-Darling test para normalidade global.""" res = stats.anderson(data[value_col].values, dist='norm') return res # ------------------------------------------------------------------ # 4. Lógica Principal: Interatividade e Análise # ------------------------------------------------------------------ # 4.1. Seletor de Variável na Sidebar (Interatividade) st.sidebar.header("Variável a Analisar") selected_cat = st.sidebar.selectbox( "Selecione a Variável Categórica:", options=CAT_COLS, help="Escolha qual variável de agrupamento será analisada em relação ao SalePrice." ) # Definir as variáveis para a análise (substitui o loop for) cat = selected_cat y_col = Y_COL st.header(f"Análise: {cat} vs {y_col}") st.markdown("---") # Boxplot st.subheader("1. Visualização: Boxplot") st.markdown(f"Distribuição de `{y_col}` por categoria em `{cat}` (ordenado pela mediana).") # Garante que o gráfico seja desenhado em uma nova figura para o Streamlit fig, ax = plt.subplots(figsize=(10, 6)) order = df.groupby(cat)[y_col].median().sort_values(ascending=False).index sns.boxplot(x=cat, y=y_col, data=df, order=order, ax=ax) ax.set_title(f"{y_col} por {cat}") plt.xticks(rotation=45) plt.tight_layout() st.pyplot(fig) # Comando Streamlit para exibir o gráfico # Testes de pressupostos st.subheader("2. Testes de Pressupostos (Homogeneidade e Normalidade)") # Normalidade st.markdown("##### Teste de Normalidade (Shapiro-Wilk) por Grupo") sh = check_normality_by_group(df, cat, y_col) st.dataframe(sh.head(20), use_container_width=True) # Anderson-Darling (Global) ad = anderson_darling_overall(df, y_col) st.info(f"**Anderson-Darling (Global):** stat={ad.statistic:.4f}. Valores críticos: {ad.critical_values} (Níveis de Significância: {ad.significance_level}%)") # Homocedasticidade (Levene) st.markdown("##### Teste de Homocedasticidade (Levene)") stat_levene, p_levene = levene_test(df, cat, y_col) st.write(f"**Levene test (center=median):** Estatística={stat_levene:.4f}, p-valor={p_levene:.4f}") # -------------------------------------------------------------------- # 3. Análise Estatística (ANOVA / Welch / Kruskal) # -------------------------------------------------------------------- st.subheader("3. Análise Estatística e Post-Hoc") # ANOVA clássica (one-way) try: model = ols(f"{y_col} ~ C({cat})", data=df).fit() anova_table = sm.stats.anova_lm(model, typ=2) st.markdown("##### ANOVA (Type II) results:") st.dataframe(anova_table) anova_p = anova_table.loc[f"C({cat})", "PR(>F)"] if f"C({cat})" in anova_table.index else anova_table.iloc[0,3] except Exception as e: st.error(f"Erro ao rodar ANOVA clássica: {e}") anova_p = 1.0 # Define um p-valor alto para não rodar o Tukey # Lógica de Testes Robustos / Não-Paramétricos non_normal_groups = sh[sh["decision"]=="not_normal"] prop_non_normal = len(non_normal_groups) / len(sh) col1, col2 = st.columns(2) with col1: # Teste de Homocedasticidade if p_levene < 0.05: st.warning("⚠️ **Alerta:** Levene $p < 0.05$ (Variâncias Heterogêneas). Aplicando **Welch ANOVA**.") try: welch_res = anova_oneway(df[y_col], df[cat], use_var='unequal') st.code(f"Welch ANOVA: F={welch_res.fvalue:.4f}, p={welch_res.pvalue:.4f}") except Exception as e: st.error(f"Erro ao rodar anova_oneway: {e}") else: st.success("✅ **Pressuposto OK:** Variâncias homogêneas (Levene $p \ge 0.05$).") with col2: # Teste de Normalidade / Kruskal-Wallis if prop_non_normal > 0.3: st.warning(f"⚠️ **Alerta:** {prop_non_normal:.0%} dos grupos rejeitam normalidade. Aplicando **Kruskal-Wallis** (Teste não-paramétrico).") groups = [df.loc[df[cat]==g, y_col].values for g in df[cat].unique()] try: kw_stat, kw_p = stats.kruskal(*groups) st.code(f"Kruskal-Wallis: Stat={kw_stat:.4f}, p={kw_p:.6f}") if kw_p < 0.05: st.info("Kruskal-Wallis indica diferença significativa.") # Post-hoc Dunn st.markdown("###### Post-hoc Dunn (Bonferroni)") dunn = sp.posthoc_dunn(df, val_col=y_col, group_col=cat, p_adjust='bonferroni') st.dataframe(dunn, use_container_width=True) except Exception as e: st.error(f"Erro ao rodar Kruskal-Wallis: {e}") else: st.success(f"✅ **Pressuposto OK:** Baixa rejeição de normalidade ({prop_non_normal:.0%}).") # Teste Post-hoc Tukey (se ANOVA clássica for significativa) if anova_p < 0.05: st.markdown("##### Post-hoc Tukey HSD (Se ANOVA Signif.)") st.info("ANOVA clássica indica diferenças. Aplicando Tukey HSD.") tukey = pairwise_tukeyhsd(endog=df[y_col], groups=df[cat], alpha=0.05) st.text(tukey.summary()) # st.text é melhor para summaries de modelos else: st.info("ANOVA NÃO indicou diferenças estatisticamente significativas entre categorias.") # -------------------------------------------------------------------- # 4. Resumo Final # -------------------------------------------------------------------- st.subheader("4. Resumo por Categoria") st.markdown("Medidas descritivas do preço de venda agrupadas pela categoria selecionada, ordenadas pela média.") summary = df.groupby(cat)[y_col].agg(['count','mean','median','std']).sort_values('mean', ascending=False) st.dataframe(summary, use_container_width=True) st.markdown("---") st.success("Análise concluída. Utilize o seletor na barra lateral para trocar de variável.")