Árvore de Decisão
Projeto Árvore de Decisão
Cars Purchase Decision
Este projeto tem como objetivo aplicar técnicas de Machine Learning para compreender os fatores que influenciam a decisão de compra de automóveis. A partir de um conjunto de dados com informações sobre idade, gênero e salário anual dos clientes, foi construída uma árvore de decisão capaz de classificar se um indivíduo provavelmente realizará a compra ou não.
Exploração dos Dados
Estatísticas Descritivas
Para o projeto foi utilizado o dataset Cars - Purchase Decision Dataset e contém detalhes de clientes que consideraram comprar um automóvel, juntamente com seus salários.
O conjunto de dados contém 1000 registros e 5 variáveis. A variável alvo é Purchased (0 = não comprou, 1 = comprou). Entre as variáveis explicativas, temos Gender (categórica), Age (numérica) e AnnualSalary (numérica).
Variáveis
-
User ID: Código do Cliente
-
Gender: Gênero do Cliente
-
Age: Idade do Cliente em anos
-
AnnualSalary: Salário anual do Cliente
-
Purchased: Se o cliente realizou a compra
Estatísticas Descritivas e Visualizações
O gráfico mostra a relação entre idade e salário dos clientes, destacando quem realizou a compra e quem não comprou:
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- ETAPA 1: Data Cleaning
df["Age"].fillna(df["Age"].median(), inplace=True)
df["Gender"].fillna(df["Gender"].mode()[0], inplace=True)
df["AnnualSalary"].fillna(df["AnnualSalary"].median(), inplace=True)
# --- ETAPA 2: Encoding
df["Gender"] = df["Gender"].map({"Male": 1, "Female": 0})
# --- ETAPA 3: Normalização
for col in ["Age", "AnnualSalary"]:
cmin, cmax = df[col].min(), df[col].max()
df[col] = 0.0 if cmax == cmin else (df[col] - cmin) / (cmax - cmin)
df0 = df[df["Purchased"] == 0]
df1 = df[df["Purchased"] == 1]
# --- PLOT: Dispersão Idade x Salário ---
fig, ax = plt.subplots(1, 1, figsize=(7, 5))
ax.scatter(
df0["Age"], df0["AnnualSalary"],
label="Não comprou (0)", alpha=0.4,
color="lightcoral", edgecolor="darkred", linewidth=0.8
)
ax.scatter(
df1["Age"], df1["AnnualSalary"],
label="Comprou (1)", alpha=0.4,
color="skyblue", edgecolor="navy", linewidth=0.8
)
ax.set_title("Idade x Salário por Decisão de Compra")
ax.set_xlabel("Idade")
ax.set_ylabel("Salário Anual")
ax.grid(linestyle="--", alpha=0.6)
ax.legend()
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
Info
A visualização deixa claro que idade e salário exercem influência relevante no comportamento de compra
O próximo gráfico apresenta a distribuição de clientes por gênero:
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- ETAPA 1: Data Cleaning
df["Gender"].fillna(df["Gender"].mode()[0], inplace=True)
counts = df["Gender"].value_counts()
# --- PLOT: Distribuição por Gênero ---
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
ax.bar(
counts.index, counts.values,
color=["pink", "skyblue"], edgecolor="lightcoral"
)
ax.set_title("Distribuição por Gênero")
ax.set_xlabel("Gênero")
ax.set_ylabel("Quantidade")
ax.grid(axis="y", linestyle="--", alpha=0.6)
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
Info
Observa-se que há uma leve predominância de mulheres no dataset.
O último gráfico apresenta a distribuição do salário anual dos clientes, permitindo visualizar a mediana, a dispersão dos valores e a presença de possíveis extremos:
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- ETAPA 1: Data Cleaning
df["AnnualSalary"].fillna(df["AnnualSalary"].median(), inplace=True)
# --- PLOT: Boxplot
fig, ax = plt.subplots(figsize=(7, 5))
bp = ax.boxplot(df["AnnualSalary"], patch_artist=True, widths=0.5)
for box in bp["boxes"]:
box.set(facecolor="skyblue", edgecolor="navy", linewidth=1.2)
for whisker in bp["whiskers"]:
whisker.set(color="navy", linewidth=1.2)
for cap in bp["caps"]:
cap.set(color="navy", linewidth=1.2)
for median in bp["medians"]:
median.set(color="darkred", linewidth=1.5)
ax.set_title("Distribuição do Salário Anual")
ax.set_ylabel("Salário Anual")
ax.set_xticks([])
ax.grid(axis="y", linestyle="--", alpha=0.6)
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
Info
O gráfico evidencia que a maior parte dos salários está concentrada em uma faixa intermediária, entre aproximadamente 50 mil e 90 mil, com a mediana em torno de 70 mil.
Pré-processamento
Pré-processamento de dados brutos deve ser a primeira etapa ao lidar com datasets de todos tamanhos.
Data Cleaning
O processo de data cleaning garante que o conjunto utilizado seja confiável e esteja livre de falhas que possam distorcer os resultados. Consiste em identificar e corrigir problemas como valores ausentes, dados inconsistentes ou informações que não fazem sentido. Essa limpeza permite que a base seja mais fiel à realidade e forneça condições adequadas para a construção de modelos de Machine Learning.
No código, a limpeza foi feita dessa forma: possíveis valores vazios em idade, gênero e salário foram preenchidos com informações representativas, como a mediana ou o valor mais frequente.
Gender | Age | AnnualSalary |
---|---|---|
Female | 36 | 63000 |
Male | 55 | 39000 |
Male | 25 | 59500 |
Female | 46 | 135500 |
Male | 32 | 77500 |
Female | 41 | 67500 |
Female | 47 | 42500 |
Male | 59 | 135500 |
Female | 53 | 90500 |
Male | 41 | 73500 |
import pandas as pd
def preprocess(df):
df['Age'].fillna(df['Age'].median(), inplace=True)
df['Gender'].fillna(df['Gender'].mode()[0], inplace=True)
df['AnnualSalary'].fillna(df['AnnualSalary'].median(), inplace=True)
features = ['Gender', 'Age', 'AnnualSalary']
return df[features]
df = pd.read_csv('https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv')
df = df.sample(n=10, random_state=42)
df = preprocess(df)
print(df.sample(n=10).to_markdown(index=False))
Encoding Categorical Variables
O processo de encoding de variáveis categóricas transforma informações em formato de texto em valores numéricos, permitindo que algoritmos de Machine Learning consigam utilizá-las em seus cálculos.
No código, o encoding foi aplicado à variável gênero, convertendo as categorias “Male” e “Female” em valores numéricos (1 e 0). Dessa forma, a base de dados mantém todas as colunas originais, mas agora com a variável categórica representada de maneira adequada para ser usada em algoritmos de classificação.
User ID | Gender | Age | AnnualSalary | Purchased |
---|---|---|---|---|
176 | 1 | 41 | 73500 | 0 |
448 | 1 | 59 | 135500 | 1 |
391 | 1 | 25 | 59500 | 0 |
623 | 0 | 47 | 42500 | 1 |
773 | 0 | 46 | 135500 | 0 |
413 | 0 | 53 | 90500 | 1 |
793 | 1 | 55 | 39000 | 1 |
836 | 0 | 36 | 63000 | 0 |
586 | 0 | 41 | 67500 | 0 |
651 | 1 | 32 | 77500 | 0 |
import pandas as pd
def preprocess(df):
# Limpeza
df['Age'].fillna(df['Age'].median(), inplace=True)
df['Gender'].fillna(df['Gender'].mode()[0], inplace=True)
df['AnnualSalary'].fillna(df['AnnualSalary'].median(), inplace=True)
# Encoding simples para Gender
df['Gender'] = df['Gender'].map({'Male': 1, 'Female': 0})
return df
df = pd.read_csv('https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv')
df = df.sample(n=10, random_state=42)
df = preprocess(df)
print(df.to_markdown(index=False))
Normalização
A normalização é o processo de reescalar os valores numéricos de forma que fiquem dentro de um intervalo fixo, normalmente entre 0 e 1. Isso facilita a comparação entre variáveis que possuem unidades ou magnitudes diferentes, evitando que atributos com valores muito altos dominem a análise.
No código, a normalização foi aplicada às colunas idade e salário anual, transformando seus valores para a faixa de 0 a 1 por meio do método Min-Max Scaling. Dessa forma, ambas as variáveis passam a estar na mesma escala, tornando o conjunto de dados mais consistente e adequado para a modelagem.
User ID | Gender | Age | AnnualSalary | Purchased |
---|---|---|---|---|
162 | Male | 0.6 | 0.301818 | 0 |
5 | Male | 0.511111 | 0.68 | 1 |
229 | Male | 0.866667 | 0.327273 | 1 |
273 | Male | 0.6 | 0.08 | 1 |
901 | Female | 0.377778 | 0.425455 | 0 |
50 | Male | 0.422222 | 0.276364 | 0 |
414 | Male | 0.444444 | 0.261818 | 0 |
533 | Male | 0.622222 | 0.0581818 | 1 |
578 | Female | 0.444444 | 0.345455 | 0 |
651 | Male | 0.311111 | 0.454545 | 0 |
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# Selecionar colunas numéricas para normalizar
features_to_normalize = ['Age', 'AnnualSalary']
# Inicializar o scaler
scaler = MinMaxScaler()
# Aplicar normalização e substituir no DataFrame
df[features_to_normalize] = scaler.fit_transform(df[features_to_normalize])
# Mostrar amostra dos dados normalizados
print(df.sample(10).to_markdown(index=False))
Divisão dos Dados
Após o pré-processamento, o conjunto de dados precisa ser separado em duas partes: uma para treinamento e outra para teste. Essa divisão é fundamental para que o modelo de Machine Learning aprenda padrões a partir de um grupo de exemplos e, depois, seja avaliado em dados que ainda não foram vistos. Dessa forma, é possível medir a capacidade de generalização do modelo e evitar que ele apenas memorize os exemplos fornecidos.
No código, os atributos escolhidos como preditores foram gênero, idade e salário anual, enquanto a variável-alvo foi Purchased, que indica se o cliente comprou ou não o produto. A divisão foi feita em 70% para treino e 30% para teste, garantindo que a proporção de clientes que compraram e não compraram fosse preservada em ambos os subconjuntos.
Tamanho treino: 700 Tamanho teste: 300
import pandas as pd
from sklearn.model_selection import train_test_split
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- Data Cleaning
df["Age"].fillna(df["Age"].median(), inplace=True)
df["Gender"].fillna(df["Gender"].mode()[0], inplace=True)
df["AnnualSalary"].fillna(df["AnnualSalary"].median(), inplace=True)
# --- Encoding
df["Gender"] = df["Gender"].map({"Male": 1, "Female": 0})
# --- Normalização
for col in ["Age", "AnnualSalary"]:
cmin, cmax = df[col].min(), df[col].max()
df[col] = 0.0 if cmax == cmin else (df[col] - cmin) / (cmax - cmin)
# --- Separar variáveis preditoras
X = df[["Gender", "Age", "AnnualSalary"]]
y = df["Purchased"]
# --- Divisão em treino e teste
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
print("Tamanho treino:", X_train.shape[0])
print("Tamanho teste:", X_test.shape[0])
Treinamento e Avaliação do Modelo
Por fim, esta é a árvore de decisão final:
Accuracy: 0.9033
Gender | Age | AnnualSalary | Purchased |
---|---|---|---|
Male | 41 | 73500 | 0 |
Male | 59 | 135500 | 1 |
Male | 25 | 59500 | 0 |
Female | 47 | 42500 | 1 |
Female | 46 | 135500 | 0 |
Female | 53 | 90500 | 1 |
Male | 55 | 39000 | 1 |
Female | 36 | 63000 | 0 |
Female | 41 | 67500 | 0 |
Male | 32 | 77500 | 0 |
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
from io import BytesIO
from sklearn import tree
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
# PREPROCESS
def preprocess(df):
# Data cleaning
df["Age"].fillna(df["Age"].median(), inplace=True)
df["Gender"].fillna(df["Gender"].mode()[0], inplace=True)
df["AnnualSalary"].fillna(df["AnnualSalary"].median(), inplace=True)
# Encoding
enc = LabelEncoder()
df["Gender"] = enc.fit_transform(df["Gender"]) # Female=0, Male=1
# Normalização Min–Max
for col in ["Age", "AnnualSalary"]:
cmin, cmax = df[col].min(), df[col].max()
df[col] = 0.0 if cmax == cmin else (df[col] - cmin) / (cmax - cmin)
# Features finais
features = ["Gender", "Age", "AnnualSalary"]
return df[features]
# CARREGAR DADOS
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# X (features) / y (target)
X = preprocess(df)
y = df["Purchased"]
# TRAIN / TEST SPLIT
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# DECISION TREE
clf = tree.DecisionTreeClassifier(max_depth=4, random_state=42)
clf.fit(X_train, y_train)
# AVALIAÇÃO
y_pred = clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"Accuracy: {acc:.4f}")
# PLOTAR ÁRVORE
plt.figure(figsize=(14, 10))
tree.plot_tree(
clf,
feature_names=X.columns.tolist(),
class_names=["Não comprou (0)", "Comprou (1)"],
filled=True, rounded=True, fontsize=9
)
buf = BytesIO()
plt.savefig(buf, format="svg", bbox_inches="tight", transparent=True)
buf.seek(0)
print(buf.getvalue().decode("utf-8"))
plt.close()
O modelo de árvore de decisão foi treinado com as variáveis idade, gênero e salário anual, atingindo uma acurácia de aproximadamente 90%. A análise da árvore mostra que idade e salário foram os principais fatores utilizados para separar compradores e não compradores, enquanto o gênero teve impacto secundário.
Conclusão
O projeto teve início com a exploração do dataset, etapa em que foi possível identificar padrões relevantes, como uma leve predominância do público feminino e a influência direta de idade e renda no comportamento de compra. Durante essa fase, também foi necessário lidar com valores ausentes e normalizar variáveis para tornar o conjunto adequado à modelagem.
Após o pré-processamento, o modelo de árvore de decisão foi treinado e alcançou uma acurácia em torno de 90%, indicando boa capacidade de identificar corretamente os clientes com maior ou menor propensão à compra. A análise da árvore revelou que idade e salário anual são os fatores que mais impactam as previsões, enquanto o gênero aparece em pontos específicos, mas com menor importância.
Em linhas gerais, o trabalho atendeu ao objetivo de desenvolver um protótipo preditivo baseado em dados demográficos e socioeconômicos. O modelo se mostrou eficiente e, com ajustes adicionais, tem potencial para ser expandido em aplicações mais completas no apoio a decisões de mercado.