File size: 11,757 Bytes
e0263f4
a3a8188
e0263f4
 
 
 
 
 
 
 
 
 
 
 
 
436a2b3
e0263f4
 
3f773f0
e0263f4
 
 
 
 
 
 
 
 
 
 
436a2b3
e0263f4
 
436a2b3
 
 
 
 
 
 
 
 
 
3a7f746
436a2b3
e0263f4
436a2b3
e0263f4
 
 
 
436a2b3
e0263f4
 
 
 
436a2b3
 
 
 
e0263f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d3f58dc
 
e0263f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3a8188
e0263f4
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# -*- 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.
""")