Spaces:
Sleeping
Sleeping
| # 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 | |
| # ----------------------------------------------------------- | |
| # 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.") |