Lesson 16 of 30

Chapter 16 — Decision Tree Classification

Chapter overview

Decision tree classification turns data into a hierarchy of if-then rules. At every internal node, the algorithm selects a feature and threshold that separates the training labels as effectively as possible. A new observation follows one branch at a time until it reaches a leaf, where the tree returns a predicted class and an estimated class probability.

Trees are popular because their logic can be displayed, they capture nonlinear relationships and interactions, and they usually do not need feature scaling. Their flexibility is also their central weakness: an unrestricted tree can memorize small details, change substantially after a modest data perturbation, and generalize poorly. This chapter therefore treats complexity control and pruning as essential parts of tree modeling.

CENTRAL IDEA  A decision tree repeatedly asks a simple question. The quality of the model depends on which questions are chosen, when splitting stops, and whether weak branches are removed.

 

Learning objectives

  • Identify the root node, internal nodes, decision rules, branches, and leaf nodes.
  • Explain how a classification tree converts leaf class counts into predictions and probabilities.
  • Calculate and interpret Gini impurity, entropy, weighted impurity, and information gain.
  • Explain how feature thresholds are evaluated during greedy recursive partitioning.
  • Control complexity with max_depth, min_samples_split, min_samples_leaf, and max_leaf_nodes.
  • Use cost-complexity pruning and validation data to select ccp_alpha.
  • Visualize a fitted tree and trace the decision path for individual observations.
  • Recognize the advantages, limitations, and common failure modes of a single decision tree.

Chapter map

Table 16.1. Chapter structure

Section

Focus

Student outcome

16.1

Tree structure

Read nodes, rules, branches, leaves, classes, and probabilities

16.2

Split selection

Compare candidate thresholds with impurity reduction

16.3

Complexity

Regularize growth and prune weak branches

16.4

Advantages

Identify situations where trees are useful

16.5

Limitations

Recognize variance, instability, and greedy-search risks

Lab

Train, visualize, pruneSelect a tree without using the test set for tuning

 

16.1 Tree structure

A classification tree organizes decisions as a top-down hierarchy. Every observation begins at the same root, follows rules through internal nodes, and ends in exactly one leaf.

Figure 16.1. Anatomy of a small classification tree

ROOT NODE

age <= 42.5

   

LEFT INTERNAL NODE

income <= 38,000

RIGHT INTERNAL NODE

account_age <= 3.5

  

LEAF A

class 0
P = 0.86

LEAF B

class 1
P = 0.67

LEAF C

class 1
P = 0.78

LEAF D

class 0
P = 0.60

    

 

Root node

The root node contains every training observation available to the tree. Its rule creates the first and usually most globally influential partition. A root such as mean radius <= 15.05 sends observations satisfying the condition to the left child and the remaining observations to the right child. The first rule is not automatically the most important causal factor; it is simply the split that best improves the chosen training criterion at that moment.

Internal node and decision rule

An internal node is a nonterminal region that can still be divided. Its decision rule normally has the form feature <= threshold for continuous features. The rule is local: it is evaluated only for observations that have already reached that node. This is why the same feature may appear more than once and why a feature can have different effects in different regions of the tree.

INTERACTION WITHOUT A PRODUCT TERM  If the tree checks feature B only after feature A sends an observation down one branch, the effect of B depends on A. The hierarchy therefore represents a feature interaction naturally.

 

Branches

A branch is the connection created by an outcome of a decision rule. In a binary tree, the left branch conventionally represents a true <= condition and the right branch represents the complementary > condition. A complete root-to-leaf path is a conjunction of rules: all conditions along the path must hold for the observation to enter that leaf.

Leaf node, predicted class, and class probability

A leaf node is terminal: no additional split is applied. The predicted class is normally the class with the largest training count, or weighted count, among observations in that leaf. The class probability is estimated from the class proportions inside the leaf. With 30 training observations in a leaf, of which 24 are class 1 and 6 are class 0, the estimated probability of class 1 is 24 / 30 = 0.80.

LEAF CLASS PROBABILITY
P(class = k | leaf m) = n(m,k) / n(m)

n(m,k) is the number of training observations of class k in leaf m; n(m) is the total number in that leaf.

 

PROBABILITY CAUTION  A leaf proportion is a model estimate, not a guarantee. Very small leaves can produce extreme probabilities such as 0 or 1 even when uncertainty is substantial. Evaluate probability calibration separately when probabilities drive decisions.

 

Trace one prediction

PYTHON  •   Train a small tree and inspect predictions

from sklearn.datasets import load_iris

from sklearn.model_selection import train_test_split

from sklearn.tree import DecisionTreeClassifier

 

data = load_iris(as_frame=True)

X_train, X_test, y_train, y_test = train_test_split(

    data.data,

    data.target,

    test_size=0.25,

    random_state=42,

    stratify=data.target,

)

 

PYTHON  •   Train a small tree and inspect predictions — continued

 

tree_model = DecisionTreeClassifier(

    max_depth=3,

    min_samples_leaf=3,

    random_state=42,

)

tree_model.fit(X_train, y_train)

 

query = X_test.iloc[[0]]

print("Predicted class:", tree_model.predict(query)[0])

print("Class probabilities:", tree_model.predict_proba(query)[0])

 

Read the learned rules as text

PYTHON  •   Export an interpretable rule listing

from sklearn.tree import export_text

 

rules = export_text(

    tree_model,

    feature_names=list(X_train.columns),

    decimals=2,

)

print(rules)

 

Table 16.2. Reading a classification tree

Element

Question it answers

Interpretation

Root

Where does every case begin?The full training population before any split

Internal node

What local question is asked?A subset that may still be partitioned

Rule

How is the subset divided?A feature-threshold condition

Branch

Which outcome occurred?True/false route from a rule

Leaf

Where does the path end?Terminal subgroup with a prediction

Predicted class

Which label wins?Largest class count or weighted count

Probability

How mixed is the leaf?Class proportion among leaf training cases

 

16.2 How a tree selects splits

At each node, the algorithm compares candidate feature-threshold pairs and chooses a partition that produces purer child nodes. This is a greedy decision: it optimizes the current split without solving for the globally best possible tree.

Figure 16.2. Greedy split selection at one node

1  CANDIDATES

2  PARTITIONS

3  SCORE

4  CHOOSE

Feature-threshold pairs

Left and right child groups

Weighted child impurity

Largest impurity reduction

 

Purity and impurity

A node is pure when all training observations inside it have the same class label. Impurity increases as the class distribution becomes more mixed. A useful split decreases impurity by separating observations into children whose class distributions are more homogeneous than the parent distribution.

Gini impurity

Gini impurity can be interpreted as the probability of a label disagreement under a simple random-label thought experiment based on the node proportions. It is zero for a pure node. For a balanced binary node with proportions 0.5 and 0.5, Gini impurity is 0.5, its binary maximum.

GINI IMPURITY
Gini(m) = 1 - Σ p(m,k)²

p(m,k) is the proportion of class k among the observations that reached node m.

 

Entropy

Entropy measures uncertainty in the class distribution. A pure node has entropy zero because its label is certain. A balanced binary node has maximum entropy of 1 bit when logarithms use base 2. Entropy places a different numerical emphasis on mixed distributions than Gini impurity, but the two criteria often produce similar trees.

ENTROPY
Entropy(m) = - Σ p(m,k) log₂ p(m,k)

Terms with p(m,k) = 0 contribute zero. In scikit-learn, entropy and log_loss both use Shannon information gain.

 

Worked impurity example

Suppose a parent node contains 10 observations: 6 positive and 4 negative. Candidate split A produces a left child with 4 positive and 0 negative observations and a right child with 2 positive and 4 negative observations. The parent Gini impurity is 0.48. The left child impurity is 0, and the right child impurity is approximately 0.444. Weighting by child sizes gives 0.267, so the Gini reduction is approximately 0.213.

Table 16.3. Manual Gini calculation

Node

Class counts

Gini

Weight

Weighted contribution

Parent

6 positive / 4 negative

0.480

1.00

0.480

Left

4 positive / 0 negative

0.000

0.40

0.000

Right

2 positive / 4 negative

0.444

0.60

0.267

Children total

1.00

0.267

Reduction

parent - children

0.213

 

PYTHON  •   Verify Gini and entropy numerically

import numpy as np

 

def gini(counts):

    proportions = np.array(counts) / np.sum(counts)

    return1 - np.sum(proportions ** 2)

 

def entropy(counts):

    proportions = np.array(counts) / np.sum(counts)

    nonzero = proportions[proportions > 0]

    return -np.sum(nonzero * np.log2(nonzero))

 

PYTHON  •   Verify Gini and entropy numerically — continued

 

parent_gini = gini([6,  4])

left_gini = gini([4,  0])

right_gini = gini([2,  4])

weighted_children = 0.4 * left_gini + 0.6 * right_gini

 

print("Parent Gini:"round(parent_gini, 3))

print("Weighted child Gini:"round(weighted_children,  3))

print("Gini reduction:"round(parent_gini - weighted_children, 3))

print("Parent entropy:"round(entropy([64]), 3))

 

Weighted impurity and information gain

A candidate split must be evaluated using both children. The child impurities are weighted by the proportion of node observations sent to each child; otherwise a tiny pure child could make a poor split look attractive. Information gain is the parent impurity minus the weighted child impurity. The tree selects a candidate with the largest improvement under the configured criterion and constraints.

WEIGHTED CHILD IMPURITY
I(children) = (nL / n) I(L) + (nR / n) I(R)

nL and nR are child sample counts and n is the parent sample count.

 

IMPURITY REDUCTION
Gain = I(parent) - I(children)

A larger positive value indicates a more useful local split under the chosen criterion.

 

Feature thresholds

For a continuous feature, candidate thresholds are considered between ordered training values that reach the current node. A threshold partitions the cases into feature <= threshold and feature > threshold. The selected threshold depends on the labels and on any sample or class weights; it is not simply the feature mean or median.

THRESHOLDS ARE DATA-DEPENDENT  A printed threshold may contain decimals even when the observed feature values are integers because a midpoint between adjacent candidate values can define the same partition.

 

Gini versus entropy

Table 16.4. Comparing classification criteria

Aspect

Gini

Entropy / log loss

Formula

1 - sum of squared class proportionsNegative sum of p log p

Pure node

0

0

Binary maximum

0.5

1 bit with log base 2

Typical result

Often similar to entropyMay choose a different split in close cases
scikit-learn value

criterion='gini'

criterion='entropy' or 'log_loss'

 

PYTHON  •   Compare Gini and entropy on the same split

from sklearn.tree import DecisionTreeClassifier

 

for criterion in ["gini""entropy"]:

    model = DecisionTreeClassifier(

        criterion=criterion,

        max_depth=4,

        min_samples_leaf=4,

        random_state=42,

    )

    model.fit(X_train, y_train)

    print(

        criterion,

        "depth=", model.get_depth(),

        "leaves=", model.get_n_leaves(),

        "test_accuracy="round(model.score(X_test, y_test), 3),

    )

 

Recursive partitioning is greedy

After choosing the best available split at the root, the algorithm repeats the search independently inside each child. It does not usually revisit the root after discovering later branches. This greedy strategy makes tree induction computationally practical, but a locally best early decision may prevent a better overall structure. Different training samples or equal-quality candidate splits can therefore lead to different trees.

16.3 Tree complexity

Tree growth must be controlled. Complexity parameters can stop weak or overly specific branches before they are created, while cost-complexity pruning removes branches after an initial tree has been grown.

Figure 16.3. Controlling tree complexity

UNRESTRICTED

PRE-PRUNED

POST-PRUNED

Many deep branches
Low training error
High variance risk

Growth stops early
Depth and sample rules
Simpler from the start

Grow, then remove weak branches
ccp_alpha controls penalty
Validate the final subtree

 

Maximum depth

max_depth limits the number of successive decisions from the root to the deepest leaf. A shallow tree is easier to explain and normally has lower variance, but it can miss real structure. An unrestricted depth allows the algorithm to continue until another stopping condition applies and can produce a large, highly specific tree.

Minimum samples per split

min_samples_split specifies how many training observations an internal node must contain before it may be split. Raising the value prevents the algorithm from dividing very small nodes. It does not guarantee large leaves by itself because a permitted split can still send only a few observations to one child.

Minimum samples per leaf

min_samples_leaf requires each resulting leaf to contain at least a specified number of training observations. This directly prevents tiny terminal regions and usually smooths class probability estimates. Both min_samples_split and min_samples_leaf can be integers or fractions of the training size in scikit-learn.

Maximum number of leaf nodes

max_leaf_nodes places a global limit on the number of terminal regions. In scikit-learn, the tree grows in best-first fashion under this constraint, prioritizing nodes with the strongest relative impurity reduction. Limiting leaves can be more directly connected to the number of distinct rule-based segments than limiting depth.

Table 16.5. Main complexity controls

Parameter

What it limits

Increasing regularization

max_depth

Longest root-to-leaf pathUse a smaller value
min_samples_splitAbility to split small nodesUse a larger value

min_samples_leaf

Smallest permitted terminal groupUse a larger value

max_leaf_nodes

Total terminal regionsUse a smaller value
min_impurity_decreaseMinimum gain required to splitUse a larger value

ccp_alpha

Post-pruning penaltyUse a larger value

 

Study depth and leaf-size effects

PYTHON  •   Create a controlled complexity comparison

import pandas as pd

from sklearn.tree import DecisionTreeClassifier

 

records = []

for max_depth in [23,  46,  None]:

    for min_leaf in [15,  15]:

        model = DecisionTreeClassifier(

            max_depth=max_depth,

            min_samples_leaf=min_leaf,

            random_state=42,

        )

        model.fit(X_train, y_train)

 

PYTHON  •   Create a controlled complexity comparison — continued

        records.append({

            "max_depth": max_depth,

            "min_samples_leaf": min_leaf,

            "tree_depth": model.get_depth(),

            "leaves": model.get_n_leaves(),

            "train_accuracy": model.score(X_train, y_train),

            "test_accuracy": model.score(X_test, y_test),

        })

 

comparison = pd.DataFrame(records)

print(comparison.round(3))

 

Cost-complexity pruning

Minimal cost-complexity pruning balances empirical fit against the size of the subtree. scikit-learn exposes this trade-off through ccp_alpha. With ccp_alpha = 0, no cost-complexity pruning is applied. As alpha increases, branches with insufficient improvement relative to their complexity are removed, so node count and depth generally decrease in steps.

COST-COMPLEXITY OBJECTIVE
Rα(T) = R(T) + α |leaves(T)|

R(T) measures leaf impurity, |leaves(T)| measures subtree size, and alpha controls the complexity penalty.

 

Selecting a pruning strength

SELECTION RULE  Do not choose ccp_alpha by maximizing the test score. Generate candidates on training data, compare them with validation or cross-validation, freeze the choice, and evaluate the test set once.

 

PYTHON  •   Generate candidate pruning strengths

from sklearn.tree import DecisionTreeClassifier

 

unpruned = DecisionTreeClassifier(random_state=42)

path = unpruned.cost_complexity_pruning_path(X_train, y_train)

 

# The final alpha normally collapses the tree to one root node.

candidate_alphas = path.ccp_alphas[:-1]

print("Number of candidate alphas:"len(candidate_alphas))

print("First candidates:", candidate_alphas[:5])

 

Pre-pruning versus post-pruning

Table 16.6. Complexity-control strategies

Approach

Mechanism

Strength

Caution

Pre-pruning

Stop growth with depth, sample, leaf, or gain limitsFast and directly controls the initial treeA useful branch may be stopped too early

Post-pruning

Grow a tree and remove weak branches with ccp_alphaCompares nested subtrees along a pruning pathAlpha still requires honest validation

Combined

Use sensible growth limits plus ccp_alphaCan prevent extreme trees before pruningMore choices increase tuning complexity

 

16.4 Advantages

A single decision tree is often a valuable first nonlinear classifier because its mechanism is transparent, its preprocessing demands are modest, and its learned rules can be inspected directly.

Easy to visualize

A small tree can be drawn as a flow of rules from the root to leaves. The diagram can show feature thresholds, impurity, sample counts, class distributions, and predicted classes. This visibility supports teaching, debugging, stakeholder discussion, and discovery of suspicious leakage features. Very large trees, however, cease to be meaningfully interpretable even if they remain technically drawable.

Nonlinear relationships and feature interactions

Axis-aligned rules divide feature space into regions, allowing a tree to represent nonlinear class boundaries. Hierarchical rules also create interactions: a threshold for one feature may matter only after another feature has placed an observation in a specific branch. No explicit polynomial or interaction term is required.

Scaling is usually unnecessary

A tree compares one feature with a threshold at a time. Multiplying a feature by a positive constant changes the numerical threshold but does not change the ordering of observations, so standardization normally does not improve the split geometry. This differs from KNN, support vector machines, and regularized linear models, whose calculations depend directly on feature scale.

NO SCALING DOES NOT MEAN NO PREPROCESSING  Raw text labels and inconsistent categories still require preparation. Missing values, categorical encoding, invalid measurements, leakage, and train-test discipline remain important.

 

Mixed feature patterns

After appropriate encoding, a tree can combine binary indicators, counts, continuous measurements, ordinal values, and one-hot encoded categories in different branches. It can also ignore features that do not provide useful impurity reduction. This flexibility is especially attractive for tabular problems containing thresholds, exceptions, and conditional rules.

Table 16.7. Practical advantages with caveats

Advantage

Why it matters

Boundary of the advantage

Visual rules

Supports explanation and debuggingOnly small trees remain easy to read
Nonlinear structureCaptures thresholds and irregular regionsAxis-aligned splits may need many nodes

Interactions

Conditional effects arise naturallyInteractions can be sample-specific
No standardizationSimplifies numeric preprocessingCategories and missing data still need care

Fast prediction

Each case follows one root-to-leaf pathDeep trees and large batches still have cost

 

Verify scale invariance

PYTHON  •   Confirm invariance to a positive scale change

import pandas as pd

from sklearn.tree import DecisionTreeClassifier

 

X_scaled_copy = X_train.copy()

X_scaled_copy.iloc[:,  0] = X_scaled_copy.iloc[:, 0] * 1000

 

original_tree = DecisionTreeClassifier(max_depth=4, random_state=42)

rescaled_tree = DecisionTreeClassifier(max_depth=4, random_state=42)

 

original_tree.fit(X_train, y_train)

rescaled_tree.fit(X_scaled_copy, y_train)

 

PYTHON  •   Confirm invariance to a positive scale change — continued

 

query_original = X_test.copy()

query_rescaled = X_test.copy()

query_rescaled.iloc[:,  0] = query_rescaled.iloc[:, 0] * 1000

 

same_predictions = (

    original_tree.predict(query_original)

    == rescaled_tree.predict(query_rescaled)

).all()

print("Same predictions:", same_predictions)

 

16.5 Limitations

The interpretability of a small tree should not obscure the statistical weaknesses of a single deep tree. High variance, instability, greedy search, and piecewise-constant predictions require careful validation.

High variance and overfitting

An unrestricted tree can keep splitting until leaves are pure or too small to divide. Training accuracy may then approach 100%, while validation accuracy stops improving or declines. The tree has learned local accidents, mislabeled observations, and sampling noise rather than stable structure. Complexity controls reduce this variance at the cost of added bias.

Instability to small data changes

A small change in the training sample can alter the best root split. Because every later split depends on the earlier hierarchy, the entire tree may reorganize. Two trees with similar predictive accuracy can therefore present noticeably different rules and feature importances. Fixing random_state improves reproducibility for a given dataset, but it does not remove sampling instability.

Greedy optimization

The algorithm selects the best current split and recurses. It does not explore every possible future tree because exhaustive global search would be impractical. A locally strong split can block a better later structure, and correlated features may substitute for one another depending on small criterion differences.

Additional practical limitations

  • Axis-aligned partitions: complex diagonal boundaries may require many step-like regions.
  • Biased importance measures: impurity-based importance can favor variables with many possible split points.
  • Weak probability estimates: small or pure leaves can produce overconfident class proportions.
  • Poor extrapolation: a tree predicts from existing leaf regions rather than extending a smooth trend.
  • Class imbalance: impurity improvement and majority-class leaves can under-serve rare classes unless metrics and weights are chosen carefully.

Table 16.8. Diagnosing common tree problems

Failure signal

Likely cause

Possible response

Training score far above validationTree is too flexibleLimit depth/leaves, enlarge leaves, or prune
Rules change across resamplesHigh sampling varianceCross-validate stability or use an ensemble
Many tiny pure leaves

Memorization

Increase min_samples_leaf or ccp_alpha
One feature dominates importanceLeakage, many thresholds, or correlationAudit availability and use permutation importance
Minority recall is weakImbalance or unsuitable thresholdingUse class-aware metrics and consider class_weight
Tree is accurate but unreadableExcessive structural complexityPrune or provide constrained summary rules

 

Measure instability with repeated splits

PYTHON  •   Compare root features across random splits

from collections import Counter

from sklearn.model_selection import train_test_split

from sklearn.tree import DecisionTreeClassifier

 

root_features = []

for seed inrange(20):

    X_part, _, y_part, _ = train_test_split(

        data.data,

        data.target,

        test_size=0.25,

 

PYTHON  •   Compare root features across random splits — continued

        random_state=seed,

        stratify=data.target,

    )

    model = DecisionTreeClassifier(max_depth=4, random_state=42)

    model.fit(X_part, y_part)

    root_index = model.tree_.feature[0]

    root_features.append(data.feature_names[root_index])

 

print(Counter(root_features))

 

MODEL-SELECTION PERSPECTIVE  A constrained tree is a strong interpretable candidate, but its validation score, stability, subgroup behavior, probability quality, and operational cost should be compared fairly with other model families.

 

Practical lab — Train, visualize, and prune a decision tree

Students build a classification tree for the Breast Cancer Wisconsin diagnostic dataset, compare training and validation performance, visualize an initial tree, generate a cost-complexity pruning path, select ccp_alpha without touching the test set, refit the chosen tree, and report final test results. The dataset is educational and must not be treated as a clinical decision system.

Lab objectives

  • Create reproducible 60/20/20 training, validation, and test subsets with stratification.
  • Train an unrestricted baseline and inspect its depth, leaves, nodes, and accuracy gap.
  • Visualize the upper levels of the tree with feature and class names.
  • Generate candidate ccp_alpha values from the training data only.
  • Compare subtree size, training accuracy, and validation accuracy across alpha values.
  • Select the simplest best-validation tree and freeze its configuration.
  • Refit on development data and evaluate once on the untouched test set.
  • Trace one test observation through the final tree and inspect feature importance cautiously.

Experimental design

Table 16.9. Practical lab design

Element

Choice

Reason

Dataset

Breast Cancer Wisconsin (diagnostic)Built-in numerical binary-classification data

Split

60% train / 20% validation / 20% testSeparate model selection from final evaluation

Stratification

Use y in both splitsPreserve malignant/benign proportions approximately

Scaling

None

Tree thresholds depend on order, not feature magnitude

Selection

Validation accuracy, then fewer nodesPrefer a simpler tree when the best score ties

Random state

42

Reproducible splitting and tie behavior

 

IMPORTANT  Accuracy is used here to isolate the pruning exercise. A real health-related application requires clinically appropriate metrics, external validation, calibration analysis, and expert oversight.

 

Step 1 — Load and inspect the data

PYTHON  •   Load the dataset and required tools

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

 

from sklearn.datasets import load_breast_cancer

from sklearn.metrics import classification_report, confusion_matrix

from sklearn.model_selection import train_test_split

from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text

 

data = load_breast_cancer(as_frame=True)

X = data.data

y = data.target

 

print("Shape:", X.shape)

print("Class names:"list(data.target_names))

print(y.value_counts().sort_index())

 

In this dataset, target 0 represents malignant and target 1 represents benign. Attach these meanings to every class-specific result so that recall or error counts are not interpreted for the wrong class.

Step 2 — Create protected subsets

PYTHON  •   Create 60/20/20 stratified subsets

X_train, X_temp, y_train, y_temp = train_test_split(

    X,

    y,

    test_size=0.40,

    random_state=42,

    stratify=y,

)

 

X_valid, X_test, y_valid, y_test = train_test_split(

    X_temp,

 

PYTHON  •   Create 60/20/20 stratified subsets — continued

    y_temp,

    test_size=0.50,

    random_state=42,

    stratify=y_temp,

)

 

print("Train:", X_train.shape)

print("Validation:", X_valid.shape)

print("Test:", X_test.shape)

 

Step 3 — Train an unrestricted baseline

PYTHON  •   Measure the initial overfitting gap

baseline_tree = DecisionTreeClassifier(random_state=42)

baseline_tree.fit(X_train, y_train)

 

print("Depth:", baseline_tree.get_depth())

print("Leaves:", baseline_tree.get_n_leaves())

print("Nodes:", baseline_tree.tree_.node_count)

print("Training accuracy:"round(baseline_tree.score(X_train, y_train), 3))

print("Validation accuracy:"round(baseline_tree.score(X_valid, y_valid), 3))

 

INTERPRETATION PROMPT  A perfect training score is not proof of a good classifier. Compare it with validation performance and inspect how many nodes were needed to achieve it.

 

Step 4 — Visualize the upper tree

PYTHON  •   Plot the first three levels

plt.figure(figsize=(168))

plot_tree(

    baseline_tree,

    max_depth=3,

    feature_names=list(X.columns),

    class_names=list(data.target_names),

    filled=True,

    rounded=True,

    proportion=True,

    precision=2,

    fontsize=8,

)

plt.title("Unrestricted tree — first three levels")

plt.tight_layout()

plt.show()

 

Limiting max_depth inside plot_tree changes only the drawing, not the fitted baseline_tree. The hidden lower branches still exist and still affect predictions. Record the root rule, the dominant classes in the first children, and any feature that appears repeatedly.

Step 5 — Generate the pruning path

PYTHON  •   Find effective alpha candidates

path_model = DecisionTreeClassifier(random_state=42)

path = path_model.cost_complexity_pruning_path(X_train, y_train)

 

# Exclude the final alpha that usually leaves only the root node.

candidate_alphas = np.unique(path.ccp_alphas[:-1])

 

if len(candidate_alphas) > 40:

    positions = np.linspace(

        0,

        len(candidate_alphas) - 1,

        40,

        dtype=int,

    )

    candidate_alphas = candidate_alphas[positions]

 

print("Candidate alphas:"len(candidate_alphas))

print(candidate_alphas[:10])

 

Step 6 — Evaluate every candidate on validation data

PYTHON  •   Record accuracy and subtree size

records = []

 

for alpha in candidate_alphas:

    model = DecisionTreeClassifier(

        random_state=42,

        ccp_alpha=float(alpha),

    )

    model.fit(X_train, y_train)

    records.append({

        "ccp_alpha": alpha,

 

PYTHON  •   Record accuracy and subtree size — continued

        "node_count": model.tree_.node_count,

        "depth": model.get_depth(),

        "leaves": model.get_n_leaves(),

        "train_accuracy": model.score(X_train, y_train),

        "validation_accuracy": model.score(X_valid, y_valid),

    })

 

results = pd.DataFrame(records)

print(results.round(4))

 

Step 7 — Plot the pruning trade-off

PYTHON  •   Visualize accuracy and node count

fig, axes = plt.subplots(1,  2, figsize=(13,  4.5))

 

axes[0].plot(

    results["ccp_alpha"],

    results["train_accuracy"],

    marker="o",

    label="Training",

)

axes[0].plot(

    results["ccp_alpha"],

    results["validation_accuracy"],

    marker="o",

    label="Validation",

)

axes[0].set_xlabel("ccp_alpha")

axes[0].set_ylabel("Accuracy")

axes[0].set_title("Accuracy across pruning strengths")

 

PYTHON  •   Visualize accuracy and node count — continued

axes[0].grid(alpha=0.25)

axes[0].legend()

 

axes[1].plot(

    results["ccp_alpha"],

    results["node_count"],

    marker="o",

    drawstyle="steps-post",

)

axes[1].set_xlabel("ccp_alpha")

axes[1].set_ylabel("Number of nodes")

axes[1].set_title("Tree size across pruning strengths")

axes[1].grid(alpha=0.25)

 

plt.tight_layout()

plt.show()

 

Questions for the pruning curves

  • How does node count change as ccp_alpha increases?
  • Where is the gap between training and validation accuracy largest?
  • Does validation accuracy improve after removing some branches?
  • Is the highest validation score achieved by one alpha or a plateau of candidates?
  • At what point does excessive pruning reduce both training and validation performance?

Step 8 — Select alpha without using the test set

PYTHON  •   Prefer the smallest tree among top validation scores

best_validation = results["validation_accuracy"].max()

 

best_candidates = results.loc[

    results["validation_accuracy"] == best_validation

]

 

selected_row = best_candidates.sort_values(

    ["node_count""ccp_alpha"],

    ascending=[True,  False],

).iloc[0]

 

selected_alpha = float(selected_row["ccp_alpha"])

print("Selected alpha:", selected_alpha)

print("Validation accuracy:"round(best_validation,  3))

print("Selected node count:"int(selected_row["node_count"]))

print("Selected depth:"int(selected_row["depth"]))

 

TIE POLICY  The lab prefers fewer nodes when several candidates have the same validation accuracy. Define the tie rule before examining the final test result.

 

Step 9 — Refit and evaluate once

PYTHON  •   Fit the final pruned tree on development data

X_development = pd.concat([X_train, X_valid])

y_development = pd.concat([y_train, y_valid])

 

final_tree = DecisionTreeClassifier(

    random_state=42,

    ccp_alpha=selected_alpha,

)

final_tree.fit(X_development, y_development)

 

test_predictions = final_tree.predict(X_test)

 

 

PYTHON  •   Fit the final pruned tree on development data — continued

print("Confusion matrix:")

print(confusion_matrix(y_test, test_predictions))

print()

print("Classification report:")

print(classification_report(

    y_test,

    test_predictions,

    target_names=data.target_names,

    digits=3,

))

print("Final test accuracy:"round(final_tree.score(X_test, y_test), 3))

 

Step 10 — Visualize and export the pruned tree

PYTHON  •   Draw the final tree

plt.figure(figsize=(1810))

plot_tree(

    final_tree,

    feature_names=list(X.columns),

    class_names=list(data.target_names),

    filled=True,

    rounded=True,

    proportion=True,

    precision=2,

    fontsize=8,

 

PYTHON  •   Draw the final tree — continued

)

plt.title("Final cost-complexity-pruned decision tree")

plt.tight_layout()

plt.show()

 

print(export_text(

    final_tree,

    feature_names=list(X.columns),

    decimals=2,

))

 

Step 11 — Trace one test observation

PYTHON  •   Inspect the decision path

query = X_test.iloc[[0]]

path_matrix = final_tree.decision_path(query)

leaf_id = final_tree.apply(query)[0]

 

visited_nodes = path_matrix.indices

print("Visited node IDs:", visited_nodes)

print("Final leaf ID:", leaf_id)

print("Predicted class:", final_tree.predict(query)[0])

print("Probabilities:", final_tree.predict_proba(query)[0])

 

Step 12 — Inspect feature importance cautiously

PYTHON  •   List the strongest impurity-based importances

importance = pd.Series(

    final_tree.feature_importances_,

    index=X.columns,

).sort_values(ascending=False)

 

print(importance.head(10))

 

importance.head(10).sort_values().plot(

    kind="barh",

    figsize=(8,  5),

    title="Top impurity-based feature importances",

)

plt.xlabel("Importance")

plt.tight_layout()

plt.show()

 

IMPORTANCE CAUTION  Impurity-based feature importance is not causality and can be unstable or biased. Compare it with validation behavior, domain knowledge, and model-agnostic methods later in the course.

 

Expected observations

  • The unrestricted tree usually achieves extremely high training accuracy and uses more nodes than the selected pruned tree.
  • Increasing ccp_alpha removes branches in discrete steps rather than continuously shrinking every rule.
  • Moderate pruning may preserve or improve validation accuracy while substantially simplifying the tree.
  • Excessive pruning eventually produces a tree that is too small and underfits.
  • The final test score can be below the best validation score because selection favored some validation-specific noise.
  • Leaf probabilities are constant for all observations that enter the same leaf.

Lab deliverables

  • A reproducible notebook containing the complete workflow and outputs.
  • A diagram of the initial tree's first three levels.
  • A table of ccp_alpha, node count, depth, leaves, training accuracy, and validation accuracy.
  • Two labeled pruning curves: accuracy versus alpha and node count versus alpha.
  • The selected alpha and the predeclared tie-breaking rule.
  • A final confusion matrix, classification report, and complete pruned-tree visualization.
  • A 200–300 word interpretation of overfitting, pruning, final complexity, and limitations.

Extension challenges

  • Replace the single validation set with StratifiedKFold cross-validation and report mean plus standard deviation for each alpha.
  • Compare cost-complexity pruning with max_depth and min_samples_leaf under the same validation procedure.
  • Use class_weight='balanced' and compare malignant-class recall, precision, and F1 rather than accuracy alone.
  • Bootstrap the development data, refit several pruned trees, and measure how often the root feature changes.
  • Compare impurity-based feature importance with permutation importance on validation data.

Common mistakes and corrections

Table 16.10. Frequent decision-tree errors

Mistake

Why it causes trouble

Correction

Report training accuracy onlyA deep tree can memorize training casesUse validation or cross-validation
Tune on the test setThe final estimate becomes optimisticFreeze complexity before test evaluation
Plot only three levels and call the tree shallowThe fitted model may contain hidden lower branchesCheck get_depth and tree_.node_count
Interpret a split causallyPredictive partitions do not prove causesUse careful predictive language
Assume no scaling means no preprocessingRaw categories and invalid data remain problematicPrepare features inside a leakage-safe workflow
Choose alpha from training scoreTraining performance favors larger treesUse held-out or cross-validated performance
Trust tiny-leaf probabilitiesSmall counts create extreme unstable estimatesIncrease leaf size and evaluate calibration
Treat feature importance as stable truthCorrelation and many thresholds can distort rankingsCheck resampling and permutation importance

 

Knowledge check

Choose one answer for each question. Complete the questions before consulting the answer key.

1. What does every observation encounter first in a decision tree?

  • A. A leaf node
  • B. The root node
  • C. A confusion matrix
  • D. A scaling transformation

2. What normally determines the predicted class of a leaf?

  • A. The class with the largest leaf count
  • B. The deepest feature
  • C. The global feature mean
  • D. A random label

3. A pure classification node has Gini impurity equal to:

  • A. -1
  • B. 0
  • C. 0.5 for every problem
  • D. The sample size

4. Information gain compares:

  • A. Test and training size
  • B. Parent impurity with weighted child impurity
  • C. Feature scales
  • D. Prediction time and fit time

5. Why must child impurity be weighted?

  • A. Children may contain different numbers of observations
  • B. Every feature needs standardization
  • C. Entropy cannot use probabilities
  • D. Trees require equal class counts

6. Which change usually makes leaves larger and the tree smoother?

  • A. Decrease min_samples_leaf
  • B. Increase min_samples_leaf
  • C. Remove random_state
  • D. Standardize every feature

7. What does a larger ccp_alpha generally encourage?

  • A. More branches
  • B. A smaller pruned subtree
  • C. More features in the dataset
  • D. A different target

8. Why can a single tree be unstable?

  • A. It never uses labels
  • B. Small data changes can alter early splits and later branches
  • C. Scaling always changes class labels
  • D. It averages thousands of trees

9. Which statement about scaling is most accurate?

  • A. Trees always fail without StandardScaler
  • B. Trees usually do not need scaling, but other preprocessing may still be required
  • C. Scaling removes overfitting
  • D. Scaling converts classification to regression

10. When should the final test set be evaluated?

  • A. During every alpha comparison
  • B. Before selecting a criterion
  • C. Once after the modeling choices are frozen
  • D. To decide the train-validation split

Answer key and explanations

Table 16.11. Knowledge-check solutions

Answer

Explanation

1 — B

Every observation begins at the root before following any branch.

2 — A

The winning leaf class is based on the largest class count or weighted count.

3 — B

A pure node contains one class and has zero impurity.

4 — B

Gain is the parent impurity minus the sample-weighted impurity of the children.

5 — A

A large child must contribute more than a very small child to the split score.

6 — B

A larger minimum leaf size blocks tiny terminal regions and usually smooths predictions.

7 — B

A larger pruning penalty removes branches whose improvement does not justify their complexity.

8 — B

Changing an early split changes which observations and candidate rules appear downstream.

9 — B

Feature magnitude does not control a one-feature threshold split, but data preparation still matters.

10 — C

The test set is reserved for final evaluation after criterion and complexity choices are fixed.

 

Score interpretation

  • 9–10 correct: ready to train, interpret, and prune a classification tree with honest validation.
  • 7–8 correct: solid understanding; revisit impurity reduction or complexity controls where needed.
  • 5–6 correct: repeat the worked split calculation and pruning-path lab.
  • 0–4 correct: review tree anatomy, leaf predictions, and the distinction between training and validation performance.

Chapter summary

  • A decision tree routes each observation from a root through internal rules and branches to one terminal leaf.
  • The leaf predicts the class with the largest training count or weighted count and estimates probabilities from leaf proportions.
  • Gini impurity and entropy quantify class mixing; useful splits reduce weighted impurity.
  • Split search is greedy and recursive, so a locally best decision is not guaranteed to produce a globally optimal tree.
  • Maximum depth, minimum split size, minimum leaf size, and maximum leaves control growth before pruning.
  • Cost-complexity pruning uses ccp_alpha to trade fit against subtree size and must be tuned without the test set.
  • Trees capture nonlinear relationships and interactions and normally do not require feature scaling.
  • A single tree can overfit, vary substantially across samples, and produce unstable rules or importance rankings.
  • Interpretability depends on restraint: a small validated tree is understandable; a huge tree is merely visible.
ONE SENTENCE TO REMEMBER  A classification tree is a hierarchy of locally chosen rules, so honest validation and complexity control are as important as the rules themselves.

 

Key vocabulary

Table 16.12. Essential terminology

Term

Meaning

Root node

First node containing all training observations available to the tree

Internal node

Nonterminal subgroup where another decision rule is applied

Branch

Route corresponding to an outcome of a decision rule

Leaf node

Terminal region that returns a prediction

Threshold

Numeric cut point used to create left and right child groups

Impurity

Degree of class mixing within a node

Gini impurity

One minus the sum of squared class proportions

Entropy

Uncertainty measure based on negative p log p

Information gain

Reduction from parent impurity to weighted child impurity

Pre-pruning

Stopping or limiting growth before weak branches are created
Cost-complexity pruningRemoving weak branches using a fit-versus-size penalty

ccp_alpha

scikit-learn parameter controlling cost-complexity pruning strength

High variance

Sensitivity of the learned tree to changes in the training sample

 

Further reading

What’s next?

The next chapter studies ensemble classification models. Random forests reduce the variance of individual trees by combining many randomized trees, while gradient boosting builds trees sequentially to correct earlier errors. The contrast explains why one transparent but unstable learner can become the foundation of highly accurate ensemble methods.