# -*- coding: utf-8 -*- import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import statsmodels.api as sm import statsmodels.formula.api as smf from statsmodels.stats.anova import anova_lm from statsmodels.stats.outliers_influence import variance_inflation_factor from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score from scipy.stats import shapiro, levene, kruskal import kagglehub import os # --- Configuração da Página do Streamlit --- st.set_page_config(layout="wide", page_title="Análise de Precificação de Imóveis") # A linha st.set_option foi removida pois não é mais necessária. # --- Título e Introdução --- st.title("📊 Dashboard de Precificação Imobiliária") st.write(""" Esta análise utiliza o dataset *Ames Housing* para entender os fatores que mais influenciam o preço de venda dos imóveis. O dashboard está dividido em duas etapas principais: 1. **Análise de Variância (ANOVA):** Compara os preços médios entre diferentes categorias de imóveis. 2. **Regressão Linear Múltipla:** Cria modelos para prever o preço de venda e mede o impacto de cada característica. """) # --- Download e Carregamento do Dataset (com cache para performance) --- # --- Funções de Carregamento de Dados --- @st.cache_data def load_data(): """ Carrega o Ames Housing Dataset a partir de uma URL do GitHub para evitar problemas de permissão em ambientes de nuvem. """ url = "https://raw.githubusercontent.com/Viniciusalgueiro/Ameshousing/refs/heads/main/AmesHousing.csv" try: df = pd.read_csv(url) except Exception as e: st.error(f"Erro ao carregar os dados da URL: {e}") return None # Retorna None em caso de falha # --- Preparação dos dados (mesma lógica de antes) --- df.columns = df.columns.str.replace('[^A-Za-z0-9_]+', '', regex=True).str.lower() if 'grlivarea' in df.columns: bins = [0, 1000, 1500, 2000, 2500, 3000, 4000, df['grlivarea'].max() + 1] labels = ['<1000', '1000-1500', '1500-2000', '2000-2500', '2500-3000', '3000-4000', '4000+'] df['area_faixa'] = pd.cut(df['grlivarea'], bins=bins, labels=labels, include_lowest=True) return df df = load_data() # Adicione esta verificação para o caso de falha no download if df is None: st.stop() # Interrompe a execução do app se os dados não puderam ser carregados # Exibir uma amostra dos dados if st.checkbox("Mostrar amostra dos dados brutos"): st.write("Amostra dos dados carregados:", df.head()) # --- ETAPA I: ANÁLISE COM ANOVA --- st.header("ETAPA I: Análise de Variância (ANOVA)") st.write(""" Aqui, verificamos se existem diferenças estatisticamente significativas nos preços de venda com base em características categóricas dos imóveis. """) anova_vars = ['overallqual', 'fireplaces', 'area_faixa'] var_selecionada = st.selectbox("Selecione a variável para análise ANOVA:", anova_vars, index=0) if var_selecionada: st.subheader(f"🔎 Análise para '{var_selecionada}' vs Preço de Venda") # Preparação dos dados para a função df_anova = df[[var_selecionada, 'saleprice']].dropna() df_anova[var_selecionada] = df_anova[var_selecionada].astype('category') # Gráfico Boxplot para visualização fig, ax = plt.subplots(figsize=(10, 6)) sns.boxplot(x=var_selecionada, y='saleprice', data=df_anova, ax=ax) plt.title(f'Distribuição do Preço de Venda por "{var_selecionada}"', fontsize=16) plt.ylabel("Preço de Venda (SalePrice)") plt.xlabel(f"Categoria de '{var_selecionada}'") plt.xticks(rotation=45) st.pyplot(fig) # Análise estatística modelo_anova = smf.ols(f"saleprice ~ C({var_selecionada})", data=df_anova).fit() anova_table = anova_lm(modelo_anova, typ=2) residuos = modelo_anova.resid shapiro_stat, shapiro_p = shapiro(residuos) levene_stat, levene_p = levene(*[df_anova['saleprice'][df_anova[var_selecionada] == cat] for cat in df_anova[var_selecionada].unique()]) kruskal_stat, kruskal_p = kruskal(*[df_anova['saleprice'][df_anova[var_selecionada] == cat] for cat in df_anova[var_selecionada].unique()]) st.write("Resultados da ANOVA:") st.dataframe(anova_table) st.write("Verificação dos Pressupostos:") col1, col2, col3 = st.columns(3) col1.metric("Teste Shapiro-Wilk (Normalidade)", f"p={shapiro_p:.4f}", "Não Normal" if shapiro_p < 0.05 else "Normal") col2.metric("Teste Levene (Homocedasticidade)", f"p={levene_p:.4f}", "Heterocedástico" if levene_p < 0.05 else "Homocedástico") col3.metric("Teste Kruskal-Wallis (Alternativa)", f"p={kruskal_p:.4f}", "Diferença Significativa" if kruskal_p < 0.05 else "Sem Diferença") st.info(""" **Interpretação:** - O **p-valor da ANOVA (PR(>F))** indica se há diferença significativa entre os grupos. Se for baixo (< 0.05), pelo menos um grupo é diferente. - Como os pressupostos de normalidade e/ou homocedasticidade geralmente não são atendidos, olhamos para o **Teste de Kruskal-Wallis**. Um p-valor baixo aqui confirma que a variável analisada tem um impacto significativo no preço. """) # --- ETAPA II: REGRESSÃO LINEAR MÚLTIPLA --- st.header("ETAPA II: Regressão Linear Múltipla") st.write(""" Nesta etapa, construímos um modelo para prever o preço de venda com base em múltiplas variáveis e avaliamos sua performance e pressupostos. """) # --- Preparação dos dados para Regressão --- df_model = df[['saleprice', 'grlivarea', 'overallqual', 'garagecars', 'neighborhood', 'area_faixa']].dropna() df_dummies = pd.get_dummies(df_model, columns=['neighborhood', 'area_faixa'], drop_first=True) X = df_dummies.drop('saleprice', axis=1) y = df_dummies['saleprice'] X = sm.add_constant(X) X = X.select_dtypes(include=np.number) # --- Modelo 1: Regressão Linear Padrão --- st.subheader("Modelo 1: Regressão Linear Padrão") modelo = sm.OLS(y, X).fit() y_pred = modelo.predict(X) st.write("Métricas de Desempenho do Modelo Padrão:") r2 = r2_score(y, y_pred) rmse = np.sqrt(mean_squared_error(y, y_pred)) mae = mean_absolute_error(y, y_pred) col1, col2, col3 = st.columns(3) col1.metric("R² (R-squared)", f"{r2:.4f}") col2.metric("RMSE", f"${rmse:,.2f}") col3.metric("MAE", f"${mae:,.2f}") with st.expander("Ver Resumo Completo e Análise de Pressupostos do Modelo Padrão"): st.text(modelo.summary()) st.write("**Análise dos Resíduos**") fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) # Gráfico de Resíduos vs Ajustados sns.residplot(x=modelo.fittedvalues, y=modelo.resid, lowess=True, ax=ax1, line_kws={'color': 'red', 'lw': 2}) ax1.set_title("Resíduos vs. Valores Ajustados") ax1.set_xlabel("Valores Ajustados") ax1.set_ylabel("Resíduos") # Q-Q Plot sm.qqplot(modelo.resid, line='s', ax=ax2) ax2.set_title("Q-Q Plot dos Resíduos") st.pyplot(fig) st.warning("**Observação:** Note a falta de normalidade dos resíduos (pontos se desviam da linha vermelha no Q-Q Plot) e uma leve heterocedasticidade (o espalhamento dos resíduos não é constante). Isso sugere que uma transformação de variáveis pode melhorar o modelo.") # --- Modelo 2: Regressão Log-Log (Sugestão Implementada) --- st.subheader("Modelo 2: Regressão Log-Log (Modelo Aprimorado)") st.write(""" Para corrigir os problemas de normalidade e heterocedasticidade, aplicamos uma transformação logarítmica na variável de preço e nas variáveis contínuas. Os coeficientes deste modelo são interpretados como **elasticidades** (variações percentuais). """) # Preparação dos dados para o modelo log df_log = df_model.copy() df_log['log_saleprice'] = np.log(df_log['saleprice']) df_log['log_grlivarea'] = np.log(df_log['grlivarea']) df_dummies_log = pd.get_dummies(df_log, columns=['neighborhood', 'area_faixa'], drop_first=True) X_log = df_dummies_log.drop(['saleprice', 'log_saleprice', 'grlivarea'], axis=1) y_log = df_dummies_log['log_saleprice'] X_log = sm.add_constant(X_log) # Garante que todas as colunas em X_log são numéricas antes de modelar X_log = X_log.select_dtypes(include=np.number) modelo_log = sm.OLS(y_log, X_log).fit() st.write("Métricas de Desempenho do Modelo Log-Log:") col1, col2, _ = st.columns(3) col1.metric("R² (R-squared)", f"{modelo_log.rsquared:.4f}", f"{modelo_log.rsquared - r2:+.4f} vs Padrão") col2.metric("AIC (Critério de Akaike)", f"{modelo_log.aic:,.2f}", f"{modelo_log.aic - modelo.aic:,.2f} vs Padrão", help="Menor é melhor") with st.expander("Ver Resumo Completo e Análise de Pressupostos do Modelo Log-Log"): st.text(modelo_log.summary()) st.write("**Análise dos Resíduos (Modelo Log-Log)**") fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) # Gráfico de Resíduos vs Ajustados (Log) sns.residplot(x=modelo_log.fittedvalues, y=modelo_log.resid, lowess=True, ax=ax1, line_kws={'color': 'red', 'lw': 2}) ax1.set_title("Resíduos vs. Valores Ajustados (Log-Log)") ax1.set_xlabel("Valores Ajustados (Log)") ax1.set_ylabel("Resíduos (Log)") # Q-Q Plot (Log) sm.qqplot(modelo_log.resid, line='s', ax=ax2) ax2.set_title("Q-Q Plot dos Resíduos (Log-Log)") st.pyplot(fig) st.success("**Observação:** A transformação Log-Log melhorou significativamente a distribuição dos resíduos. Eles agora estão muito mais próximos da normalidade (pontos alinhados no Q-Q plot) e o padrão de funil (heterocedasticidade) foi reduzido.") # --- Conclusões e Recomendações --- st.header("🧠 Conclusões e Recomendações para Investidores") st.write(""" Com base no modelo Log-Log, que é estatisticamente mais robusto, podemos extrair os seguintes insights para a tomada de decisão: """) st.subheader("O que mais impacta no preço de um imóvel?") coef_qual = modelo_log.params['overallqual'] coef_area = modelo_log.params['log_grlivarea'] coef_garagem = modelo_log.params['garagecars'] st.markdown(f""" - **Qualidade Geral (`OverallQual`):** Esta é a variável de **maior impacto**. Cada ponto a mais na escala de qualidade (de 1 a 10) está associado a um aumento médio de **`{(np.exp(coef_qual) - 1) * 100:.1f}%`** no preço do imóvel. É um fator multiplicativo poderoso. - **Área Construída (`GrLivArea`):** O impacto é direto e significativo. Um aumento de **10%** na área construída está associado a um aumento de aproximadamente **`{coef_area * 10:.1f}%`** no preço de venda. - **Vagas na Garagem (`GarageCars`):** Também é muito relevante. Cada vaga de garagem adicional aumenta o preço do imóvel em cerca de **`{(np.exp(coef_garagem) - 1) * 100:.1f}%`**. - **Localização (`Neighborhood`):** Embora os coeficientes individuais não estejam detalhados aqui, o modelo confirma que o bairro é um fator crítico para a precificação. """) st.subheader("Recomendações Práticas") st.success(""" - **Foque em Qualidade:** A recomendação mais forte é priorizar imóveis com **alta qualidade de construção e acabamento**. O retorno sobre este investimento é exponencialmente maior do que outros fatores. - **Invista em Garagens:** Para imóveis com espaço, adicionar ou ampliar uma garagem é um investimento com retorno claro e significativo. Passar de 1 para 2 vagas tem um impacto enorme no valor. - **Ampliações são Rentáveis:** Aumentar a área construída tem um impacto positivo e previsível. No entanto, o retorno é percentualmente menor em comparação com melhorias na qualidade. - **Decida com Confiança:** Use estes fatores — **Qualidade, Garagem, Área e Bairro** — como pilares para justificar preços, avaliar oportunidades de investimento e orientar clientes. """)