Decission Tree With Example


code, metrics, pruning, and interpretation

Decision Tree Code and Description

** Please type code into your code window,
instead of copying and pasting
-this can help you understand the process better **

Section 1: Imports

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report

We import dataset tools, split helpers, Decision Tree classifier, visualization helpers, and evaluation metrics.

Section 2: Load Data

data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = data.target

print("Shape:", X.shape)
print("Target classes:", np.unique(y))

Breast cancer dataset is a binary classification problem and a reliable starter dataset for tree modeling.

Section 3: Train-Test Split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

Stratified split keeps class proportions stable in both train and test sets.

Section 4: Baseline Decision Tree

dt_base = DecisionTreeClassifier(random_state=42)
dt_base.fit(X_train, y_train)

y_pred_base = dt_base.predict(X_test)
print("Baseline Accuracy:", round(accuracy_score(y_test, y_pred_base), 4))
print("Baseline Confusion Matrix:\n", confusion_matrix(y_test, y_pred_base))

Baseline tree gives a quick starting point and usually overfits when depth is not controlled.

Section 5: Hyperparameter Tuning

param_grid = {
    "max_depth": [3, 4, 5, 6, None],
    "min_samples_split": [2, 5, 10],
    "min_samples_leaf": [1, 2, 4, 8],
    "criterion": ["gini", "entropy"]
}

grid = GridSearchCV(
    DecisionTreeClassifier(random_state=42),
    param_grid=param_grid,
    scoring="f1",
    cv=5,
    n_jobs=-1
)
grid.fit(X_train, y_train)

best_dt = grid.best_estimator_
y_pred_best = best_dt.predict(X_test)

print("Best Params:", grid.best_params_)
print("Tuned Accuracy:", round(accuracy_score(y_test, y_pred_best), 4))
print("Classification Report:\n", classification_report(y_test, y_pred_best))

Grid search helps choose practical settings instead of manually guessing depth and leaf-size controls.

Section 6: Cost-Complexity Pruning (ccp_alpha)

path = best_dt.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas = path.ccp_alphas

alpha_scores = []
for alpha in ccp_alphas:
    dt_alpha = DecisionTreeClassifier(
        random_state=42,
        ccp_alpha=alpha,
        max_depth=grid.best_params_["max_depth"],
        min_samples_split=grid.best_params_["min_samples_split"],
        min_samples_leaf=grid.best_params_["min_samples_leaf"],
        criterion=grid.best_params_["criterion"]
    )
    dt_alpha.fit(X_train, y_train)
    alpha_scores.append((alpha, dt_alpha.score(X_test, y_test)))

best_alpha, best_alpha_score = max(alpha_scores, key=lambda x: x[1])
print("Best ccp_alpha:", best_alpha)
print("Best pruned accuracy:", round(best_alpha_score, 4))

Pruning removes weak branches and can improve generalization while making the tree easier to explain.

Section 7: Feature Importance and Rule Export

dt_final = DecisionTreeClassifier(
    random_state=42,
    ccp_alpha=best_alpha,
    max_depth=grid.best_params_["max_depth"],
    min_samples_split=grid.best_params_["min_samples_split"],
    min_samples_leaf=grid.best_params_["min_samples_leaf"],
    criterion=grid.best_params_["criterion"]
)
dt_final.fit(X_train, y_train)

importances = pd.Series(dt_final.feature_importances_, index=X.columns)
print(importances.sort_values(ascending=False).head(10))

print("\nTop rules (depth limited):")
print(export_text(dt_final, feature_names=list(X.columns), max_depth=3))

Feature importance highlights key split drivers; exported rules make model behavior human-readable.

Graphs and Analysis

Graph 1: Train vs Test Accuracy by Depth

Open PDF: Decision Tree Code output file
depths = list(range(1, 16))
train_scores = []
test_scores = []

for d in depths:
    model = DecisionTreeClassifier(max_depth=d, random_state=42)
    model.fit(X_train, y_train)
    train_scores.append(model.score(X_train, y_train))
    test_scores.append(model.score(X_test, y_test))

plt.figure(figsize=(8, 4.5))
plt.plot(depths, train_scores, marker="o", label="Train")
plt.plot(depths, test_scores, marker="s", label="Test")
plt.xlabel("max_depth")
plt.ylabel("Accuracy")
plt.title("Decision Tree: Depth vs Accuracy")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

This curve usually shows where extra depth stops helping test performance and starts overfitting.

Graph 2: Pruning Curve (ccp_alpha vs Test Accuracy)

alphas = [a for a, _ in alpha_scores]
scores = [s for _, s in alpha_scores]

plt.figure(figsize=(8, 4.5))
plt.plot(alphas, scores, marker="o")
plt.xlabel("ccp_alpha")
plt.ylabel("Test Accuracy")
plt.title("Cost-Complexity Pruning Path")
plt.grid(alpha=0.3)
plt.show()

Pick an alpha near peak score that also keeps the tree simpler.

Graph 3: Confusion Matrix of Final Pruned Tree

from sklearn.metrics import ConfusionMatrixDisplay

y_pred_final = dt_final.predict(X_test)
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred_final, display_labels=data.target_names, cmap="Blues", values_format="d"
)
plt.title("Decision Tree - Final Confusion Matrix")
plt.show()

Confusion matrix helps inspect which class still suffers errors after tuning and pruning.

Graph 5: Tree Structure (Top Levels)

plt.figure(figsize=(20, 10))
plot_tree(
    dt_final,
    feature_names=X.columns,
    class_names=data.target_names,
    filled=True,
    max_depth=3,
    fontsize=8
)
plt.title("Decision Tree Structure (depth limited for readability)")
plt.show()

This gives a direct visual of split conditions and leaf predictions for presentation and review.

Exercises for Practice

Exercise 1: Compare `criterion="gini"` vs `criterion="entropy"` with same depth constraints.

Exercise 2: Fix `max_depth` at 3, 5, 7, 10 and report train-test gap.

Exercise 3: Tune `min_samples_leaf` and explain how it changes overfit behavior.

Exercise 4: Build tree on only top 5 important features and compare metrics.

Exercise 5: Use class imbalance handling and compare recall for minority class.

Exercise 6: Export top rules and explain one false prediction using path logic.

Exercise 7: Compare final single tree against RandomForest baseline.

Other Best Datasets for Decision Tree Practice

1. Iris (`load_iris`): clean multiclass starter for split logic and tree visualization.

2. Wine (`load_wine`): multiclass dataset with richer feature interactions.

3. Digits (`load_digits`): higher-dimensional multiclass classification challenge.

4. California Housing (`fetch_california_housing`): strong option for Decision Tree Regressor workflow.

5. Diabetes (`load_diabetes`): compact regression dataset for error and pruning practice.

6. Titanic (OpenML `fetch_openml(\"titanic\")`): realistic classification with missing values and preprocessing needs.

Graph 4: Top Feature Importances (Same Dataset)

importances = pd.Series(dt_final.feature_importances_, index=X.columns)
top10 = importances.sort_values(ascending=False).head(10)

plt.figure(figsize=(8.5, 4.8))
top10.sort_values().plot(kind="barh", color="#42a5f5")
plt.xlabel("Feature Importance")
plt.title("Decision Tree: Top 10 Important Features")
plt.grid(axis="x", alpha=0.3)
plt.show()

This graph makes the same breast-cancer model easier to understand by showing which features dominate split decisions.

A practical Decission Tree workflow is: baseline fit, depth/leaf tuning, pruning, confusion-matrix validation, and rule-level interpretation before deployment.