UNB logo

Programa de Pós-graduação em Computação Aplicada – PPCA (UnB)

Análise Estatística de Dados e Informações - Prova Final

Professor: João Gabriel de Moraes Souza
Aluno: Angelo Donizete Buso Júnior
In [1]:
import warnings
warnings.filterwarnings("ignore")

# Manipulação de Dados
import pandas as pd
from IPython.display import display
import numpy as np

# Visualização de Dados
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

# Estatística e Testes Estatísticos
import shap
from scipy import stats
from scipy.stats import kurtosis, skew, f_oneway, shapiro, levene, kruskal
import statsmodels.api as sm
from statsmodels.formula.api import ols
from statsmodels.stats.outliers_influence import variance_inflation_factor

# Pré-Processamento de Dados
from sklearn.model_selection import KFold, cross_validate
from sklearn.preprocessing import StandardScaler, LabelEncoder, MinMaxScaler, OneHotEncoder
from imblearn.under_sampling import RandomUnderSampler
from imblearn.over_sampling import SMOTE
from collections import Counter

# Algoritmos de Machine Learning
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.svm import SVC

# Treinamento e Validação de Modelos
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, StratifiedKFold

# Avaliação de Modelos
from sklearn.metrics import (
    mean_squared_error, mean_absolute_error, r2_score,
    confusion_matrix, accuracy_score, precision_score, recall_score,
    classification_report, roc_auc_score, roc_curve, ConfusionMatrixDisplay
)
from scikitplot.metrics import plot_confusion_matrix, plot_roc

# Configurações Opcionais
pd.set_option('display.float_format', '{:.2f}'.format)
sns.set_theme(style="whitegrid")
plt.rc("figure", figsize=(10, 6))

# Verificação de Versões
print("Versão do Seaborn:", sns.__version__)
print("Versão do Pandas:", pd.__version__)
Versão do Seaborn: 0.13.2
Versão do Pandas: 2.2.3

Questão 1¶

1. Compreensão do Problema de Negócio¶

O mercado imobiliário é um setor altamente competitivo, onde a precificação correta dos imóveis desempenha um papel crucial na tomada de decisões de compradores, vendedores e investidores. A utilização de modelos preditivos permite estimar os preços das propriedades com base em características estruturais e de localização, oferecendo uma vantagem estratégica para os envolvidos no setor.

Neste estudo, exploraremos os fatores que influenciam os preços de venda de imóveis na região de King County, nos Estados Unidos. Utilizaremos a Regressão Linear como técnica principal para prever os preços das propriedades e identificar as variáveis que mais impactam a valorização imobiliária. Para garantir uma abordagem estruturada e metodologicamente sólida, aplicaremos a metodologia CRISP-DM (Cross-Industry Standard Process for Data Mining), um framework amplamente reconhecido em projetos de ciência de dados.

O CRISP-DM é composto por seis etapas principais:

  1. Compreensão do Problema de Negócio: Definir os objetivos e requisitos do projeto.
  2. Compreensão dos Dados: Explorar, descrever e identificar padrões no conjunto de dados.
  3. Preparação dos Dados: Limpar, transformar e selecionar variáveis relevantes.
  4. Modelagem: Construir e ajustar modelos preditivos.
  5. Avaliação: Validar se os modelos atendem aos objetivos do negócio.
  6. Implementação: Integrar os resultados para apoio à tomada de decisão.

Para esta análise, utilizamos o conjunto de dados House Sales in King County, USA, disponível no Kaggle. O dataset contém informações sobre imóveis vendidos entre maio de 2014 e maio de 2015 na região de King County, incluindo Seattle. As variáveis incluem características como número de quartos, área do terreno, presença de vista para o mar, idade da construção, entre outras.

Além da construção do modelo preditivo, realizaremos uma análise exploratória e testes estatísticos para avaliar se os pressupostos da Regressão Linear (como linearidade, normalidade dos resíduos e homocedasticidade) são atendidos. Dessa forma, este estudo visa não apenas prever preços de imóveis, mas também gerar insights interpretáveis sobre os principais fatores que influenciam a precificação no mercado imobiliário. Adicionalmente, busca-se demonstrar os desafios inerentes ao desenvolvimento de modelos de Machine Learning, uma vez que a obtenção de um modelo ideal pode demandar diversas iterações e refinamentos ao longo do processo.

1.1 Dicionário dados¶

Abaixo transcrevemos o significado de cada um dos colunas do dataset.

  • id: Identificador único de cada imóvel.
  • date: Data da venda do imóvel.
  • price: Preço pelo qual o imóvel foi vendido. **Variável *Target***
  • bedrooms: Número de quartos no imóvel.
  • bathrooms: Número de banheiros no imóvel.
  • sqft_living: Área habitável em pés quadrados.
  • sqft_lot: Tamanho total do lote em pés quadrados.
  • floors: Número de andares do imóvel.
  • waterfront: Indica se o imóvel tem vista para a água (1) ou não (0).
  • view: Índice de 0 a 4 que classifica a qualidade da vista do imóvel.
  • condition: Classificação da condição atual do imóvel, variando de 1 a 5.
  • grade: Classificação da qualidade da construção e design do imóvel, variando de 1 a 13.
  • sqft_above: Área em pés quadrados acima do solo.
  • sqft_basement: Área em pés quadrados do porão.
  • yr_built: Ano em que o imóvel foi construído.
  • yr_renovated: Ano em que o imóvel foi reformado pela última vez.
  • zipcode: Código postal onde o imóvel está localizado.
  • lat: Latitude da localização do imóvel.
  • long: Longitude da localização do imóvel.
  • sqft_living15: Área habitável média dos 15 imóveis mais próximos, em pés quadrados.
  • sqft_lot15: Tamanho médio do lote dos 15 imóveis mais próximos, em pés quadrados.

2. Coleta Dados¶

In [2]:
path = "/home/buso/mestrado/aedi-ppca/dados/kc_house_data.csv"

2.1 Carga dados¶

In [3]:
data = pd.read_csv(path)
data.head()
Out[3]:
id date price bedrooms bathrooms sqft_living sqft_lot floors waterfront view ... grade sqft_above sqft_basement yr_built yr_renovated zipcode lat long sqft_living15 sqft_lot15
0 7129300520 20141013T000000 221900.00 3 1.00 1180 5650 1.00 0 0 ... 7 1180 0 1955 0 98178 47.51 -122.26 1340 5650
1 6414100192 20141209T000000 538000.00 3 2.25 2570 7242 2.00 0 0 ... 7 2170 400 1951 1991 98125 47.72 -122.32 1690 7639
2 5631500400 20150225T000000 180000.00 2 1.00 770 10000 1.00 0 0 ... 6 770 0 1933 0 98028 47.74 -122.23 2720 8062
3 2487200875 20141209T000000 604000.00 4 3.00 1960 5000 1.00 0 0 ... 7 1050 910 1965 0 98136 47.52 -122.39 1360 5000
4 1954400510 20150218T000000 510000.00 3 2.00 1680 8080 1.00 0 0 ... 8 1680 0 1987 0 98074 47.62 -122.05 1800 7503

5 rows × 21 columns

3. Análise Exploratória¶

3.1 Visão Geral dos dados¶

A análise exploratória de dados (EDA) é um passo essencial em projetos de ciência de dados, pois permite uma melhor compreensão das características, distribuições e possíveis anomalias nos dados. Este processo não apenas ajuda a identificar padrões relevantes, mas também fornece uma base sólida para a seleção de variáveis e construção de modelos preditivos. Ao explorar os dados, é possível descobrir relações implícitas entre as variáveis e verificar se os dados disponíveis são suficientes e adequados para atingir os objetivos do estudo.

Ter uma consciência situacional dos dados permite identificar outliers, valores faltantes e distribuições inesperadas que podem impactar negativamente a modelagem. Por exemplo, a identificação de uma variável com alta variância ou multicolinearidade é fundamental para evitar problemas como a instabilidade do modelo. Além disso, a EDA auxilia na escolha de transformações apropriadas para as variáveis, ajustando os dados para atender aos pressupostos do modelo. Este passo inicial é crucial para garantir a validade e a confiabilidade das análises subsequentes, além de direcionar melhor os esforços do projeto.

In [4]:
def visaogeral(df, messagem):
    print(f'{messagem}:\n')
    print("Qtd Observações:", df.shape[0])
    print("\nQtd Atributos:", df.shape[1])
    print("\nAtributos:")
    print(df.columns.tolist())
    print("\nQtd Valores missing:", df.isnull().sum().values.sum())
    print("\nValores Unicos: indicativo de valores categóricos")
    print(df.nunique().sort_values(ascending=True).head(40))
In [5]:
visaogeral(data,'Visão Geral dataSet treino')
Visão Geral dataSet treino:

Qtd Observações: 21613

Qtd Atributos: 21

Atributos:
['id', 'date', 'price', 'bedrooms', 'bathrooms', 'sqft_living', 'sqft_lot', 'floors', 'waterfront', 'view', 'condition', 'grade', 'sqft_above', 'sqft_basement', 'yr_built', 'yr_renovated', 'zipcode', 'lat', 'long', 'sqft_living15', 'sqft_lot15']

Qtd Valores missing: 0

Valores Unicos: indicativo de valores categóricos
waterfront           2
view                 5
condition            5
floors               6
grade               12
bedrooms            13
bathrooms           30
zipcode             70
yr_renovated        70
yr_built           116
sqft_basement      306
date               372
long               752
sqft_living15      777
sqft_above         946
sqft_living       1038
price             4028
lat               5034
sqft_lot15        8689
sqft_lot          9782
id               21436
dtype: int64
In [6]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 21613 entries, 0 to 21612
Data columns (total 21 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   id             21613 non-null  int64  
 1   date           21613 non-null  object 
 2   price          21613 non-null  float64
 3   bedrooms       21613 non-null  int64  
 4   bathrooms      21613 non-null  float64
 5   sqft_living    21613 non-null  int64  
 6   sqft_lot       21613 non-null  int64  
 7   floors         21613 non-null  float64
 8   waterfront     21613 non-null  int64  
 9   view           21613 non-null  int64  
 10  condition      21613 non-null  int64  
 11  grade          21613 non-null  int64  
 12  sqft_above     21613 non-null  int64  
 13  sqft_basement  21613 non-null  int64  
 14  yr_built       21613 non-null  int64  
 15  yr_renovated   21613 non-null  int64  
 16  zipcode        21613 non-null  int64  
 17  lat            21613 non-null  float64
 18  long           21613 non-null  float64
 19  sqft_living15  21613 non-null  int64  
 20  sqft_lot15     21613 non-null  int64  
dtypes: float64(5), int64(15), object(1)
memory usage: 3.5+ MB

A análise exploratória das características do conjunto de dados é uma etapa fundamental para compreender sua estrutura, identificar padrões e detectar potenciais desafios, como a presença de valores ausentes ou a necessidade de transformação de variáveis categóricas. Esses aspectos são essenciais para a construção de um modelo preditivo robusto e confiável.

O dataset utilizado contém aproximadamente 21,6 mil observações e 21 atributos, que representam diversas características das propriedades imobiliárias em King County, Washington (EUA). Durante a inspeção inicial, não foram identificados valores ausentes. No entanto, é importante destacar que operações de transformação e engenharia de features podem gerar dados faltantes ao longo do pipeline. Portanto, é crucial monitorar e mitigar possíveis impactos desses possíveis dados faltantes, no intuito de preservar a qualidade do modelo.

Além disso, algumas variáveis apresentam poucos valores únicos, como waterfront, view, condition, floors e grade, indicando um comportamento categórico, apesar de estarem representadas numericamente. Esse fator exige atenção especial na modelagem, pois pode demandar estratégias de tratamento como variáveis ordinais, dependendo da natureza dos dados.

A variável date foi carregada do tipo object, muito embora deveria estar no formado de data, há um indício que teremos que transformar essa variável em seu formato para realizar algumas anaálises.

Essa análise preliminar fornece insights estratégicos que orientam as próximas etapas de preparação, transformação e modelagem dos dados, garantindo um fluxo de trabalho mais eficiente e direcionado à melhoria do desempenho preditivo.

3.1.1 Variáveis Explanatórias e Variável Dependente¶

Em modelos de Machine Learning supervisionado, as variáveis são classificadas em variáveis explanatórias (ou independentes) e variável dependente (ou alvo). As variáveis explanatórias representam os fatores utilizados para prever ou explicar a variável dependente, que é o resultado que se deseja modelar. No caso de um modelo aplicado à previsão de preços de imóveis, as variáveis explanatórias incluem atributos como tamanho do imóvel, número de quartos, localização e idade da construção, enquanto a variável dependente é o preço de venda. A escolha e o tratamento adequado dessas variáveis são essenciais para garantir a qualidade preditiva e interpretabilidade do modelo.

In [7]:
data.columns
Out[7]:
Index(['id', 'date', 'price', 'bedrooms', 'bathrooms', 'sqft_living',
       'sqft_lot', 'floors', 'waterfront', 'view', 'condition', 'grade',
       'sqft_above', 'sqft_basement', 'yr_built', 'yr_renovated', 'zipcode',
       'lat', 'long', 'sqft_living15', 'sqft_lot15'],
      dtype='object')
In [8]:
X = data.drop("price", axis=1)
y = data["price"]

3.1.2 Missing Values¶

In [9]:
def missing_values_table(df):
    mis_val = df.isnull().sum()        
    mis_val_percent = 100 * df.isnull().sum() / len(df)        
    mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)        
    mis_val_table_ren_columns = mis_val_table.rename(
    columns = {0 : 'Missing Values', 1 : '% of Total Values'})        
    mis_val_table_ren_columns = mis_val_table_ren_columns[
    mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(
    '% of Total Values', ascending=False).round(1)
    
    print ("Seu dataframe tem " + str(df.shape[1]) + " colunas.\n"      
        "Há " + str(mis_val_table_ren_columns.shape[0]) +
            " colunas que possuem valores ausentes.")
    
    return mis_val_table_ren_columns
In [10]:
y.isnull().sum()
Out[10]:
0

A varíavel dependente não possui nenhum valor missing!

In [11]:
missing_values_table(X)
Seu dataframe tem 20 colunas.
Há 0 colunas que possuem valores ausentes.
Out[11]:
Missing Values % of Total Values

Embora o conjunto de dados inicial não apresente valores ausentes, é fundamental considerar que transformações e engenharia de features podem introduzir dados faltantes ao longo do pipeline. Assim, é essencial monitorar e tratar essas ocorrências para garantir a integridade e a qualidade do modelo preditivo.

3.1.3 Análise Variável Dependente¶

In [12]:
y.describe()
Out[12]:
count     21613.00
mean     540088.14
std      367127.20
min       75000.00
25%      321950.00
50%      450000.00
75%      645000.00
max     7700000.00
Name: price, dtype: float64

A variável dependente price representa o preço de venda dos imóveis e é a feature que queremos modelar e explicar. Com base no resumo estatístico, tem-se:

  • Média (mean): O preço médio das propriedades é de aproximadamente $540.088, refletindo o valor médio praticado na venda de caas nessa região de Washington.

  • Desvio Padrão (std): $367.127, indicando uma alta variabilidade nos preços, o que sugere a presença de imóveis significativamente mais caros ou mais baratos em relação à média.

  • Valores Mínimo e Máximo (min e max): O menor preço registrado foi de $75.000, enquanto o maior chegou a $7.700.000, mostrando uma grande amplitude nos valores das propriedades.

  • Quartis (25%, 50% e 75%):

    • 25% (primeiro quartil): 25% das propriedades têm preços abaixo de $321.950.
    • 50% (mediana): O preço mediano é $450.000, indicando que metade das propriedades custa mais que esse valor e que metade custa menos.
    • 75% (terceiro quartil): 25% das propriedades mais caras têm preços acima de $645.000, limitado a $7.700.000.

Esses dados mostram que os preços de venda têm possuem uma grande dispersão e provável assimetria, visto que a média é consideravelmente maior que a mediana, refletindo diferenças significativas nas características das propriedades. Essa variabilidade será explorada durante a modelagem para identificar quais fatores influenciam mais fortemente os preços.

In [13]:
y.quantile([0.80,0.90,0.93,0.94,0.99])
Out[13]:
0.80    700108.00
0.90    887000.00
0.93    998000.00
0.94   1063560.00
0.99   1964400.00
Name: price, dtype: float64

Conforme acima, podemos dizer que 93% das propriedades estão distribuídas entre o preço de venda de $75.000 e $998.000. E 7% ultrapassa os 6 digitos (milhões). Essa distribuição reflete que os valores mais altos representam propriedades de luxo ou com características muito diferenciadas não indicando uma região elitizada. Sugere ainda que dado o percentil 99 o valor máximo de venda observado foge muito dos padrões.

Sendo assim, os dados sugerem que os preços de venda têm uma distribuição assimétrica à direita, ou seja, há uma concentração maior de imóveis com preços mais baixos, refletindo diferenças significativas nas características das propriedades.

3.1.3.1 Amplitude¶
In [14]:
plt.figure(figsize=(10, 6))
plt.plot(np.sort(y), label="Preço Venda", color='blue')
plt.title("Amplitude de Preço Venda")
plt.ylabel("Preço de Venda")
plt.legend()

plt.xticks([])
plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image

Ordenando a variável price identificamos uma curva fortemente inclinada para cima no final, indicando que a maioria dos imóveis possui preços relativamente baixos e apenas uma pequena parcela apresenta valores extremamente altos. No final da curva, os valores disparam rapidamente, sugerindo a existência de imóveis de alto luxo que podem atuar como outliers, influenciando a média geral.

Essa característica pode afetar negativamente o desempenho do modelo de regressão linear, tornando necessário o uso de transformações (logaritmo do preço) ou a análise segmentada do mercado imobiliário.

3.1.3.2 Plot - boxplot¶
In [15]:
sns.set_style("whitegrid")
In [16]:
plt.figure(figsize=(10, 6))

sns.boxplot(y=y, color='blue')
plt.title("Boxplot do preço de venda")
plt.xlabel("Preço Venda")

plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image

O boxplot da variável alvo (preço de venda do imóvel) revela uma distribuição assimétrica à direita, com a maioria dos preços concentrados entre $321.950 e $645.000. A mediana dos preços é de aproximadamente $450.000, indicando que metade dos imóveis custa menos que esse valor.

A presença de outliers acima de $1.000.000, chegando até $7.700.000, sugere a existência de imóveis de alto padrão na região. Esses valores extremos podem impactar a modelagem, especialmente em técnicas sensíveis a outliers, como a Regressão Linear.

Para melhorar o desempenho do modelo, recomenda-se a aplicação de transformações (como logarítmica) na variável price ou a segmentação do mercado em categorias distintas (ex.: imóveis comuns vs. imóveis de luxo). Essas estratégias podem ajudar a normalizar a distribuição dos dados e aumentar a precisão das previsões.

3.1.3.3 Plots Variável Dependente¶

O QQ-Plot (Quantile-Quantile Plot) é uma ferramenta gráfica usada para verificar se os dados seguem uma distribuição normal. Ele compara os quantis dos dados observados com os quantis de uma distribuição teórica (normal, no caso).

In [17]:
plt.figure(figsize=(10, 6))
sm.qqplot(y, line='s', fit=True)

plt.title("QQ-Plot de SalePrice")

plt.tight_layout()
plt.show()
<Figure size 1000x600 with 0 Axes>
No description has been provided for this image

O QQ-Plot da variável price (preço de venda dos imóveis) revela que os dados não seguem uma distribuição normal. Os pontos se afastam da linha diagonal nas extremidades, especialmente na cauda direita, indicando uma assimetria positiva (cauda direita mais pesada). Esse comportamento é consistente com a presença de outliers e a distribuição assimétrica observada no boxplot.

A não normalidade dos dados pode impactar a validade do modelo de Regressão Linear, uma vez que um dos pressupostos dessa técnica é que os resíduos devem seguir uma distribuição normal. Para corrigir esse problema, recomenda-se a aplicação de uma transformação logarítmica na variável price, que ajudará a reduzir a assimetria e aproximar os dados de uma distribuição normal. Essa abordagem melhorará a qualidade do modelo e a confiabilidade das inferências estatísticas.

3.1.3.4 Distribuição Dados - skw/Kurt¶

A assimetria mede o quanto a distribuição dos dados é inclinada para a direita ou para a esquerda em relação à média.

A curtose mede a forma da distribuição em relação às suas caudas (se os valores extremos são mais ou menos frequentes do que em uma distribuição normal).

In [18]:
# Estatísticas descritivas adicionais
print("Skewness (Assimetria):", skew(y))
print("Kurtosis (Curtose):", kurtosis(y))
Skewness (Assimetria): 4.023789858140135
Kurtosis (Curtose): 34.577262255687536

A variável price apresenta uma assimetria (skewness) de 4,02, o que indica uma assimetria positiva forte. Isso significa que a distribuição dos preços dos imóveis tem uma cauda longa à direita, com muitos valores extremos acima da média. Além disso, a curtose (kurtosis) de 34,58 sugere uma distribuição leptocúrtica, com caudas extremamente pesadas e um pico muito agudo. Esses valores confirmam a presença de outliers e a necessidade de transformações para normalizar a distribuição, como a aplicação de uma transformação logarítmica.

3.1.3.5 Histograma Variável Dependente¶
In [19]:
plt.figure(figsize=(10, 6))
sns.histplot(y, kde=True, bins=30, color='blue')
plt.title("Histograma de preço de venda")
plt.xlabel("Preço de Venda")
plt.ylabel("Frequência")

plt.ticklabel_format(style='plain', axis='x')

plt.tight_layout()
plt.show()
No description has been provided for this image

O histograma confirma que a distribuição da variável preço de venda não é normal, apresentando alta assimetria à direita e curtose elevada. Esse comportamento reforça a necessidade de ajustes para garantir que os modelos preditivos tenham um desempenho mais robusto e interpretem corretamente a relação entre os atributos do imóvel e seu preço.

A presença de outliers pode impactar negativamente o modelo de Regressão Linear, tornando-o menos preciso. Para mitigar esse problema, recomenda-se a aplicação de uma transformação logarítmica na variável price, que ajudará a normalizar a distribuição e reduzir o impacto dos valores extremos. Além disso, a segmentação do mercado em categorias distintas (ex.: imóveis comuns vs. imóveis de luxo) pode ser uma estratégia eficaz para melhorar a precisão do modelo, abaixo demonstramos como a transformação logaritmica auxiliará nas premissas de normailidade dos dados, assumindo que usaremos regressão logistica.

In [20]:
log_price = np.log1p(y)  # log1p evita problemas com log(0)

# Criando o histograma
plt.figure(figsize=(10, 6))
sns.histplot(log_price, bins=20, kde=True, color='blue', alpha=0.6)
plt.xlabel("Log(Preço de Venda)")
plt.ylabel("Frequência")
plt.title("Histograma do Log do Preço de Venda")
plt.grid(True)
plt.show()
No description has been provided for this image

3.1.4 Análise da Variáveis Explanatórias¶

Uma variável independente X explica a variação em outra variável, que é chamada variável dependente y. Este relacionamento existe em apenas uma direção:

variável independente (X) -> variável dependente (y)

In [22]:
X.head()
Out[22]:
id date bedrooms bathrooms sqft_living sqft_lot floors waterfront view condition grade sqft_above sqft_basement yr_built yr_renovated zipcode lat long sqft_living15 sqft_lot15
0 7129300520 20141013T000000 3 1.00 1180 5650 1.00 0 0 3 7 1180 0 1955 0 98178 47.51 -122.26 1340 5650
1 6414100192 20141209T000000 3 2.25 2570 7242 2.00 0 0 3 7 2170 400 1951 1991 98125 47.72 -122.32 1690 7639
2 5631500400 20150225T000000 2 1.00 770 10000 1.00 0 0 3 6 770 0 1933 0 98028 47.74 -122.23 2720 8062
3 2487200875 20141209T000000 4 3.00 1960 5000 1.00 0 0 5 7 1050 910 1965 0 98136 47.52 -122.39 1360 5000
4 1954400510 20150218T000000 3 2.00 1680 8080 1.00 0 0 3 8 1680 0 1987 0 98074 47.62 -122.05 1800 7503

Para facilitar as análises gráficas das variáveis explanatóras, iremos categorizá-las em grupos temáticos, permitindo comparações mais intuitivas

3.1.4.1 Localização¶

Variáveis relacionadas à posição geográfica do imóvel:

  • zipcode: CEP
  • lat: Latitude
  • long: Longitude
In [23]:
localizacao = ['zipcode', 'lat', 'long', 'price']
df_localizacao = data[localizacao]
In [24]:
def atribuir_cor_preco(preco):
    if preco <= 500000:
        return 'green'  # Verde para preços até 500 mil
    elif preco <= 1000000:
        return 'yellow'  # Amarelo para preços entre 500 mil e 1 milhão
    elif preco <= 3000000: # Laranja para preços entre 1 milhão e 3 milhões
        return 'orange'
    elif preco <= 5000000: # Roxo para preços entre 3 milhões e 5 milhões
        return 'purple'
    else:
        return 'red'  # Vermelho para preços acima de 1 milhão
In [24]:
mapa = folium.Map(location=[df_localizacao['lat'].mean(), df_localizacao['long'].mean()], zoom_start=10)

for index, row in df_localizacao.iterrows():
    # Chama a função para obter a cor do preço
    cor = atribuir_cor_preco(row['price'])
    folium.CircleMarker(
        location=[row['lat'], row['long']],
        # tamanho do círculo
        radius=5,
        # define a cor dado o valor da linha
        color=cor,
        fill=True,
        fill_color=cor,
        fill_opacity=0.6,
        # exibe o preço no popup (quando clicar no círculo)
        popup=f"Preço: R${row['price']}"
    ).add_to(mapa)

mapa
Out[24]:
Make this Notebook Trusted to load map: File -> Trust Notebook

A maior parte dos pontos no mapa estão em verde e amarela, indicando que a maioria dos imóveis está na faixa de até $1.000.000, sugere que King County possui predominância de imóveis residenciais dentro dessa faixa de preço.

Há pontos laranja e roxo concentrados em algumas regiões específicas, indicando áreas com imóveis de alto padrão. Nota-se também que áreas mais próximas do Lake Washington e da infraestrutura urbana de Seattle e Bellevue apresentam mais pontos amarelos e laranjas, sugerindo que a localização/bairro exercem certa influencia diretamente os preços.

A casa com o maior preço vendida ($ 7.700.000,0) fica próximo ao Volunteer Park!
casa maior valor

3.1.4.2 Características Estruturais do Imóvel¶

Variáveis que descrevem os aspectos físicos da propriedade:

  • sqft_living: Área útil em pés²
  • sqft_lot: Área total do terreno em pés²
  • sqft_above: Área construída acima do solo
  • sqft_basement: Área do porão
  • floors: Número de andares
  • bedrooms: Número de quartos
  • bathrooms: Número de banheiros
In [25]:
estrutura_imovel = ['sqft_living', 'sqft_lot', 'sqft_above', 'sqft_basement', 'floors', 'bedrooms', 'bathrooms']

Os gráficos abaixo, permitirá visualizar as relações entre as variáveis explanatórias e a variável dependente price. A dispersão dos pontos fornece insights sobre a variação dos preços de venda em função de cada característica, enquanto a linha de regressão destaca a tendência geral da relação. Essa abordagem facilita a identificação de padrões, possíveis associações lineares, outliers e comportamentos não lineares que podem impactar a modelagem.

In [26]:
for feature in estrutura_imovel:
    plt.figure(figsize=(10, 6))
    sns.lineplot(x=X[feature], y=y, palette="viridis", label='Dados de dispersão')
    sns.regplot(x=X[feature], y=y, scatter=False, color='blue', label='Regressão Linear')

    plt.title(f"Relação entre {feature} e Preço Venda")

    plt.ticklabel_format(style='plain', axis='x')
    plt.ticklabel_format(style='plain', axis='y')
    plt.xlabel(feature)
    plt.ylabel("price")
    plt.legend()
    
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Variável Relação com Preço Insight
sqft_living Positiva Áreas úteis maiores aumentam o preço, mas com dispersão para imóveis grandes.
sqft_lot Fraca A área do terreno tem impacto limitado no preço, a menos que seja associada a outros fatores.
sqft_above Positiva forte Áreas construídas maiores acima do solo são preditores consistentes de preços mais altos.
sqft_basement Moderada Porões maiores agregam valor, mas o impacto depende de outros fatores.
floors Não linear Imóveis com 1,5 a 2,5 andares tendem a ser os mais valorizados.
bedrooms Não linear Imóveis com 3 a 5 quartos são os mais valorizados; mais quartos podem não agregar valor proporcional.
bathrooms Positiva Banheiros adicionais aumentam o preço, mas o impacto diminui após 5 banheiros.

Importe ressaltar que na base existem registros em que o registro de andares é valor decimal!

In [27]:
X['floors'].unique()
Out[27]:
array([1. , 2. , 1.5, 3. , 2.5, 3.5])
3.1.4.2.1 Correlação dos atributos estruturais¶
In [28]:
estrutura_imovel = ['sqft_living', 'sqft_lot', 'sqft_above', 'sqft_basement', 'floors', 'bedrooms', 'bathrooms', 'price']
data[estrutura_imovel].corr()
Out[28]:
sqft_living sqft_lot sqft_above sqft_basement floors bedrooms bathrooms price
sqft_living 1.00 0.17 0.88 0.44 0.35 0.58 0.75 0.70
sqft_lot 0.17 1.00 0.18 0.02 -0.01 0.03 0.09 0.09
sqft_above 0.88 0.18 1.00 -0.05 0.52 0.48 0.69 0.61
sqft_basement 0.44 0.02 -0.05 1.00 -0.25 0.30 0.28 0.32
floors 0.35 -0.01 0.52 -0.25 1.00 0.18 0.50 0.26
bedrooms 0.58 0.03 0.48 0.30 0.18 1.00 0.52 0.31
bathrooms 0.75 0.09 0.69 0.28 0.50 0.52 1.00 0.53
price 0.70 0.09 0.61 0.32 0.26 0.31 0.53 1.00
In [29]:
plt.figure(figsize=(12, 8))
correlation_matrix = data[estrutura_imovel].corr()
sns.heatmap(correlation_matrix, annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Matriz de Correlação - Variáveis Estruturais")
plt.show()
No description has been provided for this image

1️⃣ sqft_living (Área útil) → Correlação com variável target = 0,70. A variável mais correlacionada com o preço, quanto maior a área útil, maior o preço do imóvel.

2️⃣ bathrooms (Número de banheiros) → Correlação com variável target = 0,53. Impacto significativo no preço, casas com mais banheiros tendem a ter maior valorização. No entanto pode estar relacionada a sqft_living.

3️⃣ sqft_above (Área construída acima do solo) → Correlação com a variável target = 0,61. Possui uma alta correlação com sqft_living (0,88), podendo indicar multicolinearidade. Como sqft_living já inclui essa área, podemos testar remover essa variável e avaliar o impacto. Motivo -> evitar multicolinearidade.

4️⃣ bedrooms (Número de quartos) → Correlação com a variável target = 0,31. Menos relevante que sqft_living e bathrooms. Poderá ser mantida para interpretação, mas não é essencial.

5️⃣ floors (Número de andares) → Correlação com a variável target = 0,26. Impacto menor do que o esperado, conhecimento a priore era que quanto mais andares, mais espaço poderia ter a casa. Pode indicar que o número de andares não é um fator crítico na precificação.

6️⃣ sqft_basement (Área do porão) → Correlação com a variável target = 0,32. Impacto pequeno, mas pode ser interessante em áreas urbanas, para armazenamento.

7️⃣ sqft_lot (Área do terreno) → Correlação com a variável target = 0,09 muito baixa com o preço. O tamanho do terreno não é um grande determinante do valor do imóvel. Poderá ser descartada do modelo sem grande perda de informação.

3.1.4.3 Qualidade da Construção e Infraestrutura¶

Variáveis que indicam a condição e a qualidade do imóvel:

  • grade: Avaliação da construção (escala de 1 a 13)
  • condition: Estado geral da casa (escala de 1 a 5)
  • yr_built: Ano de construção
  • yr_renovated: Ano da última renovação

Aqui podemos identificar que 2 variáveis representam caracteristicas típicas de variáveis categóricas, sendo as features grade e condition. Vamos analisá-las separadamente. A descrição dessas features estão abaixo.

grade -> nota recebida referente a classificação da construção, representa a qualidade construtiva das melhorias.

  • 1-3: Fica aquém dos padrões mínimos de construção.

  • 4: Construção geralmente mais antiga e de baixa qualidade.

  • 5: Baixos custos de construção e mão de obra. Design pequeno e simples.

  • 6: Nota mais baixa que atende atualmente ao código de construção. Materiais de baixa qualidade e designs simples.

  • 7: Grau médio de construção e design.

  • 8: Um pouco acima da média em construção e design. Geralmente melhores materiais no trabalho de acabamento externo e interno.

  • 9: Melhor projeto arquitetônico com design e qualidade extra de interiores e exteriores.

  • 10: Casas dessa qualidade geralmente têm características de alta qualidade. O trabalho de acabamento é melhor e mais qualidade de design é vista nas plantas baixas. Geralmente tem uma metragem quadrada maior.

  • 11: Design personalizado e acabamento de alta qualidade funcionam com comodidades adicionais de madeiras maciças, louças sanitárias e opções mais luxuosas.

  • 12: Design personalizado e excelentes construtores. Todos os materiais são da mais alta qualidade e todas as conveniências estão presentes.

  • 13: Geralmente projetado e construído sob medida. Nível mansão. Grande quantidade de trabalho da mais alta qualidade, acabamento em madeira, mármore, entradas, etc.

condition => relação à idade e a nota recebida. Codificado 1-5.

  • 1 Pobre - Desgastado: Reparo e revisão necessários em superfícies pintadas, telhados, encanamentos, aquecimento e inúmeras inadequações funcionais. Manutenção e abuso diferidos excessivos, valor limitado em uso, abandono iminente ou grande reconstrução; A reutilização ou mudança na ocupação é iminente. A idade efetiva está próxima do final da escala, independentemente da idade cronológica real.

  • 2 Justo - Muito desgastado: Muito reparo necessário. Muitos itens precisam de retoque ou revisão, manutenção adiada óbvia, utilidade e sistemas de construção inadequados, todos encurtando a expectativa de vida e aumentando a idade efetiva.

  • 3 Média: Alguma evidência de manutenção adiada e obsolescência normal com a idade, pois alguns pequenos reparos são necessários, juntamente com alguns retoques. Todos os principais componentes ainda funcionais e contribuindo para uma expectativa de vida prolongada. A idade e a utilidade efetivas são padrão para propriedades semelhantes de sua classe e uso.

  • 4 Bom: Nenhuma manutenção óbvia necessária, mas nem tudo é novo. A aparência e a utilidade estão acima do padrão e a idade efetiva geral será menor do que a propriedade típica.

  • 5 Muito Bom: Todos os itens bem conservados, muitos tendo sido revisados e reparados por apresentarem sinais de desgaste, aumentando a expectativa de vida e diminuindo a idade efetiva com pouca deterioração ou obsolescência evidente com alto grau de utilidade.

In [30]:
qualidade_construcao = ['grade', 'condition']   
In [31]:
for feature in qualidade_construcao:
    plt.figure(figsize=(10, 6))
    sns.boxplot(x=data[feature], y=y, palette="viridis")

    plt.title(f"Relação entre {feature} e Preço Venda")
    plt.xlabel(feature)
    plt.ylabel("price")
    #plt.ticklabel_format(style='plain', axis='x')
    plt.ticklabel_format(style='plain', axis='y')
    
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image

A variável grade representa a qualidade da construção, variando de 1 a 13, onde 1 é de qualidade muito baixa e 13 indica um padrão de construção superior. Existe uma relação clara e crescente entre grade e price.

Imóveis com grade baixa (≤6) têm preços médios bem menores. A valorização é mais expressiva a partir do grade 8, onde os preços começam a crescer significativamente. Imóveis com grade ≥10 mostram grande dispersão, indicando que casas de alto padrão têm grande variação nos preços. Logo a featura grade é um fator que pode explicar o preço do imóvel.

A variável condition mede o estado físico do imóvel e varia de 1 (muito ruim) a 5 (excelente). A relação entre condition (estado de conservação) e price preço de venda, diferentemente de grade, não evidencia um padrão de valorização clara. A mediana dos preços é semelhante entre níveis 2 a 5. Além de que imóveis com condition = 5 não são muito mais caros do que os de condition entre 3 ou 4. Existe alta dispersão em todos os níveis, sugerindo que a condição do imóvel não é um grande fator de precificação isoladamente.

In [32]:
data.groupby('grade')['price'].describe()
Out[32]:
count mean std min 25% 50% 75% max
grade
1 1.00 142000.00 NaN 142000.00 142000.00 142000.00 142000.00 142000.00
3 3.00 205666.67 113517.99 75000.00 168500.00 262000.00 271000.00 280000.00
4 29.00 214381.03 94306.17 80000.00 145000.00 205000.00 265000.00 435000.00
5 242.00 248523.97 118100.28 78000.00 175000.00 228700.00 295750.00 795000.00
6 2038.00 301919.64 122970.28 82000.00 215037.50 275276.50 366837.50 1200000.00
7 8981.00 402590.26 155876.92 90000.00 285000.00 375000.00 485000.00 2050000.00
8 6068.00 542852.77 217473.37 140000.00 390000.00 510000.00 640000.00 3070000.00
9 2615.00 773513.19 316120.08 230000.00 571500.00 720000.00 880000.00 2700000.00
10 1134.00 1071771.07 483545.09 316000.00 768087.50 914327.00 1250000.00 3600000.00
11 399.00 1496841.73 705099.30 420000.00 1036000.00 1284000.00 1700000.00 7062500.00
12 90.00 2191222.00 1027818.63 835000.00 1500000.00 1817500.00 2668500.00 5350000.00
13 13.00 3709615.38 1859449.90 1780000.00 2415000.00 2983000.00 3800000.00 7700000.00
In [33]:
grade_counts = data.groupby('grade').size().reset_index(name='counts')

plt.figure(figsize=(10, 6))
sns.barplot(x='grade', y='counts', data=grade_counts)

plt.title('Quantidade de cada categoria de avaliação de qualidade')
plt.xlabel('Grade')
plt.ylabel('Quantidade')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [34]:
data.groupby('condition')['price'].describe()
Out[34]:
count mean std min 25% 50% 75% max
condition
1 30.00 334431.67 271172.80 78000.00 160000.00 262500.00 431125.00 1500000.00
2 172.00 327287.15 245418.41 80000.00 189750.00 279000.00 397300.00 2555000.00
3 14031.00 542012.58 364449.06 75000.00 329500.00 450000.00 640000.00 7062500.00
4 5679.00 521200.39 358516.23 89000.00 305000.00 440000.00 625000.00 7700000.00
5 1701.00 612418.09 410971.92 110000.00 350000.00 526000.00 725000.00 3650000.00
In [35]:
category_counts_agg = data.groupby(['grade', 'condition']).agg(
    mean_price=('price', 'mean'),
    count=('price', 'size')
    ).reset_index()
category_counts_agg
Out[35]:
grade condition mean_price count
0 1 1 142000.00 1
1 3 2 280000.00 1
2 3 3 75000.00 1
3 3 5 262000.00 1
4 4 1 150000.00 1
5 4 2 179600.00 5
6 4 3 210000.00 13
7 4 4 243905.00 10
8 5 1 280833.33 9
9 5 2 206166.67 15
10 5 3 246993.46 100
11 5 4 245299.01 84
12 5 5 271127.59 34
13 6 1 303813.64 11
14 6 2 269483.90 59
15 6 3 290651.85 1035
16 6 4 318731.57 685
17 6 5 310140.86 248
18 7 1 334416.67 6
19 7 2 342698.52 75
20 7 3 385666.79 5234
21 7 4 412923.07 2833
22 7 5 479667.75 833
23 8 1 932500.00 2
24 8 2 421996.15 13
25 8 3 510102.77 4269
26 8 4 589527.30 1394
27 8 5 736538.05 390
28 9 2 715000.00 2
29 9 3 726587.80 2041
30 9 4 889664.52 446
31 9 5 1123419.90 126
32 10 2 1752500.00 2
33 10 3 1012934.13 921
34 10 4 1176284.59 156
35 10 5 1735830.36 55
36 11 3 1414401.44 332
37 11 4 1839916.43 56
38 11 5 2238477.27 11
39 12 3 2138601.08 74
40 12 4 2467961.54 13
41 12 5 2290000.00 3
42 13 3 3338636.36 11
43 13 4 5750000.00 2
In [36]:
category_counts = data.groupby(['grade', 'condition']).size().unstack(fill_value=0)
palette = sns.light_palette("blue", n_colors=len(category_counts.columns))

category_counts.plot(kind='bar', stacked=True, figsize=(10, 6), color=palette)

plt.title('Quantidade de cada categoria de grade e condition')
plt.xlabel('Grade')
plt.ylabel('Quantidade')
plt.legend(title='Condition')

plt.tight_layout()
plt.show()
No description has been provided for this image

Observando por estratos a distribuição das features grade e condition, nota-se uma concentração certas categorias. A maioria dos imóveis tem grade entre 6 e 9, com destaque para grade 7, que domina a amostra. Pouquíssimos imóveis possuem grades extremas (1, 3, 4, 12 e 13).

A variável condition predomina em níveis intermediários, sendo 3 e 4 são as mais comuns em todas as faixas de grade. Condition 5 (excelente estado) aparece mais em imóveis de grade elevada (9+), mas em quantidade reduzida.

Imóveis com condition 1 e 2 são raros, indicando que a maioria das casas está em estado razoável ou bom. Na outra ponta, imóveis de alto padrão (grade > 10) também são pouco frequentes.

Poucos imóveis atingem os níveis mais altos de grade (10-13), o que explica a maior dispersão de preços observada nos boxplots anteriores. A maior parte dos imóveis com condition 5 está associada a grade superiores a 7, reforçando a correlação entre qualidade da construção e estado de conservação.

Vamos verificar as features relacionadas ao tempo que as construiçoes possuem, sendo a variável yr_built que representa o ano de construção e a feature yr_renovated o ano da última reforma.

In [37]:
idade_construcao = data[['yr_built', 'yr_renovated', 'price']]
In [38]:
plt.figure(figsize=(12,6))
sns.scatterplot(data=idade_construcao, x='yr_built', y='price', alpha=0.5)

plt.title("Relação entre Ano de Construção e Preço de Venda")
plt.xlabel("Ano de Construção")
plt.ylabel("Preço de Venda")
plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [39]:
idade_construcao['decade_built'] = (idade_construcao['yr_built'] // 10) * 10  # Criar faixas por década

plt.figure(figsize=(12,6))
sns.boxplot(data=idade_construcao, x='decade_built', y='price')

plt.xticks(rotation=45)
plt.title("Distribuição dos Preços por Década de Construção")
plt.xlabel("Década de Construção")
plt.ylabel("Preço de Venda")
plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [40]:
plt.figure(figsize=(12,6))
sns.scatterplot(data=idade_construcao[idade_construcao['yr_renovated'] > 0], x='yr_renovated', y='price', color='red', label="Reformados")
sns.scatterplot(data=idade_construcao, x='yr_built', y='price', alpha=0.5, label="Ano Construção")

plt.title("Ano de Construção x Ano de Reforma x Preço")
plt.xlabel("Ano")
plt.ylabel("Preço de Venda")
plt.legend()
plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [41]:
# idade_construcao['yr_renovated'].value_counts()
print(f'Apenas {data.shape[0] - data['yr_renovated'].loc[data['yr_renovated'] == 0].count()} imóveis foram reformados.')
Apenas 914 imóveis foram reformados.

Os imóveis foram construídos entre os anos 1900 a 2015. Não há um aumento claro do preço com a idade do imóvel, mas imóveis construídos após 2000 tendem a ter mais pontos em valores elevados. Observa-se que o ponto maior do preço foi um imóvel construído próximo da década de 1910, sugerindo assim que esse valor pode ser justificado por fatos históricos, o que precisa ser melhor verificado. Muito embora existem outliers com preços muito altos em diferentes períodos. Imóveis mais novos podem ser mais caros, mas há uma grande variação. A idade isoladamente não é um fator determinante.

A mediana dos preços não aumenta de forma linear ao longo das décadas. Há alta dispersão de preços em todas as décadas, com outliers em cada uma. Imóveis construídos após o ano de 1990 apresentam maior concentração de preços elevados. Embora imóveis mais novos possam ter preços mais altos, há imóveis antigos com preços semelhantes.

Os pontos vermelhos representam imóveis reformados. Os dados mostram que reformas são mais comuns após 1980 e estão associadas a preços elevados. No entanto muitos imóveis antigos não foram reformados e mantêm valores médios.

3.1.4.4 Atributos Externos e Vizinhança¶

Variáveis que influenciam o valor do imóvel devido à localização ou benefícios adicionais:

  • waterfront: Vista para o mar (0 = não, 1 = sim)
  • view: Classificação da vista (escala de 0 a 4)
  • sqft_living15: Média da área útil das 15 casas mais próximas
  • sqft_lot15: Média da área total dos lotes das 15 casas mais próximas
In [42]:
plt.figure(figsize=(8,5))
sns.boxplot(data= data, x='waterfront', y='price')

plt.title("Distribuição de Preços por Presença de Vista para o Mar")
plt.xlabel("Waterfront (0 = Não, 1 = Sim)")
plt.ylabel("Preço de Venda")
plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
No description has been provided for this image

Imóveis com vista para o mar (waterfront = 1) têm preços médios significativamente maiores do que os sem vista, tomando com base a mediana dos preços para casas com vistas para o mar estar acima da mediana de observações em que o waterfront = 0. Observa-se outliers, nos dois grupos. Sendo assim, imóveis à beira-mar têm uma valorização notável.

In [43]:
plt.figure(figsize=(8,5))
sns.boxplot(data=data, x='view', y='price')

plt.title("Distribuição de Preços por Qualidade da Vista")
plt.xlabel("Qualidade da Vista (0 a 4)")
plt.ylabel("Preço de Venda")
plt.ticklabel_format(style='plain', axis='y')

plt.tight_layout()
plt.show()
plt.show()
No description has been provided for this image

Existe uma tendência de aumento dos preços conforme a qualidade da vista aumenta (view de 0 a 4). Para view = 4, a mediana dos preços é bem superior às demais categorias. Ainda podemos identificar que para as faixas de 0 à 2 a distribuição dos preços são similares.

In [44]:
plt.figure(figsize=(8,5))
sns.scatterplot(data=data, x='sqft_living15', y='price', alpha=0.5)
plt.title("Relação entre Média de Área Útil das 15 Casas Mais Próximas e Preço")
plt.xlabel("Média sqft_living15")
plt.ylabel("Preço de Venda")
plt.show()
No description has been provided for this image
In [45]:
plt.figure(figsize=(8,5))
sns.histplot(data['sqft_living15'], bins=30, kde=True)
plt.title("Distribuição da Média da Área Útil das Casas Vizinhas")
plt.xlabel("Média sqft_living15")
plt.ylabel("Frequência")
plt.show()
No description has been provided for this image

Há uma certa relação positiva entre sqft_living15 e o preço de venda, ou seja, quanto maior a média da área útil das casas próximas, maior tende a ser o preço do imóvel. A relação não é estritamente linear, mas há um padrão crescente claro.

A maioria das casas vizinhas tem área útil entre 1.500 e 2.500 sqft. A distribuição é assimétrica, com poucos imóveis tendo sqft_living15 acima de 4.000 sqft.

3.1.4.5 Avaliando a Multicolinearidade¶

A avaliação de multicolinearidade é um passo essencial em análises de regressão linear, pois a presença de correlações elevadas entre variáveis explicativas pode distorcer os coeficientes do modelo, dificultando a interpretação e reduzindo sua precisão preditiva. Uma das ferramentas mais utilizadas para diagnosticar multicolinearidade é o Fator de Inflação da Variância (Variance Inflation Factor - VIF). O VIF mede o quanto a variância de um coeficiente de regressão é inflada devido à correlação entre variáveis explicativas.

Vlores de VIF acima de 10 são geralmente considerados indicadores de multicolinearidade severa, enquanto valores próximos de 1 sugerem baixa correlação. Essa métrica ajuda a identificar quais variáveis podem ser redundantes e devem ser ajustadas ou removidas do modelo, melhorando sua robustez e interpretabilidade.

In [46]:
X.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 21613 entries, 0 to 21612
Data columns (total 20 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   id             21613 non-null  int64  
 1   date           21613 non-null  object 
 2   bedrooms       21613 non-null  int64  
 3   bathrooms      21613 non-null  float64
 4   sqft_living    21613 non-null  int64  
 5   sqft_lot       21613 non-null  int64  
 6   floors         21613 non-null  float64
 7   waterfront     21613 non-null  int64  
 8   view           21613 non-null  int64  
 9   condition      21613 non-null  int64  
 10  grade          21613 non-null  int64  
 11  sqft_above     21613 non-null  int64  
 12  sqft_basement  21613 non-null  int64  
 13  yr_built       21613 non-null  int64  
 14  yr_renovated   21613 non-null  int64  
 15  zipcode        21613 non-null  int64  
 16  lat            21613 non-null  float64
 17  long           21613 non-null  float64
 18  sqft_living15  21613 non-null  int64  
 19  sqft_lot15     21613 non-null  int64  
dtypes: float64(4), int64(15), object(1)
memory usage: 3.3+ MB
In [47]:
numerical_features = ['price', 'bedrooms', 'bathrooms', 'sqft_living',
       'floors', 'waterfront', 'view', 'condition', 'grade',
       'sqft_above', 'sqft_basement','sqft_living15']
In [48]:
vif_data = pd.DataFrame()
vif_data["Feature"] = numerical_features
vif_data["VIF"] = [variance_inflation_factor(data[numerical_features].values, i) for i in range(data[numerical_features].shape[1])]
In [49]:
print("\nVariance Inflation Factor (VIF):")
print(vif_data)
Variance Inflation Factor (VIF):
          Feature   VIF
0           price  7.43
1        bedrooms 21.35
2       bathrooms 24.91
3     sqft_living   inf
4          floors 15.80
5      waterfront  1.25
6            view  1.54
7       condition 21.04
8           grade 69.37
9      sqft_above   inf
10  sqft_basement   inf
11  sqft_living15 26.18

"Em geral, considera-se um problema VIF > 5. Mas esta é uma regra de bolso. Há quem diga que acima de 10 é que se deve corrigir o problema de colinearidade... Algumas alternativas para solucionar problemas de multicolinearidade são: regressão por mínimos quadrados parciais (partial least squares, do inglês), regressão de componentes principais (similar a última), regressão de cumeeira (ridge regression)."
(Silva, 2023, p. 199)

Um dos pressupostos fundamentais da regressão linear é a ausência de multicolinearidade entre as variáveis independentes, que foi claramente violado, conforme evidenciado pelos valores do VIF. Variáveis como sqft_living, sqft_above e sqft_basement tendenram ao ∞. As variáveis bedrooms (21,35), bathrooms (24,91), floors (15.80), condition(21,04), grade (69,37) e sqft_living15 indicam níveis de multicolinearidade. Essa condição compromete a estabilidade e a interpretabilidade do modelo, podendo inflacionar os erros padrão dos coeficientes e dificultar a identificação da real contribuição de cada variável na previsão.

No entanto, usaremos a "Licença científica" e seguiremos em frente para explorar como o modelo se comporta, reconhecendo que, conforme o ciclo CRISP-DM, a transição entre as etapas de preparação dos dados e modelagem pode ser feita iterativamente. Isso permite revisitar as etapas, ajustar o tratamento de multicolinearidade e refinar o modelo conforme necessário, garantindo maior robustez e adequação aos dados ao longo do processo.

In [50]:
numerical_features = ['bedrooms', 'bathrooms', 'sqft_living', 'floors', 'waterfront', 'view', 'condition', 'grade']
vif_data = pd.DataFrame()
vif_data["Feature"] = numerical_features
vif_data["VIF"] = [variance_inflation_factor(data[numerical_features].values, i) for i in range(data[numerical_features].shape[1])]

print("\nVariance Inflation Factor (VIF):")
print(vif_data)
Variance Inflation Factor (VIF):
       Feature   VIF
0     bedrooms 20.49
1    bathrooms 24.38
2  sqft_living 19.37
3       floors 12.89
4   waterfront  1.20
5         view  1.43
6    condition 20.11
7        grade 55.09

4. Pré-Processamento dados¶

4.1 Feature Engineer¶

Após a Análise Exploratória dos Dados (EDA), iniciamos a etapa de pré-processamento, essencial para preparar os dados de forma adequada para a modelagem.

Nesse trabalho, (i) transformamos variáveis categóricas em variáveis dummy, permitindo que classes representadas como strings sejam convertidas em um formato numérico compreensível para os algoritmos de aprendizado. Além disso, (ii) tratamos os valores ausentes, optando por remover observações com dados faltantes, dado seu impacto mínimo (apenas 2 registros).

Essas ações visam garantir que os dados estejam limpos, estruturados e prontos para alimentar o modelo, maximizando sua eficácia e desempenho.

4.1.1 Imputação Missing¶

Não encontrado 3.1.2

In [51]:
# X = X.dropna()
# y = y[X.index]

4.2 Split dados¶

Para avaliar o desempenho do modelo, é fundamental dividir os dados em conjuntos de treino e teste. Duas abordagens amplamente utilizadas são:

  • (i) o método estático, implementado pela função train_test_split da biblioteca sklearn.model_selection, que separa os dados em uma única iteração; e
  • (ii) a validação cruzada (Cross-validation), que realiza divisões aleatórias e avalia o modelo em diferentes subconjuntos para maior robustez.

Optamos pelo método estático, configurando 80% dos dados para treino e 20% para teste, garantindo simplicidade e uma divisão consistente para o desenvolvimento inicial do modelo.

4.2.1 Estático - nível linha¶

In [52]:
X.columns
Out[52]:
Index(['id', 'date', 'bedrooms', 'bathrooms', 'sqft_living', 'sqft_lot',
       'floors', 'waterfront', 'view', 'condition', 'grade', 'sqft_above',
       'sqft_basement', 'yr_built', 'yr_renovated', 'zipcode', 'lat', 'long',
       'sqft_living15', 'sqft_lot15'],
      dtype='object')
In [53]:
numerical_features = ['bedrooms', 'bathrooms', 'sqft_living',
       'floors', 'waterfront', 'view', 'condition', 'grade']
X = X[numerical_features]
In [56]:
X.head()
Out[56]:
bedrooms bathrooms sqft_living floors waterfront view condition grade
0 3 1.00 1180 1.00 0 0 3 7
1 3 2.25 2570 2.00 0 0 3 7
2 2 1.00 770 1.00 0 0 3 6
3 4 3.00 1960 1.00 0 0 5 7
4 3 2.00 1680 1.00 0 0 3 8
In [57]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

= output: dados pré-processados e splitados para iniciar a construção do modelo de ML

5. Seleção Algoritmos¶

5.1 Algoritmos Regressores¶

A regressão linear é uma técnica estatística utilizada para prever o valor de uma variável dependente (contínua) com base em uma ou mais variáveis independentes. O objetivo é estabelecer um modelo matemático que expresse a relação entre as variáveis, permitindo entender como determinadas variáveis explicativas influenciam a resposta. Um exemplo de seu uso é prever o preço de uma casa com base em seu tamanho, onde o preço é a variável dependente e o tamanho, a variável independente, ou seja, o objetivo do presente.

A abordagem baseia-se na suposição de que as variáveis estão linearmente relacionadas, permitindo que a relação seja descrita por uma equação linear simples ou múltipla. Para construir o modelo, é necessário treinar o algoritmo com dados históricos, identificar os padrões matemáticos subjacentes e, em seguida, usar esses padrões para fazer previsões. Essa simplicidade faz da regressão linear uma ferramenta amplamente utilizada em diversas áreas, como finanças, engenharia, e claro ciência de dados.

Além de sua facilidade de implementação, a regressão linear fornece coeficientes que são facilmente interpretáveis, permitindo compreender o impacto de cada variável independente na resposta. No entanto, sua aplicabilidade depende de atender a pressupostos importantes, como linearidade, homocedasticidade, independência dos erros e ausência de multicolinearidade.

5.1.1 Regressão Linear¶

Assume que os dados estão em Distribuição Normal e também assume que as variáveis são relevantes para a construção do modelo e que não sejam colineares, ou seja, variáveis com alta correlação (deve-se entregar ao algoritmo as variáveis realmente relevantes).

In [58]:
model = LinearRegression()
In [60]:
print(X.dtypes)
bedrooms         int64
bathrooms      float64
sqft_living      int64
floors         float64
waterfront       int64
view             int64
condition        int64
grade            int64
dtype: object
In [61]:
model.fit(X_train, y_train)
Out[61]:
LinearRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LinearRegression()
In [62]:
coefficients = pd.DataFrame({"Feature": X.columns, "Coefficient": model.coef_})
intercept = model.intercept_
In [63]:
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)

residuals = y_test - y_pred_test
In [64]:
print("Coeficientes do modelo:")
print(coefficients)
Coeficientes do modelo:
       Feature  Coefficient
0     bedrooms    -30632.18
1    bathrooms     -7969.01
2  sqft_living       190.49
3       floors    -12012.75
4   waterfront    559483.69
5         view     64298.00
6    condition     53036.76
7        grade     98875.62

Coeficientes 𝛽 positivos indicam que, à medida que a variável explicativa associada aumenta (mantendo as outras constantes), o preço da variável dependente 𝑦 também aumenta. Ou seja, essas variáveis têm um impacto positivo no preço.

Coeficientes 𝛽 negativos indicam que, à medida que a variável explicativa associada aumenta, o preço (𝑦) diminui, tendo um impacto negativo no preço.

Essa relação é direta, e a magnitude do coeficiente representa o impacto médio da variável na resposta 𝑦. Por exemplo:

  • Se 𝛽=50, um aumento de 1 unidade na variável 𝑥 correspondente resultará em um aumento médio de 50 unidades no preço (𝑦).
  • Se 𝛽= −30, um aumento de 1 unidade em 𝑥 reduzirá, em média, o preço em 30 unidades.

Em termos práticos temos:

Os coeficientes do modelo de regressão indicam o impacto de cada variável sobre o preço de venda do imóvel. Cada coeficiente representa o efeito marginal, ou seja, a variação esperada no preço quando essa variável aumenta em uma unidade, mantendo as demais constantes.

Variável Coeficiente Interpretação
bedrooms -30.632,18 Cada quarto adicional está associado a uma redução média de 30.632,18 no preço do imóvel. Isso pode indicar que o número de quartos isoladamente não é um bom preditor e pode estar capturando efeitos já explicados por sqft_living.
bathrooms -7.969,01 Cada banheiro adicional reduz o preço médio em 7.969,01. Esse efeito negativo pode ser um indicador de multicolinearidade, pois bathrooms pode estar correlacionado com sqft_living ou floors.
sqft_living +190,49 Cada aumento de 1 pé quadrado na área útil aumenta o preço médio em 190,49. Este coeficiente reforça que o tamanho do imóvel é um dos principais fatores de valorização.
floors -12.012,75 Cada andar adicional reduz o preço médio em 12.012,75. Esse resultado pode indicar que imóveis de muitos andares podem ser menos valorizados na região.
waterfront +559.483,69 Casas com vista para o mar são, em média, 559.483,69 mais caras que aquelas sem vista. Forte impacto positivo!
view +64.298,00 Cada ponto a mais na classificação da vista aumenta o preço médio em 64.298,00. A valorização crescente mostra que imóveis com melhores vistas têm um impacto direto no preço.
condition +53.036,76 Cada melhoria na condição geral da casa aumenta o preço médio em 53.036,76. Faz sentido, pois imóveis em melhor estado de conservação são mais valorizados.
grade +98.875,62 Cada nível a mais na classificação da qualidade da construção aumenta o preço médio em 98.875,62. Como esperado, a qualidade da construção tem um impacto significativo.

5.2 Performance para Regressores¶

Existem diversas métricas disponíveis para avaliar o desempenho de um modelo, cada uma com suas características e aplicações específicas. É considerado uma boa prática utilizar a mesma métrica ao comparar diferentes modelos, garantindo consistência e permitindo uma análise justa dos resultados. Para mais detalhes sobre as opções de métricas, consulte a documentação oficial do scikit-learn.

Métricas Para Avaliar Modelos de Regressão

  • Mean Squared Error (MSE)
  • Root Mean Squared Error (RMSE)
  • Mean Absolute Error (MAE)
  • R Squared (R²)
  • Adjusted R Squared (R²)
  • Mean Square Percentage Error (MSPE)
  • Mean Absolute Percentage Error (MAPE)
  • Root Mean Squared Logarithmic Error (RMSLE)

5.2.1 MSE¶

O MSE é uma das métricas mais simples e amplamente utilizadas para avaliar o desempenho de modelos de regressão. Ele mede o erro quadrado médio entre as previsões do modelo e os valores reais da variável-alvo. Para cada ponto, calcula-se a diferença entre o valor predito e o valor observado, eleva-se ao quadrado, e, em seguida, calcula-se a média desses valores.

Um MSE maior indica um desempenho pior do modelo, enquanto um MSE de zero seria alcançado por um modelo perfeito. Essa métrica nunca assume valores negativos, pois os erros individuais são elevados ao quadrado, garantindo que todos sejam positivos. Apesar de sua simplicidade, o MSE pode ser influenciado por outliers, o que o torna menos útil em alguns cenários específicos.

In [65]:
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
In [66]:
# quanto menor melhor
print(f'O MSE do modelo é: {train_rmse} para os dados de treino e {test_rmse} para os dados de teste!')
O MSE do modelo é: 228523.7241964183 para os dados de treino e 241797.04179854685 para os dados de teste!

5.2.2 MAE¶

O MAE é a média das diferenças absolutas entre as previsões do modelo e os valores reais da variável-alvo. Essa métrica fornece uma visão clara do quão distante, em média, as previsões estão dos valores reais, sem considerar a direção do erro (positiva ou negativa).

Um MAE igual a zero indica que o modelo não cometeu erros, ou seja, as previsões foram perfeitas. Por ser baseado em valores absolutos, o MAE é menos sensível a outliers em comparação com o MSE, o que pode torná-lo mais adequado em cenários onde grandes erros individuais não devem ter peso excessivo na avaliação do modelo.

In [67]:
train_mae = np.sqrt(mean_absolute_error(y_train, y_pred_train))
test_mae = np.sqrt(mean_absolute_error(y_test, y_pred_test))
In [68]:
# quanto menor melhor
print(f'O MAE do modelo é: {train_mae} para os dados de treino e {test_mae} para os dados de teste!')
O MAE do modelo é: 389.2203128991908 para os dados de treino e 393.44530256636966 para os dados de teste!

5.2.3 R²¶

O R² é uma métrica amplamente utilizada para avaliar o quão bem o modelo explica a variabilidade dos dados observados. Ele mede a proporção da variância da variável-alvo que é explicada pelas variáveis independentes do modelo, oferecendo uma indicação do nível de precisão das previsões em relação aos valores observados.

Os valores do R² variam de 0 a 1, onde:

  • 0 indica que o modelo não explica nenhuma variação nos dados (desempenho ruim).
  • 1 representa um modelo perfeito, que explica 100% da variância da variável-alvo.

Embora o R² seja útil para interpretar a qualidade do ajuste do modelo, ele não avalia diretamente a magnitude dos erros, sendo necessário combiná-lo com outras métricas para uma análise mais completa.

In [69]:
train_r2 = r2_score(y_train, y_pred_train)
test_r2 = r2_score(y_test, y_pred_test)
print(f'O R2 do modelo é: {train_r2} para os dados de treino e {test_r2} para os dados de teste!')
O R2 do modelo é: 0.6002782500272685 para os dados de treino e 0.6132618010425313 para os dados de teste!
Consolidando as principais Técnicas de performance para Regressores¶
In [70]:
print("O MSE do modelo é:", train_rmse)
print("O MAE do modelo é:", train_mae)
print("O R² do modelo é:", train_r2)
O MSE do modelo é: 228523.7241964183
O MAE do modelo é: 389.2203128991908
O R² do modelo é: 0.6002782500272685

5.3 Distribuição dos resíduos¶

In [71]:
plt.figure(figsize=(10, 6))
sns.scatterplot(x=y_pred_test, y=residuals, color="blue")
plt.axhline(0, linestyle="--", color="red")
plt.title("Resíduos vs. Valores Preditos")
plt.xlabel("Valores Preditos")
plt.ylabel("Resíduos")
plt.show()
No description has been provided for this image

Os resíduos não estão uniformemente distribuídos ao longo da linha central (linha vermelha em zero). Existe um padrão de aumento na variância dos resíduos para valores preditos maiores, sugerindo que o modelo apresenta heterocedasticidade, ou seja, a variabilidade dos resíduos não é constante, fere pressupostos de uma regressão linear. Além de pontos distantes do centro, especialmente em valores preditos altos, indicam potenciais outliers ou casos que o modelo não conseguiu ajustar bem.

In [72]:
plt.figure(figsize=(10, 6))
sns.histplot(residuals, kde=True, color="purple")
plt.title("Distribuição dos Resíduos")
plt.xlabel("Resíduos")
plt.ylabel("Frequência")
plt.show()
No description has been provided for this image

O histograma indica que os resíduos estão concentrados em torno de zero, com uma leve assimetria. Embora o pico seja bem definido, as caudas mais alongadas sugerem que os resíduos não seguem uma distribuição perfeitamente normal. A curva roxa de densidade destaca a presença de uma cauda direita levemente mais longa, indicando que alguns resíduos positivos são maiores do que o esperado. Podemos inferir que a distribuição dos resíduos não é perfeitamente normal e pode influenciar a validade de inferências estatísticas, como os intervalos de confiança e testes de hipóteses.

In [73]:
sm.qqplot(residuals, line="s")
plt.title("QQ-Plot dos Resíduos")
plt.show()
No description has been provided for this image

Em um modelo ideal com resíduos normais, os pontos devem estar alinhados com a linha vermelha. Nos quantis centrais (em torno de zero), os resíduos seguem razoavelmente a linha de referência, indicando uma aproximação da normalidade para a maior parte dos dados. Muito embora, nos extremos, há desvios significativos da linha, com resíduos nas caudas (tanto inferiores quanto superiores) afastando-se da normalidade. Isso sugere a presença de outliers ou resíduos que não seguem bem a distribuição normal.

In [74]:
print("Média dos resíduos:", np.mean(residuals))
print("Desvio padrão dos resíduos:", np.std(residuals))
Média dos resíduos: 2608.9596338330316
Desvio padrão dos resíduos: 241782.9662158963
In [75]:
print("O MSE do modelo é:", train_rmse)
print("O MAE do modelo é:", train_mae)
print("O R² do modelo é:", train_r2)
O MSE do modelo é: 228523.7241964183
O MAE do modelo é: 389.2203128991908
O R² do modelo é: 0.6002782500272685

6. Conclusões¶

O modelo de regressão linear apresentou um R² de 0,6, indicando que 60% da variabilidade do preço de venda das propriedades price pode ser explicada pelas variáveis explicativas selecionadas. Este resultado, é claro que possui uma capacidade preditiva muito baixa, ainda mais quando os pressupostos fundamentais da regressão linear são violados.

Durante a etapa de análise exploratória de dados (EDA), identificamos indícios de fragilidade nos dados, como distribuições assimétricas, outliers e possível multicolinearidade entre as variáveis. Essas características foram confirmadas durante a avaliação dos resíduos e dos pressupostos do modelo. Algumas relações parecem não ser totalmente lineares, apresentando coeficientes negativos inesperados. Isso indica que pode haver efeitos não capturados, como interações entre variáveis ou relações exponenciais. Os gráficos residuais indicaram a presença de padrões não aleatórios, heterocedasticidade e desvios significativos da normalidade, comprometendo a validade inferencial do modelo. Além disso, o cálculo do VIF revelou multicolinearidade em algumas variáveis, o que pode influenciar negativamente as estimativas dos coeficientes.

Com base no framework CRISP-DM, é evidente que o modelo deve retornar à fase de preparação e pré-processamento dos dados. Nesta etapa, será essencial adotar estratégias como: (i) aplicar trasnformação logaritmo na variavel target, (ii) realizar combinação de variáveis ja existentes para minimizar os coeficientes negativos inesperados, (iii) criar novos atributos com base na idade dos imóveis, (iv) possibilidade de indicar imóveis com valor histórico, (v) mapear proximidade de áreas verdes e parques, (vi) agrupar variveis multiclass com mais coesão (grade e condition) e ainda (vii) proximidade de infraestrutura urbana.

O modelo sendo reavaliado para garantir que atenda tanto aos pressupostos estatísticos quanto às métricas preditivas esperadas dará ao modelo confiança e interpretabilidade, sendo útil para a precificação de imóveis com caracteristicas similares, além de possibilitar mapear comportamentos de valorização no início desse movimento, colocando em vantagem estratégica o utilizador desse modelo.

No presente estudo, observamos que a variável alvo, class, apresenta um desbalanceamento leve. Embora exista uma diferença na proporção entre as classes "bom pagador" e "mau pagador", a princípio, não se faz necessária a aplicação imediata de técnicas de balanceamento. A prioridade será avaliar o impacto deste desbalanceamento no desempenho dos modelos de machine learning antes de implementar qualquer ajuste.

Caso seja confirmado um impacto negativo significativo, as principais técnicas de balanceamento de classes a serem consideradas incluem:

Oversampling: Esta técnica visa equilibrar as classes através do aumento da representatividade da classe minoritária. Isso pode ser feito replicando registros existentes da classe minoritária ou, de forma mais sofisticada, gerando novas amostras sintéticas. Uma das abordagens mais populares de oversampling sintético é o SMOTE (Synthetic Minority Over-sampling Technique). O SMOTE cria novas instâncias minoritárias através da interpolação entre exemplos minoritários vizinhos, expandindo o espaço de características da classe minoritária de maneira inteligente.

Undersampling: Em contraste com o oversampling, o undersampling busca o equilíbrio reduzindo a representatividade da classe majoritária. Isso é alcançado através da eliminação aleatória ou criteriosa de registros da classe majoritária. Embora o undersampling possa acelerar o processo de treinamento do modelo, ele apresenta o risco de descartar informações potencialmente relevantes contidas nos registros da classe majoritária que foram removidos.

A decisão final sobre a necessidade e qual técnica de balanceamento aplicar será estritamente baseada em evidências empíricas. Se a avaliação do modelo indicar um viés excessivo em favor da classe majoritária, manifestado por uma performance insatisfatória na identificação de maus pagadores, as técnicas de balanceamento serão reavaliadas e aplicadas para mitigar este viés e otimizar a capacidade preditiva do modelo para ambas as classes.