Projeto: Baixa, média e alta renda, qual casa você pode comprar nos EUA?
Objetivo
O objetivo deste projeto é utilizar um modelo de árvore de decisão para classificar imóveis em diferentes faixas de preço (baixa, média e alta renda) com base em suas características.
1. Exploração dos Dados (EDA)
Nesta etapa, foi realizada a análise exploratória do dataset AmesHousing.csv, verificando as primeiras linhas, informações gerais, estatísticas descritivas, valores ausentes e visualização de algumas variáveis categóricas.
Primeiras 5 linhas do dataset:
Order PID MS SubClass MS Zoning Lot Frontage Lot Area ... Misc Val Mo Sold Yr Sold Sale Type Sale Condition SalePrice
0 1 526301100 20 RL 141.0 31770 ... 0 5 2010 WD Normal 215000
1 2 526350040 20 RH 80.0 11622 ... 0 6 2010 WD Normal 105000
2 3 526351010 20 RL 81.0 14267 ... 12500 6 2010 WD Normal 172000
3 4 526353030 20 RL 93.0 11160 ... 0 4 2010 WD Normal 244000
4 5 527105010 60 RL 74.0 13830 ... 0 3 2010 WD Normal 189900
[5 rows x 82 columns]
Informações do dataset:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2930 entries, 0 to 2929
Data columns (total 82 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Order 2930 non-null int64
1 PID 2930 non-null int64
2 MS SubClass 2930 non-null int64
3 MS Zoning 2930 non-null object
4 Lot Frontage 2440 non-null float64
5 Lot Area 2930 non-null int64
6 Street 2930 non-null object
7 Alley 198 non-null object
8 Lot Shape 2930 non-null object
9 Land Contour 2930 non-null object
10 Utilities 2930 non-null object
11 Lot Config 2930 non-null object
12 Land Slope 2930 non-null object
13 Neighborhood 2930 non-null object
14 Condition 1 2930 non-null object
15 Condition 2 2930 non-null object
16 Bldg Type 2930 non-null object
17 House Style 2930 non-null object
18 Overall Qual 2930 non-null int64
19 Overall Cond 2930 non-null int64
20 Year Built 2930 non-null int64
21 Year Remod/Add 2930 non-null int64
22 Roof Style 2930 non-null object
23 Roof Matl 2930 non-null object
24 Exterior 1st 2930 non-null object
25 Exterior 2nd 2930 non-null object
26 Mas Vnr Type 1155 non-null object
27 Mas Vnr Area 2907 non-null float64
28 Exter Qual 2930 non-null object
29 Exter Cond 2930 non-null object
30 Foundation 2930 non-null object
31 Bsmt Qual 2850 non-null object
32 Bsmt Cond 2850 non-null object
33 Bsmt Exposure 2847 non-null object
34 BsmtFin Type 1 2850 non-null object
35 BsmtFin SF 1 2929 non-null float64
36 BsmtFin Type 2 2849 non-null object
37 BsmtFin SF 2 2929 non-null float64
38 Bsmt Unf SF 2929 non-null float64
39 Total Bsmt SF 2929 non-null float64
40 Heating 2930 non-null object
41 Heating QC 2930 non-null object
42 Central Air 2930 non-null object
43 Electrical 2929 non-null object
44 1st Flr SF 2930 non-null int64
45 2nd Flr SF 2930 non-null int64
46 Low Qual Fin SF 2930 non-null int64
47 Gr Liv Area 2930 non-null int64
48 Bsmt Full Bath 2928 non-null float64
49 Bsmt Half Bath 2928 non-null float64
50 Full Bath 2930 non-null int64
51 Half Bath 2930 non-null int64
52 Bedroom AbvGr 2930 non-null int64
53 Kitchen AbvGr 2930 non-null int64
54 Kitchen Qual 2930 non-null object
55 TotRms AbvGrd 2930 non-null int64
56 Functional 2930 non-null object
57 Fireplaces 2930 non-null int64
58 Fireplace Qu 1508 non-null object
59 Garage Type 2773 non-null object
60 Garage Yr Blt 2771 non-null float64
61 Garage Finish 2771 non-null object
62 Garage Cars 2929 non-null float64
63 Garage Area 2929 non-null float64
64 Garage Qual 2771 non-null object
65 Garage Cond 2771 non-null object
66 Paved Drive 2930 non-null object
67 Wood Deck SF 2930 non-null int64
68 Open Porch SF 2930 non-null int64
69 Enclosed Porch 2930 non-null int64
70 3Ssn Porch 2930 non-null int64
71 Screen Porch 2930 non-null int64
72 Pool Area 2930 non-null int64
73 Pool QC 13 non-null object
74 Fence 572 non-null object
75 Misc Feature 106 non-null object
76 Misc Val 2930 non-null int64
77 Mo Sold 2930 non-null int64
78 Yr Sold 2930 non-null int64
79 Sale Type 2930 non-null object
80 Sale Condition 2930 non-null object
81 SalePrice 2930 non-null int64
dtypes: float64(11), int64(28), object(43)
memory usage: 1.8+ MB
None
Estatísticas descritivas:
Order PID MS SubClass MS Zoning Lot Frontage ... Mo Sold Yr Sold Sale Type Sale Condition SalePrice
count 2930.00000 2.930000e+03 2930.000000 2930 2440.000000 ... 2930.000000 2930.000000 2930 2930 2930.000000
unique NaN NaN NaN 7 NaN ... NaN NaN 10 6 NaN
top NaN NaN NaN RL NaN ... NaN NaN WD Normal NaN
freq NaN NaN NaN 2273 NaN ... NaN NaN 2536 2413 NaN
mean 1465.50000 7.144645e+08 57.387372 NaN 69.224590 ... 6.216041 2007.790444 NaN NaN 180796.060068
std 845.96247 1.887308e+08 42.638025 NaN 23.365335 ... 2.714492 1.316613 NaN NaN 79886.692357
min 1.00000 5.263011e+08 20.000000 NaN 21.000000 ... 1.000000 2006.000000 NaN NaN 12789.000000
25% 733.25000 5.284770e+08 20.000000 NaN 58.000000 ... 4.000000 2007.000000 NaN NaN 129500.000000
50% 1465.50000 5.354536e+08 50.000000 NaN 68.000000 ... 6.000000 2008.000000 NaN NaN 160000.000000
75% 2197.75000 9.071811e+08 70.000000 NaN 80.000000 ... 8.000000 2009.000000 NaN NaN 213500.000000
max 2930.00000 1.007100e+09 190.000000 NaN 313.000000 ... 12.000000 2010.000000 NaN NaN 755000.000000
[11 rows x 82 columns]
Valores ausentes por coluna:
Order 0
PID 0
MS SubClass 0
MS Zoning 0
Lot Frontage 490
...
Mo Sold 0
Yr Sold 0
Sale Type 0
Sale Condition 0
SalePrice 0
Length: 82, dtype: int64
from matplotlib import patches
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from IPython.display import display
df = pd.read_csv('data/AmesHousing.csv')
print("Primeiras 5 linhas do dataset:")
print(df.head())
print("\nInformações do dataset:")
print(df.info())
print("\nEstatísticas descritivas:")
print(df.describe(include='all'))
colunas = df.columns
print("\nColunas do dataset:")
print(colunas)
nulos = df.isnull().sum()
print("\nValores ausentes por coluna:")
print(nulos.head(100).to_string())
plt.style.use('ggplot')
plt.figure(figsize=(15,10))
df.isnull().mean().sort_values(ascending=False).head(20).plot(kind='bar', color='salmon')
plt.title("Percentual de Valores Ausentes nas 20 Variáveis com Mais NAs")
plt.ylabel("% de valores faltantes")
plt.savefig('./docs/classes/arvore-de-decisao/img/valores_ausentes.png')
#variáveis categóricas
plt.figure(figsize=(25, 20))
plt.subplot(2, 2, 1)
sns.countplot(data=df, x='MS Zoning', palette='viridis', order=df['MS Zoning'].value_counts().index)
plt.title('Classificação de Zonas Residenciais')
plt.xticks(rotation=45)
plt.xlabel('Zona de Zoneamento')
plt.ylabel('Contagem')
plt.tight_layout()
plt.subplot(2, 2, 2)
df['Street'].value_counts().plot.pie(autopct='%1.1f%%', figsize=(6,6), colors=sns.color_palette('viridis', 5), labels=df['Street'].value_counts().index)
plt.title('Tipo de Rua')
plt.ylabel('') #remove o label do eixo Y
plt.tight_layout()
plt.subplot(2, 2, 3)
df['Neighborhood'].value_counts().head(5).plot(kind='bar', color=sns.color_palette('viridis', 5))
plt.title('Top 5 Vizinhanças')
plt.xticks(rotation=45)
plt.xlabel('Vizinhança')
plt.ylabel('Contagem')
plt.tight_layout()
plt.subplot(2, 2, 4)
sns.countplot(data=df, x='House Style', palette='viridis', order=df['House Style'].value_counts().index)
plt.xticks(rotation=45)
plt.title('Estilo da Casa')
plt.ylabel('Contagem')
plt.tight_layout()
plt.savefig('./docs/classes/arvore-de-decisao/img/categoricas.png')
#variáveis numéricas
plt.figure(figsize=(15, 10))
plt.subplot(2, 2, 1)
sns.histplot(df['SalePrice'], bins=30, kde=True, color='salmon')
plt.title("Distribuição do Preço de Venda")
plt.xlabel("Preço de Venda")
plt.ylabel("Frequência")
plt.tight_layout()
plt.subplot(2, 2, 2)
sns.histplot(df['Gr Liv Area'], bins=30, kde=True, color='salmon')
plt.title("Distribuição da Área Acima do Solo")
plt.xlabel("Área Acima do Solo (pés²)")
plt.ylabel("Frequência")
plt.tight_layout()
plt.subplot(2, 2, 3)
sns.histplot(df['Year Built'], bins=30, kde=True, color='salmon')
plt.title("Distribuição do Ano de Construção")
plt.xlabel("Ano de Construção")
plt.ylabel("Frequência")
plt.tight_layout()
plt.subplot(2, 2, 4)
sns.histplot(df['Total Bsmt SF'], bins=30, kde=True, color='salmon')
plt.title("Distribuição da Área do Porão")
plt.xlabel("Área do Porão (pés²)")
plt.ylabel("Frequência")
plt.tight_layout()
plt.savefig('./docs/classes/arvore-de-decisao/img/dist_geral_numericas.png')
plt.show()
plt.figure(figsize=(15, 10))
plt.subplot(2, 1, 1)
sns.boxplot(x='Neighborhood', y='SalePrice', palette='viridis', data=df)
plt.title("Preço por Bairro")
plt.xticks(rotation=90)
plt.subplot(2, 1, 2)
sns.boxplot(x='Overall Qual', y='SalePrice', data=df, palette='viridis')
plt.title("Qualidade Geral vs Preço de Venda")
plt.tight_layout()
plt.savefig('./docs/classes/arvore-de-decisao/img/boxplots.png')
corr_cols = ['Lot Area', 'Gr Liv Area', 'Total Bsmt SF', 'Garage Area', '1st Flr SF',
'2nd Flr SF', 'Full Bath', 'Half Bath', 'Bedroom AbvGr', 'TotRms AbvGrd',
'Overall Qual', 'Overall Cond', 'Year Built', 'Year Remod/Add']
plt.figure(figsize=(10,8))
sns.heatmap(df[corr_cols].corr(), annot=True, cmap='coolwarm', fmt=".2f")
plt.title("Correlação entre variáveis numéricas")
plt.savefig('./docs/classes/arvore-de-decisao/img/matriz_correlacao.png') Utilizamos a biblioteca Pandas para a análise de dados e a biblioteca Matplotlib.pyplot para fazer os gráficos para uma melhor visualização dos dados.
Logo após carregarmos a base de dados:
- base = pd.read_csv('caminho/para/a/base')
Utilizamos comandos para a análise exploratória da base:
-
base.head() -> sem a especificação de quantas linhas puxar, o head imprime as primeiras 5 linhas da sua base de dados.
-
base.info() -> mostra as colunas da base de dados e seus respectivos tipos.
-
base.describe(include='all') -> imprime as estatísticas descritivas das colunas da base de dados, como a frequência, a média, o desvio padrão e etc.
-
base.isnull().sum() -> soma todos os valores nulos por coluna.
Após finalizar esse processo com a análise exploratória, precisamos fazer a visualização da análise realizada. Por isso, utilizamos os seguintes comandos:
-
plt.style.use('ggplot') -> sendo plt a biblioteca do matplotlib, esse comando serve para escolhermos o estilo que
-
plt.figure(figsize=(15, 10)) -> cria uma nova figura para colocar os gráficos.
-
plt.subplot(2, 2, 1) -> este comando divide a figura em uma grade de 2 linhas por 2 colunas. O último parâmetro é a posição do gráfico.
-
base['nome_coluna'].value_counts().plot.pie(autopct='%1.1f%%', figsize=(6,6)) -> constrói um gráfico de pizza com a coluna escolhida da base. O comando autopct exibe as porcentagens em cada fatia.
-
plt.title('Título') -> intitula o gráfico.
-
plt.ylabel('') -> remove o label do eixo Y, comando utilizado apenas em gráficos de pizza
-
base['nome_coluna'].value_counts().head(5).plot(kind='bar') -> constrói um gráfico de barra, e com o comando head(5) irá aparecer apenas os 5 mais frequentes daquela coluna.
-
plt.xticks(rotation=45) -> rotaciona o gráfico de barra a 45° para uma melhor visualização.
-
plt.savefig('caminho/que/deseja/salvar') -> salva a figura completa que foi iniciada lá no início do código no caminho que foi apontado.
-
plt.show() -> exibe a figura na tela.
Gráficos gerados na análise exploratória
A partir deste gráfico, podemos observar que algumas variáveis possuem uma quantidade significativa de valores ausentes. Isso pode impactar a análise e o desempenho do modelo, sendo necessário considerar estratégias para lidar com esses dados faltantes, como imputação ou remoção dessas variáveis.
No gráfico acima, podemos observar a distribuição das variáveis categóricas selecionadas. Isso nos ajuda a entender a frequência de diferentes categorias dentro dessas variáveis, o que pode ser útil para identificar padrões ou tendências no conjunto de dados. Um exemplo disso é o gráfico de pizza que mostra a predominância de um tipo específico de rua, indicando que a maioria das propriedades está localizada em ruas pavimentadas. E o gráfico de classe de zona residencial, onde a maioria das propriedades está situada em zonas residenciais (RL: Residential Low Density).
No gráfico acima, podemos observar a distribuição das variáveis numéricas selecionadas. Isso nos ajuda a entender a frequência de diferentes valores dentro dessas variáveis, o que pode ser útil para identificar padrões ou tendências no conjunto de dados. Um exemplo disso é a distribuição do preço de venda, que apresenta uma assimetria à direita, indicando que a maioria das propriedades tem preços mais baixos, com algumas propriedades de alto valor.
Os boxplots acima ilustram a relação entre a qualidade geral das casas e o preço de venda. Podemos observar que, em geral, casas com melhor qualidade tendem a ter preços de venda mais altos. No entanto, também há uma variação significativa nos preços dentro de cada categoria de qualidade, indicando que outros fatores além da qualidade geral também influenciam o preço de venda. Além disso, temos um boxplot sobre o preço de venda e o bairro onde a casa está localizada. Podemos observar que certos bairros, como NoRidge e NridgHt, apresentam preços de venda significativamente mais altos em comparação com outros bairros. Isso sugere que a localização é um fator importante na determinação do valor das propriedades.
A matriz de correlação acima mostra a relação entre várias variáveis numéricas no conjunto de dados. Podemos observar que algumas variáveis, como "Gr Liv Area" (área de vida acima do solo) e "Overall Qual" (qualidade geral), têm uma correlação positiva forte com o preço de venda ("SalePrice"). Isso indica que casas com maior área de vida e melhor qualidade tendem a ter preços de venda mais altos. Outras variáveis, como "Lot Area" (área do lote) e "Garage Area" (área da garagem), também mostram correlações positivas, mas em menor grau. Essas informações podem ser úteis para identificar quais características das casas são mais influentes na determinação do preço de venda.
2. Pré-processamento
Como primeira fase do pré-processamento, precisamos tratar os valores ausentes. Como visto anteriormente, principalmente no gráfico gerado, temos muitos valores nulos a serem tratados. Abaixo estão listados a quantidade dos valores nulos em todas as variáveis:
- Lot Frontage (490 valores nulos),
- Mas Vnr Type (1775 valores nulos),
- Mas Vnr Area (23 valores nulos),
- Bsmt Qual (80 valores nulos),
- Bsmt Cond (80 valores nulos),
- Bsmt Exposure (83 valores nulos),
- BsmtFin Type 1 (80 valores nulos),
- BsmtFin SF 1 (1 valor nulo),
- BsmtFin Type 2 (81 valores nulos),
- BsmtFin SF 2 (1 valor nulo),
- Bsmt Unf SF (1 valor nulo),
- Total Bsmt SF (1 valor nulo),
- Electrical (1 valor nulo),
- Bsmt Full Bath (2 valores nulos),
- Bsmt Half Bath (2 valores nulos),
- Fireplace Qu (1422 valores nulos),
- Garage Type (157 valores nulos),
- Garage Yr Blt (159 valores nulos),
- Garage Finish (159 valores nulos),
- Garage Cars (1 valor nulo),
- Garage Area (1 valor nulo),
- Garage Qual (159 valores nulos),
- Garage Cond (159 valores nulos),
- Pool QC (2917 valores nulos),
- Fence (2358 valores nulos),
- Misc Feature (2824 valores nulos).
Para tratar esses valores nulos, utilizei as seguintes estratégias:
- Remoção das variáveis com muitos valores nulos (mais de 50% dos dados ausentes): Mas Vnr Type, Fireplace Qu, Pool QC, Fence e Misc Feature.
- Preenchimento dos valores nulos em variáveis numéricas com a mediana da coluna.
- Preenchimento dos valores nulos em variáveis categóricas com a moda da coluna.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
df = pd.read_csv('data/AmesHousing.csv')
#tratamento de valores nulos
maiores_valores_nulos = ['Pool QC', 'Misc Feature', 'Alley', 'Fence', 'Fireplace Qu']
df = df.drop(columns=maiores_valores_nulos)
# Preenchimento de valores nulos em variáveis numéricas com a mediana
num_cols = df.select_dtypes(include=['float64', 'int64']).columns
for col in num_cols:
df[col].fillna(df[col].median(), inplace=True)
# Preenchimento de valores nulos em variáveis categóricas com a moda
cat_cols = df.select_dtypes(include=['object']).columns
for col in cat_cols:
df[col].fillna(df[col].mode()[0], inplace=True)
nulos = df.isnull().sum()
print("\nValores ausentes por coluna:")
print(nulos.head(100).to_string()) # Verificando se ainda há valores nulos Valores ausentes por coluna:
Order 0
PID 0
MS SubClass 0
MS Zoning 0
Lot Frontage 0
Lot Area 0
Street 0
Lot Shape 0
Land Contour 0
Utilities 0
Lot Config 0
Land Slope 0
Neighborhood 0
Condition 1 0
Condition 2 0
Bldg Type 0
House Style 0
Overall Qual 0
Overall Cond 0
Year Built 0
Year Remod/Add 0
Roof Style 0
Roof Matl 0
Exterior 1st 0
Exterior 2nd 0
Mas Vnr Type 0
Mas Vnr Area 0
Exter Qual 0
Exter Cond 0
Foundation 0
Bsmt Qual 0
Bsmt Cond 0
Bsmt Exposure 0
BsmtFin Type 1 0
BsmtFin SF 1 0
BsmtFin Type 2 0
BsmtFin SF 2 0
Bsmt Unf SF 0
Total Bsmt SF 0
Heating 0
Heating QC 0
Central Air 0
Electrical 0
1st Flr SF 0
2nd Flr SF 0
Low Qual Fin SF 0
Gr Liv Area 0
Bsmt Full Bath 0
Bsmt Half Bath 0
Full Bath 0
Half Bath 0
Bedroom AbvGr 0
Kitchen AbvGr 0
Kitchen Qual 0
TotRms AbvGrd 0
Functional 0
Fireplaces 0
Garage Type 0
Garage Yr Blt 0
Garage Finish 0
Garage Cars 0
Garage Area 0
Garage Qual 0
Garage Cond 0
Paved Drive 0
Wood Deck SF 0
Open Porch SF 0
Enclosed Porch 0
3Ssn Porch 0
Screen Porch 0
Pool Area 0
Misc Val 0
Mo Sold 0
Yr Sold 0
Sale Type 0
Sale Condition 0
SalePrice 0
Após tratar os valores nulos, precisamos transformar as variáveis categóricas em numéricas para que o modelo de árvore de decisão possa utilizá-las. Utilizei duas técnicas principais para isso:
Label Encoding -> serve para variáveis categóricas que possuem uma ordem ou hierarquia e que têm um número limitado de categorias. Exemplo: Poor, Fair, Average, Good, Excellent.
One-Hot Encoding -> serve para variáveis categóricas que não possuem uma ordem ou hierarquia e têm um número elevado de categorias. Exemplo: Cor do carro (vermelho, azul, verde).
Mapeamento Manual -> para variáveis ordinais específicas, onde atribuímos valores numéricos com base na ordem percebida.
Na tabela abaixo estão listadas as variáveis que foram transformadas utilizando cada técnica:
| Técnica | Variáveis |
|---|---|
| Mapeamento Manual | Exter Qual, Exter Cond, Bsmt Qual, Bsmt Cond, Heating QC, Kitchen Qual, Garage Qual, Garage Cond, Bsmt Exposure, BsmtFin Type 1, BsmtFin Type 2, Functional |
| One-Hot Encoding | MS Zoning, Street, Lot Shape, Land Contour, Utilities, Lot Config, Land Slope, Neighborhood, Condition 1, Condition 2, Bldg Type, House Style, Roof Style, Roof Matl, Exterior 1st, Exterior 2nd, Mas Vnr Type, Foundation, Heating, Central Air, Electrical, Garage Type, Garage Finish, Paved Drive, Sale Type, Sale Condition |
- Variáveis categóricas foram transformadas em numéricas para uso no modelo.
MS SubClass Lot Frontage ... Sale Condition_Normal Sale Condition_Partial
0 20 141.0 ... True False
1 20 80.0 ... True False
2 20 81.0 ... True False
3 20 93.0 ... True False
4 60 74.0 ... True False
5 60 78.0 ... True False
6 120 41.0 ... True False
7 120 43.0 ... True False
8 120 39.0 ... True False
9 60 60.0 ... True False
[10 rows x 233 columns]
variaveis_ordinal = {
'Exter Qual': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Exter Cond': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Bsmt Qual': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Bsmt Cond': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Heating QC': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Kitchen Qual': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Garage Qual': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Garage Cond': {'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5},
'Bsmt Exposure': {'No':1, 'Mn':2, 'Av':3, 'Gd':4},
'BsmtFin Type 1': {'Unf':1, 'LwQ':2, 'Rec':3, 'BLQ':4, 'ALQ':5, 'GLQ':6},
'BsmtFin Type 2': {'Unf':1, 'LwQ':2, 'Rec':3, 'BLQ':4, 'ALQ':5, 'GLQ':6},
'Functional': {'Sal':1, 'Sev':2, 'Maj2':3, 'Maj1':4, 'Mod':5, 'Min2':6, 'Min1':7, 'Typ':8},
}
for col, mapping in variaveis_ordinal.items():
if col in df.columns:
df[col] = df[col].map(mapping).fillna(0).astype(int)
variaveis_nominais = [
'MS Zoning', 'Street', 'Lot Shape', 'Land Contour',
'Utilities', 'Lot Config', 'Land Slope', 'Neighborhood', 'Condition 1',
'Condition 2', 'Bldg Type', 'House Style', 'Roof Style', 'Roof Matl',
'Exterior 1st', 'Exterior 2nd', 'Mas Vnr Type', 'Foundation',
'Heating', 'Central Air', 'Electrical', 'Garage Type', 'Garage Finish',
'Paved Drive', 'Sale Type', 'Sale Condition'
]
df = pd.get_dummies(df, columns=variaveis_nominais, drop_first=False)
print(df.head(10))
3. Divisão dos Dados
Nessa etapa, eu criei a variável target e escolhi como features todas as outras variáveis para a construção do meu modelo. Os dados foram divididos em conjuntos de treino e teste, utilizando 70% dos dados para treino e 30% para teste. É importante garantir que a divisão seja feita de forma aleatória para evitar viés.
Target
0 981
1 980
2 969
Name: count, dtype: int64
Features usadas no modelo:
['MS SubClass', 'Lot Frontage', 'Lot Area', 'Overall Qual', 'Overall Cond', 'Year Built', 'Year Remod/Add', 'Mas Vnr Area', 'Exter Qual', 'Exter Cond', 'Bsmt Qual', 'Bsmt Cond', 'Bsmt Exposure', 'BsmtFin Type 1', 'BsmtFin SF 1', 'BsmtFin Type 2', 'BsmtFin SF 2', 'Bsmt Unf SF', 'Total Bsmt SF', 'Heating QC', '1st Flr SF', '2nd Flr SF', 'Low Qual Fin SF', 'Gr Liv Area', 'Bsmt Full Bath', 'Bsmt Half Bath', 'Full Bath', 'Half Bath', 'Bedroom AbvGr', 'Kitchen AbvGr', 'Kitchen Qual', 'TotRms AbvGrd', 'Functional', 'Fireplaces', 'Garage Yr Blt', 'Garage Cars', 'Garage Area', 'Garage Qual', 'Garage Cond', 'Wood Deck SF', 'Open Porch SF', 'Enclosed Porch', '3Ssn Porch', 'Screen Porch', 'Pool Area', 'Misc Val', 'Mo Sold', 'Yr Sold', 'MS Zoning_A (agr)', 'MS Zoning_C (all)', 'MS Zoning_FV', 'MS Zoning_I (all)', 'MS Zoning_RH', 'MS Zoning_RL', 'MS Zoning_RM', 'Street_Grvl', 'Street_Pave', 'Lot Shape_IR1', 'Lot Shape_IR2', 'Lot Shape_IR3', 'Lot Shape_Reg', 'Land Contour_Bnk', 'Land Contour_HLS', 'Land Contour_Low', 'Land Contour_Lvl', 'Utilities_AllPub', 'Utilities_NoSeWa', 'Utilities_NoSewr', 'Lot Config_Corner', 'Lot Config_CulDSac', 'Lot Config_FR2', 'Lot Config_FR3', 'Lot Config_Inside', 'Land Slope_Gtl', 'Land Slope_Mod', 'Land Slope_Sev', 'Neighborhood_Blmngtn', 'Neighborhood_Blueste', 'Neighborhood_BrDale', 'Neighborhood_BrkSide', 'Neighborhood_ClearCr', 'Neighborhood_CollgCr', 'Neighborhood_Crawfor', 'Neighborhood_Edwards', 'Neighborhood_Gilbert', 'Neighborhood_Greens', 'Neighborhood_GrnHill', 'Neighborhood_IDOTRR', 'Neighborhood_Landmrk', 'Neighborhood_MeadowV', 'Neighborhood_Mitchel', 'Neighborhood_NAmes', 'Neighborhood_NPkVill', 'Neighborhood_NWAmes', 'Neighborhood_NoRidge', 'Neighborhood_NridgHt', 'Neighborhood_OldTown', 'Neighborhood_SWISU', 'Neighborhood_Sawyer', 'Neighborhood_SawyerW', 'Neighborhood_Somerst', 'Neighborhood_StoneBr', 'Neighborhood_Timber', 'Neighborhood_Veenker', 'Condition 1_Artery', 'Condition 1_Feedr', 'Condition 1_Norm', 'Condition 1_PosA', 'Condition 1_PosN', 'Condition 1_RRAe', 'Condition 1_RRAn', 'Condition 1_RRNe', 'Condition 1_RRNn', 'Condition 2_Artery', 'Condition 2_Feedr', 'Condition 2_Norm', 'Condition 2_PosA', 'Condition 2_PosN', 'Condition 2_RRAe', 'Condition 2_RRAn', 'Condition 2_RRNn', 'Bldg Type_1Fam', 'Bldg Type_2fmCon', 'Bldg Type_Duplex', 'Bldg Type_Twnhs', 'Bldg Type_TwnhsE', 'House Style_1.5Fin', 'House Style_1.5Unf', 'House Style_1Story', 'House Style_2.5Fin', 'House Style_2.5Unf', 'House Style_2Story', 'House Style_SFoyer', 'House Style_SLvl', 'Roof Style_Flat', 'Roof Style_Gable', 'Roof Style_Gambrel', 'Roof Style_Hip', 'Roof Style_Mansard', 'Roof Style_Shed', 'Roof Matl_ClyTile', 'Roof Matl_CompShg', 'Roof Matl_Membran', 'Roof Matl_Metal', 'Roof Matl_Roll', 'Roof Matl_Tar&Grv', 'Roof Matl_WdShake', 'Roof Matl_WdShngl', 'Exterior 1st_AsbShng', 'Exterior 1st_AsphShn', 'Exterior 1st_BrkComm', 'Exterior 1st_BrkFace', 'Exterior 1st_CBlock', 'Exterior 1st_CemntBd', 'Exterior 1st_HdBoard', 'Exterior 1st_ImStucc', 'Exterior 1st_MetalSd', 'Exterior 1st_Plywood', 'Exterior 1st_PreCast', 'Exterior 1st_Stone', 'Exterior 1st_Stucco', 'Exterior 1st_VinylSd', 'Exterior 1st_Wd Sdng', 'Exterior 1st_WdShing', 'Exterior 2nd_AsbShng', 'Exterior 2nd_AsphShn', 'Exterior 2nd_Brk Cmn', 'Exterior 2nd_BrkFace', 'Exterior 2nd_CBlock', 'Exterior 2nd_CmentBd', 'Exterior 2nd_HdBoard', 'Exterior 2nd_ImStucc', 'Exterior 2nd_MetalSd', 'Exterior 2nd_Other', 'Exterior 2nd_Plywood', 'Exterior 2nd_PreCast', 'Exterior 2nd_Stone', 'Exterior 2nd_Stucco', 'Exterior 2nd_VinylSd', 'Exterior 2nd_Wd Sdng', 'Exterior 2nd_Wd Shng', 'Mas Vnr Type_BrkCmn', 'Mas Vnr Type_BrkFace', 'Mas Vnr Type_CBlock', 'Mas Vnr Type_Stone', 'Foundation_BrkTil', 'Foundation_CBlock', 'Foundation_PConc', 'Foundation_Slab', 'Foundation_Stone', 'Foundation_Wood', 'Heating_Floor', 'Heating_GasA', 'Heating_GasW', 'Heating_Grav', 'Heating_OthW', 'Heating_Wall', 'Central Air_N', 'Central Air_Y', 'Electrical_FuseA', 'Electrical_FuseF', 'Electrical_FuseP', 'Electrical_Mix', 'Electrical_SBrkr', 'Garage Type_2Types', 'Garage Type_Attchd', 'Garage Type_Basment', 'Garage Type_BuiltIn', 'Garage Type_CarPort', 'Garage Type_Detchd', 'Garage Finish_Fin', 'Garage Finish_RFn', 'Garage Finish_Unf', 'Paved Drive_N', 'Paved Drive_P', 'Paved Drive_Y', 'Sale Type_COD', 'Sale Type_CWD', 'Sale Type_Con', 'Sale Type_ConLD', 'Sale Type_ConLI', 'Sale Type_ConLw', 'Sale Type_New', 'Sale Type_Oth', 'Sale Type_VWD', 'Sale Type_WD ', 'Sale Condition_Abnorml', 'Sale Condition_AdjLand', 'Sale Condition_Alloca', 'Sale Condition_Family', 'Sale Condition_Normal', 'Sale Condition_Partial']
Tamanho do treino: (2051, 232)
Tamanho do teste: (879, 232)
Distribuição das classes no target:
Target
1 0.344222
0 0.338859
2 0.316919
Name: proportion, dtype: float64
#criando a target para classificar o preço das casas em baixa, média e alta
print(df['SalePrice'].describe())
df['Target'] = pd.qcut(
df['SalePrice'],
q=3,
labels=['Baixa', 'Média', 'Alta']
)
df['Target'] = df['Target'].map({'Baixa':0, 'Média':1, 'Alta':2}).astype('int')
print(df['Target'].value_counts())
x = df.drop(columns=['SalePrice', 'Target'])
y = df['Target']
features = x.columns.tolist()
print("Features usadas no modelo:\n", features)
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)
print("Tamanho do treino:", X_train.shape)
print("Tamanho do teste:", X_test.shape)
print("\nDistribuição das classes no target:")
print(y_train.value_counts(normalize=True)) -
As variáveis de entrada (features -> variáveis x) e saída (target -> variável y) foram definidas.
-
Split 70/30 para treino e teste, garantindo avaliação justa do modelo.
4. Treinamento do Modelo
O modelo de árvore de decisão foi treinado com os dados de treino.
#criação e treino do modelo de árvore de decisão
clf = DecisionTreeClassifier(random_state=42, max_depth=3, criterion='entropy') # você pode ajustar max_depth
clf.fit(X_train, y_train) -
Como critério de divisão, utilizou-se a entropia para medir a incerteza com base na teoria da informação (usado quando se quer interpretar a árvore em termos de bits de informação).
-
Utilizou-se o
DecisionTreeClassifiercom profundidade máxima de 3 para evitar overfitting. -
O modelo foi ajustado aos dados de treino.
5. Avaliação do Modelo
O desempenho do modelo foi avaliado com métricas de classificação e visualização da árvore.
#fazer previsões no conjunto de teste
y_pred = clf.predict(X_test)
#avaliação do modelo
print("Relatório de classificação:\n", classification_report(y_test, y_pred))
#visualização da árvore de decisão
plt.figure(figsize=(16,8))
plot_tree(clf, filled=True, fontsize=8, class_names=['Baixa','Média','Alta'])
plt.savefig('./docs/classes/arvore-de-decisao/img/arvore_decisao.png') -
O modelo foi avaliado por métricas como precisão, recall e F1-score.
-
A acurácia geral do modelo foi de 76%, indicando um bom desempenho na classificação das casas em baixa, média e alta renda.
-
A precisão e recall foram particularmente altos para a classe de alta renda, sugerindo que o modelo é eficaz em identificar casas de maior valor.
-
O F1-score para a classe de renda média foi o mais baixo, refletindo a dificuldade em distinguir propriedades com características intermediárias.
O modelo de árvore de decisão desenvolvido para classificar as casas da base AmesHousing em três categorias — baixa, média e alta renda — apresentou resultados bastante satisfatórios e consistentes. A acurácia global obtida foi de 76%, indicando que o modelo conseguiu aprender de forma eficiente os padrões que distinguem as faixas de preço dos imóveis a partir de suas características estruturais, de qualidade e localização.
A análise das métricas por classe mostra um comportamento coerente com a natureza do problema. As casas de alta renda foram as mais bem classificadas, com precision de 0,85 e recall de 0,88, evidenciando que o modelo identifica de forma muito precisa os imóveis de maior valor. As casas de baixa renda também apresentaram um bom desempenho, com métricas equilibradas de precision e recall em torno de 0,78. Já a faixa de renda média foi a mais desafiadora, com F1-score de 0,62, o que é esperado, pois essas propriedades possuem características intermediárias que se sobrepõem às das outras classes — tornando sua separação menos evidente.
A estrutura da árvore de decisão revela divisões bastante intuitivas. Os principais critérios de separação estão relacionados a variáveis como qualidade geral do imóvel (Overall Qual), área habitável (Gr Liv Area) e ano de construção (Year Built), fatores que historicamente são determinantes para o valor de mercado de uma casa. O modelo dividiu os dados de forma lógica: imóveis com menor qualidade e área ficaram concentrados nos nós que levam à classificação de baixa renda, enquanto casas de padrão superior e maior metragem foram alocadas nos ramos associados à alta renda. As subdivisões intermediárias formam os grupos de renda média, onde a entropia é mais alta, refletindo a maior mistura entre categorias.
5.1 Importância das Variáveis
A análise da importância das variáveis reforça a relevância dos atributos selecionados. A qualidade geral do imóvel (Overall Qual) foi a variável mais influente, seguida pela área habitável (Gr Liv Area) e pelo ano de construção (Year Built). Essas três variáveis juntas explicam uma parcela significativa da capacidade preditiva do modelo. Outras características, como o número de banheiros, a presença de garagem e a localização (Neighborhood), também tiveram impacto relevante, embora em menor escala.
5.2 Matriz de Confusão
Mais detalhadamente, a matriz de confusão revela que o modelo cometeu alguns erros de classificação, especialmente entre as classes de baixa e média renda. Houve uma quantidade considerável de casas de baixa renda que foram classificadas como média renda, o que pode ser atribuído à sobreposição das características dessas categorias. No entanto, os erros entre baixa e alta renda foram mínimos, indicando que o modelo é eficaz em distinguir extremos.
6. Relatório Final
Concluo que, visualmente, observa-se que a árvore possui nós bem definidos e relativamente puros, principalmente nas extremidades — o que reforça a boa capacidade de discriminação do modelo. O uso do critério de entropia permitiu identificar divisões que maximizam o ganho de informação, resultando em agrupamentos coerentes e interpretáveis. A predominância de variáveis de qualidade e tamanho nas decisões internas indica que o modelo está de fato capturando a lógica do mercado imobiliário, em que o preço é fortemente determinado por essas dimensões.
O modelo se mostra robusto, interpretável e eficiente para o objetivo proposto. Apesar de apresentar certa dificuldade na distinção entre as faixas médias de preço — algo comum em problemas desse tipo —, o desempenho global é satisfatório e demonstra que a árvore de decisão conseguiu aprender padrões reais e relevantes presentes nos dados.







