siep / src /streamlit_app.py
Pegumenezes's picture
Update src/streamlit_app.py
d3f58dc verified
# -*- 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.
""")