Skip to content

Árvore de Decisão

Exploração de dados

A variável target foi criada a partir da coluna quality, classificando os vinhos em duas categorias:

0 (ruim): vinhos com qualidade menor que 5;

1 (bom): vinhos com qualidade maior ou igual a 5.

No gráfico abaixo, é possível observar um forte desbalanceamento entre as classes. A grande maioria dos vinhos foi classificada como “bons” (1), enquanto apenas uma pequena parcela foi classificada como “ruins” (0).

Resumo do conjunto de dados Total de linhas: 6.497 Total de colunas: 12 Variável de destino: quality (valores inteiros de 3 a 9) Formato de arquivo: CSV (UTF-8) Valores ausentes: Nenhum Duplicatas removidas: Sim

1 - 6251

0 - 246

GráficoRB

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import kagglehub
import os

from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

sns.set_style("whitegrid")
plt.rcParams["figure.figsize"] = (10,6)

# Baixar dataset
path = kagglehub.dataset_download("saeedomranpour/red-and-white-wine-quality")
print("Path to dataset files:", path)

print("\nArquivos dentro do dataset:")
print(os.listdir(path))

# Usar o arquivo cleaned
file_path = os.path.join(path, "wine_quality_merged.csv")

# Carregar dataset
df = pd.read_csv(file_path, index_col=0)
print("\nPrimeiros registros:")
print(df.head())

print("\nInformações gerais:")
print(df.info())

print("\nDistribuição da variável quality original:")
print(df["quality"].value_counts().sort_index())

# ======================
# Criar variável alvo (target)
# 1 = bom (quality >= 5), 0 = ruim (quality < 5)
# ======================
df["target"] = (df["quality"] >= 5).astype(int)

print("\nDistribuição após transformação para target:")
print(df["target"].value_counts())

sns.countplot(x="target", data=df)
plt.title("Distribuição da variável alvo (0=ruim, 1=bom)")
plt.show()

Distribuição da Qualidade do Vinho

GráficoRB

# Estatísticas descritivas
print("\nEstatísticas descritivas:")
print(df.describe())                       # Média, desvio padrão, min, max, quartis

# Visualização da distribuição da qualidade
sns.histplot(df["quality"], bins=7, kde=True, color=cor_vinho)
plt.title("Distribuição da Qualidade do Vinho")
plt.show()

É um histograma com curva KDE que mostra como as notas de qualidade se distribuem.

Exemplo do gráfico:

A maioria dos vinhos está entre 5 e 6 pontos.

Poucos vinhos têm nota 3 ou 8+.

A distribuição mostra que a maior parte dos vinhos está concentrada em notas médias (5-6). Isso ajuda a entender a base de dados e a justificar a criação da variável target binária (bom >=5, ruim <5).

Correlação entre Variáveis Numéricas

GráficoRB

# Mapa de correlação entre colunas numéricas
numeric_df = df.select_dtypes(include=np.number)   # Seleciona apenas colunas numéricas
sns.heatmap(numeric_df.corr(), cmap="Reds", annot=False)
plt.title("Mapa de Correlação entre Variáveis Numéricas")
plt.show()

heatmap de correlação mostra como cada variável se relaciona com as outras.

Os valores variam de -1 a 1:

1: correlação positiva perfeita (quando uma aumenta, a outra também aumenta)

-1: correlação negativa perfeita (uma aumenta e a outra diminui)

0: nenhuma correlação linear

Cores mais escuras indicam maior correlação positiva.

O mapa de correlação permite identificar relações lineares entre variáveis. Correlações altas podem indicar redundância de informação, enquanto correlações baixas indicam independência entre atributos.

Pré-processamento

1-Verifica se há valores nulos.

2-Define features (X) e variável alvo (y).

3-Converte variáveis categóricas em dummies (se tiver).

4-Divide em treino (70%) e teste (30%).

Tamanho treino: 4547 registros

Tamanho teste: 1950 registros

#pré processamento


# Verificar valores ausentes
print("\nValores ausentes:")
print(df.isnull().sum())

# Remover linhas com valores ausentes
df = df.dropna()

# Separar variáveis explicativas (X) e variável alvo (y)
X = df.drop(["quality", "target"], axis=1)  # Todas as colunas menos quality e target
y = df["target"]                             # Apenas a coluna target

# Transformar variáveis categóricas em variáveis dummy (0 ou 1)
X = pd.get_dummies(X, drop_first=True)       # drop_first=True evita multicolinearidade

# Normalizar os dados numéricos
scaler = StandardScaler()                    # Cria o objeto para padronização
X_scaled = scaler.fit_transform(X)           # Calcula média e desvio e transforma os dados


# 3. Divisão dos Dados


# Separar em treino (70%) e teste (30%), mantendo proporção da classe (stratify=y)
X_train, X_test, y_train, y_test = train_test_split(
    X_scaled, y, test_size=0.3, random_state=42, stratify=y
)

print(f"\nTamanho treino: {X_train.shape[0]} registros")
print(f"Tamanho teste: {X_test.shape[0]} registros")
# Converte variáveis categóricas em dummies (se tiver).
#pd.get_dummies(X) → cria colunas dummies para todas variáveis categóricas.
#drop_first=True → remove a primeira categoria de cada variável (para evitar multicolinearidade, ou seja, colunas redundantes).
# Divide em treino (70%) e teste (30%).

Explicação do Pré-processamento

Verificou-se a ausência de valores faltantes no dataset. As variáveis independentes foram selecionadas, excluindo-se quality e target. As variáveis categóricas foram codificadas por meio de dummies (one-hot encoding) para permitir a leitura pelo modelo. Por fim, o conjunto de dados foi dividido em treino (70%) e teste (30%), de forma estratificada para manter a proporção da variável alvo

Foi investigada a presença de valores zero nas colunas do dataset. A análise revelou que as colunas citric acid e a variável alvo target continham valores iguais a zero.

• Coluna citric acid: Um valor zero nesta coluna representa a ausência de ácido cítrico no vinho, o que é uma medição química válida e relevante para a análise da qualidade do vinho. Portanto, esses valores foram mantidos.

• Coluna target: A variável target foi criada para classificar os vinhos em 'ruim' (0) e 'bom' (1). O valor zero é uma das duas classes do nosso modelo de classificação e, portanto, é essencial para o treinamento e a avaliação do mesmo.

Ao remove valores ausentes e separa as features (X) da variável alvo (y). Colunas categóricas são transformadas em dummies binárias e todos os dados numéricos são normalizados com StandardScaler para manter a mesma escala, garantindo que o modelo de árvore de decisão aprenda de forma eficiente.

# Modelo Árvore de Decisão

model = DecisionTreeClassifier(random_state=42, max_depth=5)
model.fit(X_train, y_train)

# Avaliação
y_pred = model.predict(X_test)

print("\nAcurácia:", accuracy_score(y_test, y_pred))
print("\nRelatório de Classificação:\n", classification_report(y_test, y_pred))

Descrição

Treinamento do modelo- Cria uma árvore de decisão limitada a profundidade 5 (para não crescer demais) e treina com os dados de treino.

Avaliação- az previsões com os dados de teste e calcula métricas

O modelo foi avaliado nos dados de teste com métricas de desempenho. A acurácia foi de cerca de 96%, mostrando boa performance geral. A precisão, recall e F1-score foram altos para vinhos bons, mas baixos para vinhos ruins, devido ao desbalanceamento do conjunto de dados.

Acurácia: mede a proporção total de acertos do modelo. No experimento, o modelo apresentou uma acurácia de aproximadamente 96%, indicando bom desempenho geral.

Precisão: avalia, dentre as amostras previstas como “boas”, quantas realmente pertenciam a essa classe. O modelo mostrou alta precisão para a classe majoritária (vinhos bons).

Recall: mede a capacidade de identificar corretamente todos os casos da classe positiva. O recall também foi alto para vinhos bons, mas baixo para vinhos ruins, devido ao desbalanceamento da base.

F1-Score: combina precisão e recall em uma única métrica. Os valores foram satisfatórios para vinhos bons, mas menores para vinhos ruins, reforçando a dificuldade do modelo em prever a classe minoritária.

Matriz

# Matriz de confusão
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues")
plt.title("Matriz de Confusão")
plt.xlabel("Predito")
plt.ylabel("Real")
plt.show()

Descrição Matriz

A matriz de confusão mostra os seguintes resultados:

Verdadeiro Negativo (TN) = 5 → vinhos ruins corretamente classificados como ruins.

Falso Positivo (FP) = 69 → vinhos ruins incorretamente classificados como bons.

Falso Negativo (FN) = 6 → vinhos bons incorretamente classificados como ruins.

Verdadeiro Positivo (TP) = 1870 → vinhos bons corretamente classificados como bons.

Isso indica que o modelo tem ótimo desempenho na identificação de vinhos bons (classe 1), conseguindo classificar corretamente a grande maioria desses casos. No entanto, o desempenho é muito baixo para vinhos ruins (classe 0), já que praticamente não consegue reconhecê-los.

Esse comportamento está diretamente relacionado ao desbalanceamento do conjunto de dados, onde os vinhos bons são muito mais numerosos que os ruins. Como consequência, o modelo "aprende" a priorizar a classe majoritária (bons) e tem dificuldade em identificar a classe minoritária (ruins).

Importância das Variáveis na Árvore

arvore

``` python
# Importância das variáveis
importances = model.feature_importances_                # Extrai importância de cada feature
feat_imp = pd.Series(importances, index=X.columns).sort_values(ascending=False)

# Gráfico das variáveis mais importantes
sns.barplot(x=feat_imp, y=feat_imp.index, color=cor_vinho)
plt.title("Importância das Variáveis na Árvore")
plt.show()
```

O gráfico mostra quais atributos mais influenciam a decisão do modelo.

Cada barra representa uma variável do dataset; quanto maior a barra, mais importante ela é para classificar um vinho como bom ou ruim.

Exemplo do gráfico:

free sulfur dioxide e volatile acidity são as mais importantes.

Variáveis como residual sugar e density têm pouca ou nenhuma importância para a árvore.

O modelo identifica que a quantidade de dióxido de enxofre livre e a acidez volátil são os principais fatores que determinam se um vinho é classificado como bom ou ruim. Outras variáveis têm menor influência.

Árvore de Decisão

arvore

``` python
# Visualizar a árvore
plt.figure(figsize=(18,10))
plot_tree(model, feature_names=X.columns, class_names=["ruim","bom"], filled=True)
plt.show()
```

Estrutura da Árvore

• Nó Raiz: O nó superior da árvore é o nó raiz, que inicia o processo de tomada de decisão. Neste caso, a primeira divisão é baseada na característica volatile acidity.

• Nós Internos: Cada nó interno (não folha) contém uma condição de teste (por exemplo, volatile acidity <= 0.39). Se a condição for verdadeira, o caminho da esquerda é seguido; caso contrário, o caminho da direita é seguido.

• Nós Folha: Os nós folha são os nós terminais da árvore e contêm a previsão da classe (class = bom ou class = ruim). A cor dos nós (azul para 'bom' e laranja para 'ruim') indica a classe majoritária naquele nó.

Interpretação dos Nós

Cada nó na árvore exibe as seguintes informações:

• Condição de Divisão: A característica e o valor de corte usados para dividir os dados (ex: volatile acidity <= 0.39).

• gini: O coeficiente de Gini, que mede a impureza do nó. Um valor de Gini próximo de 0 indica um nó mais puro (ou seja, a maioria das amostras pertence a uma única classe).

• samples: O número de amostras de treinamento que atingiram aquele nó.

• value: A contagem de amostras para cada classe ([ruim, bom]) naquele nó. Por exemplo, value = [1750, 4747] significa que o nó contém 1750 amostras da classe 'ruim' e 4747 amostras da classe 'bom'.

• class: A classe majoritária no nó, que seria a previsão se a decisão terminasse ali.

Conclusão final - Relatório de Classificação

Accuracy (Acurácia): 96% → parece muito bom, mas é enganador.

Precision e Recall para classe 0 (ruim):

Precision = 0.45 → Quando ele diz que é ruim, só 45% estão realmente corretos.

Recall = 0.07 → Ele só encontra 7% dos vinhos ruins!

Classe 1 (bom): quase perfeito, acerta praticamente todos.

Conclusão: o modelo é ótimo para prever vinhos bons, mas péssimo para prever vinhos ruins.