Skip to content

Á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:

2025-09-16T14:52:00.130874 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
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:

2025-09-16T14:52:00.308233 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
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:

2025-09-16T14:52:00.407973 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/
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 2025-09-16T14:52:00.812148 image/svg+xml Matplotlib v3.10.6, https://matplotlib.org/

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.