Chapter 16 · Decision Tree Classification
Chapter 8's flowchart returns wearing class labels — and its splitting criterion gets a promotion. Where the regression tree reduced variance, the classification tree reduces entropy: messiness measured in bits, computable by hand. This chapter works entropy and information gain entirely by hand on real data, meets Gini as the fast-running rival, reads the fitted tree as three audit-ready rules — and watches a four-leaf tree out-score the fifty-leaf memoriser.
🎯 Learning Objectives
- Compute entropy by hand for a real class mix, and read the 0-to-1 bit scale intuitively
- Compute information gain for the fitted tree's actual root split, from real class counts
- Compare entropy with Gini impurity — formulas, values, and the real near-identical trees
- Explain the leaf's majority vote and why its "probabilities" are overconfident
- Use the real depth study to see memorisation billed and pruning rewarded
- Read the fitted tree as business rules, and connect its instability to Chapter 17's fix
1. The Flowchart Returns
If you survived Chapter 8, you already know this algorithm's skeleton: greedy, axis-aligned splits, grown one question at a time, predictions made at the leaves. Only the target's type has changed from numbers to classes, and that changes two things — what a leaf says (a majority vote instead of a mean) and how splits are scored (entropy or Gini instead of variance). CART's promise holds: one growing algorithm, both supervised worlds.
Same running dataset, same split and scaling as Chapters 11–15. The source
code's one new ingredient is explicit: criterion='entropy' — the
choice of messiness-measure the tree greedily reduces. That choice is this
chapter's main event.
2. Entropy: Measuring Messiness
Before choosing a question, the tree needs a number for "how mixed up is this node?" — entropy, imported from information theory (Shannon, 1948): the average number of bits needed to encode a label drawn from the node:
- H = 0 — pure node: one class only, zero bits needed, nothing left to ask
- H = 1 — a perfect 50/50 mix: maximum surprise, one full bit per label
- The curve is symmetric, peaking at p = 0.5 — messiness is about balance, not which class leads
2.1 By hand — the training root
Our 300 training customers: 189 non-buyers, 111 buyers (Ch 15's priors again).
H = −(0.630 · log₂ 0.630) − (0.370 · log₂ 0.370)
= −(0.630 × (−0.667)) − (0.370 × (−1.435))
= 0.420 + 0.531 = 0.951 bits
3. Information Gain: Choosing the Question
Every candidate split (feature, threshold) divides the node into two children; the tree scores each by information gain — how much entropy the question removes — and greedily takes the best:
The fitted tree's actual first question (raw units): Age ≤ 44.5? Count the classes on each side of that real threshold:
| Node | Rows | Non-buyers | Buyers | Entropy (hand) |
|---|---|---|---|---|
| Root | 300 | 189 | 111 | 0.951 |
| Left: Age ≤ 44.5 | 215 | 175 | 40 | 0.693 |
| Right: Age > 44.5 | 85 | 14 | 71 | 0.645 |
The arithmetic, end to end:
IG = 0.951 − 0.680 = 0.271 bits
3.1 Gini — the fast rival
sklearn's default criterion is not entropy but Gini impurity — the probability two random draws from the node disagree:
4. The Leaf: Votes & Overconfident Probabilities
A classification leaf predicts its residents' majority class —
the vote from Chapter 8's regression mean. Its predict_proba is the
vote share: a leaf holding 9 buyers and 1 non-buyer says [0.1, 0.9]. For our
three anchor customers (real outputs):
| Customer | Prediction | Probability | Reading |
|---|---|---|---|
| (30, ₹87k) | NO | [1.0, 0.0] | Fell in a pure non-buyer leaf |
| (30, ₹150k) | YES | [0.0, 1.0] | Pure wealthy-young leaf |
| (45, ₹87k) | YES | [0.0, 1.0] | Pure older leaf |
Notice what those 1.0s and 0.0s are not: calibrated belief. A leaf holding a handful of training rows reports total certainty because its residents happened to agree — sample-of-ten unanimity masquerading as probability 1. Contrast Naive Bayes's graded [0.061, 0.939] or logistic's [0.509, 0.491]: trees speak in votes, and a vote of 10–0 is not the same epistemic state as 100–0, though both print as 1.0. If the business consumes probabilities, Chapter 18's calibration tools — or Chapter 17's averaged forests, whose votes come from hundreds of trees — are the remedy.
5. Depth & the Memoriser's Bill
Chapter 8's regression memoriser (R² = 1.0 on ten rows) returns here with a bill attached. The real depth study — same seed, same criterion:
| max_depth | Leaves | Test acc | Train acc | Diagnosis |
|---|---|---|---|---|
| 1 | 2 | 0.89 | 0.820 | One question ("Age ≤ 44.5?") — the stump |
| 2 | 4 | 0.94 | 0.910 | The champion — see below |
| 3 | 8 | 0.94 | 0.910 | Ties the champion with double the rules |
| 4 | 13 | 0.93 | 0.917 | Decay begins |
| None (source) | 50 | 0.91 | 1.000 | The memoriser: perfect training, 3 points worse on test |
Read the last row first: the unpruned tree achieves training accuracy 1.000 — 50 leaves isolating every training quirk — and loses three test points to a tree with four leaves. Then read row two: the depth-2 tree's 0.94 is the best score of any model in this Part so far (K-NN and RBF SVM sit at 0.93), using three questions. One honest caveat, as always: 0.94 vs 0.93 is one test row in a hundred, and this sweep consulted the test set — the defensible selection protocol is Chapter 31's cross-validated grid. The structural lesson survives the caveat: the parsimonious tree won.
min_samples_leaf) is pre-pruning — stopping early.
Post-pruning grows full and then removes splits that fail a complexity
test. sklearn ships pre-pruning (ccp_alpha for cost-complexity
pruning); either way, the principle is Chapter 6's: flexibility must pay
rent, or get evicted.
6. Implementation in Python
import numpy as np
import pandas as pd
dataset = pd.read_csv('Social_Network_Ads.csv')
X = dataset.iloc[:, :-1].values
y = dataset.iloc[:, -1].values
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=0)
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train) # harmless habit; the tree is scale-blind
X_test = sc.transform(X_test)
from sklearn.tree import DecisionTreeClassifier
classifier = DecisionTreeClassifier(criterion='entropy', random_state=0)
classifier.fit(X_train, y_train)
from sklearn.metrics import confusion_matrix, accuracy_score
y_pred = classifier.predict(X_test)
cm = confusion_matrix(y_test, y_pred)
print(cm) # [[62 6]
# [ 3 29]]
print(accuracy_score(y_test, y_pred)) # 0.91
Listing 16.1 — The source tree (decision_tree_classification.py).
# The champion, inspected and audited
champ = DecisionTreeClassifier(criterion='entropy', max_depth=2,
random_state=0).fit(X_train, y_train)
print(champ.score(X_test, y_test)) # 0.94 — four leaves
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 5))
plot_tree(champ, feature_names=['Age', 'Salary'], class_names=['No', 'Yes'],
filled=True, rounded=True)
plt.show() # the entire model, on one slide
# The depth study of Section 5
for d in [1, 2, 3, 4, None]:
m = DecisionTreeClassifier(criterion='entropy', max_depth=d,
random_state=0).fit(X_train, y_train)
print(f'depth={str(d):4s} test={m.score(X_test, y_test):.3f} '
f'train={m.score(X_train, y_train):.3f} leaves={m.get_n_leaves()}')
Listing 16.2 — Reading the champion as a picture and reproducing the depth study.
export_text(classifier,
feature_names=[...]) prints the whole tree as if-then rules — the
compliance-department version of plot_tree. Both are reasons
trees survive in regulated industries long after accuracy kings change.
7. Reading the Results: Three Rules
The updated scoreboard, all real, same split and scaling:
| Logistic | K-NN | SVM lin | SVM RBF | NB | DT full | DT depth-2 | |
|---|---|---|---|---|---|---|---|
| Accuracy | 0.89 | 0.93 | 0.90 | 0.93 | 0.90 | 0.91 | 0.94 |
| Errors | 11 | 7 | 10 | 7 | 10 | 9 | 6 (2 FN + 4 FP) |
| Model size | 3 coefs | 300 rows | 128 SVs | 88 SVs | 9 stats | 50 leaves | 4 leaves |
And the champion's entire intelligence, in English — audit-ready:
- Age > 44.5 → predict BUY (regardless of salary — both right-side leaves vote yes; the salary question there only splits buyer-heavy from buyer-clean).
- Age ≤ 44.5 and Salary > ₹90,500 → predict BUY (the wealthy-young segment — our boundary customer (30, ₹150k) lives here).
- Age ≤ 44.5 and Salary ≤ ₹90,500 → predict NO.
The standing caveat is Chapter 8's and Chapter 9's: this exact tree is a child of this exact sample. Resample the 300 rows and the thresholds move — 44.5 could become 42 or 47, and a different feature might lead — because greedy splits amplify small data changes. High variance, one more time; and the fix is one chapter away.
8. Strengths, Limits & the Road Ahead
| Strengths | Limitations |
|---|---|
| Audit-ready rules — the model a compliance officer can read | Axis-aligned staircases approximate diagonals expensively |
| Scale-blind; mixed feature types welcome; no preprocessing ceremony | Unpruned = memoriser (real: train 1.000 / test 0.91) |
| Fast training and prediction; feature importance for free | Vote-share "probabilities" — overconfident at pure leaves |
| Beats fancier models when the true structure is boxy rules | High variance: resampling regrows a different tree |
That final limitation is the launchpad: Chapter 9 averaged wobbling regression trees into a stable forest, and the identical cure awaits here. Chapter 17 grows a classification forest — bootstraps, feature subsampling, votes — and with it closes the classifier toolkit before Chapter 18 judges them all under one honest metric system.
🛠 Hands-On: Grow, Score, PruneIntermediate
The audit committee wants a model they can read aloud, the data science lead wants the best test score. Deliver both: the hand-computed entropy audit, the full tree, and the pruned champion — then write the memo.
Steps
- By hand: compute the root entropy from 189/111 (expect ≈ 0.951 bits); then the Gini (expect ≈ 0.466).
- Fit the full entropy tree; verify [[62, 6], [3, 29]] and 0.91; extract the root question and its two children's questions.
- By hand: information gain of the root split from the side counts (215: 175/40, 85: 14/71) — expect ≈ 0.271 bits.
- Run the depth study {1, 2, 3, 4, None}; identify the memoriser row and the champion row.
- Print or draw the depth-2 tree; write its rules in English; classify the three anchor customers by hand through the rules.
- Write the memo: which tree ships, what it costs in accuracy (if anything), and the Chapter 31 caveat on this selection procedure.
View Solution / Walkthrough
import numpy as np, pandas as pd, math
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.metrics import confusion_matrix
ds = pd.read_csv('Social_Network_Ads.csv')
X, y = ds.iloc[:, :-1].values, ds.iloc[:, -1].values
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0)
H = -(189/300)*math.log2(189/300) - (111/300)*math.log2(111/300)
print('root entropy %.4f gini %.4f' % (H, 1-(189/300)**2-(111/300)**2))
full = DecisionTreeClassifier(criterion='entropy', random_state=0).fit(X_tr, y_tr)
print(confusion_matrix(y_te, full.predict(X_te)), full.score(X_te, y_te))
print(export_text(full, feature_names=['Age', 'Salary'])[:300])
def ent(v):
p = v.mean(); q = 1-p
return 0 if p in (0, 1) else -(p*math.log2(p) + q*math.log2(q))
left = y_tr[X_tr[:, 0] <= 44.5]; right = y_tr[X_tr[:, 0] > 44.5]
IG = H - (len(left)/300*ent(left) + len(right)/300*ent(right))
print('IG(Age<=44.5) = %.4f bits' % IG)
for d in [1, 2, 3, 4, None]:
m = DecisionTreeClassifier(criterion='entropy', max_depth=d,
random_state=0).fit(X_tr, y_tr)
print('depth', d, 'test %.3f train %.3f leaves %d' %
(m.score(X_te, y_te), m.score(X_tr, y_tr), m.get_n_leaves()))
champ = DecisionTreeClassifier(criterion='entropy', max_depth=2,
random_state=0).fit(X_tr, y_tr)
print(export_text(champ, feature_names=['Age', 'Salary']))
Expected results (real):
- Step 1: H = 0.951 bits; Gini = 0.466.
- Step 2: cm [[62, 6], [3, 29]], 0.91; root "Age ≤ 44.5", then "Salary ≤ 90,500" (young side) and "Salary ≤ 41,500" (older side).
- Step 3: weighted H = 0.680 → IG = 0.271 bits.
- Step 4: 0.89 / 0.94 / 0.94 / 0.93 / 0.91(train 1.000) — memoriser at None, champion at depth 2.
- Step 5: rules per Section 7; anchors — (30, 87k): rule 3 → NO; (30, 150k): rule 2 → YES; (45, 87k): rule 1 → YES. All match the model's output.
- Step 6: ship the depth-2 tree (0.94, four leaves, three readable rules); the selection used the test set, so confirm the depth on cross-validated scores (Ch 31) before the audit committee meets reality.
🎯 Key Takeaways
Entropy = messiness in bits: 0 for pure nodes, 1 for a coin flip. Our root, at 189/111, sits at 0.951 — nearly maximally worth splitting.
Information gain = entropy removed: the real root question "Age ≤ 44.5?" gained 0.271 bits — one question turned a coin flip into two 80%+ pure nodes.
Gini vs entropy is a non-choice: real trees differed by one test row (0.90 vs 0.91, 47 vs 50 leaves). Depth matters far more than criterion.
Leaves vote, and their probabilities are vote shares — pure leaves report 1.0 with a handful of rows' worth of certainty. Calibrate before acting on the numbers.
The memoriser's bill, itemised: full tree trains to 1.000 and loses to a 4-leaf pruned tree, 0.91 vs 0.94 — parsimony's third victory of the course.
The champion model is three English sentences — transparency no other classifier here matches — and its instability is exactly what Chapter 17's forest averages away.
Test Your Understanding
30 questions across the chapter — entropy and information gain by hand, Gini, the leaf vote, the depth study and the champion's rules. Choose one answer per question, then check your answers.
Q1.A classification tree predicts by:
Q2.The split criterion used by this chapter's source code is:
Q3.Entropy of a node is defined as:
Q4.(Hand) With classes split 189/111 out of 300, the root entropy is:
Q5.Entropy equals 0 exactly when:
Q6.(Real) The fitted tree's first question is:
Q7.(Real) After the root split: left = 215 rows (175/40), right = 85 rows (14/71). Their entropies:
Q8.(Hand) The information gain of the root split is:
Q9."Greedy" splitting means the tree:
Q10.Gini impurity of the root (189/111) is:
Q11.(Real) Entropy vs Gini, full trees, same data and seed:
Q12.A tree's predict_proba outputs:
Q13.(Real) Customer (30, ₹150k) — the boundary-sitter of five previous chapters — is classified by the tree as:
Q14.Every tree question has the form:
Q15.(Real) Fitting this tree on raw (unscaled) features instead gave:
Q16.(Real) The depth study's champion was:
Q17.(Real) The full unpruned tree scored training 1.000 but test 0.91. The diagnosis:
Q18."Pruning" a tree means:
Q19.In Chapter 6's vocabulary, increasing tree depth:
Q20.This chapter chose the champion depth by sweeping on the test set. The professional protocol:
Q21.(Real) The depth-1 stump scored 0.89 with a single question. That question was:
Q22.(Real) The source tree's confusion matrix [[62, 6], [3, 29]] reads:
Q23.The champion's "entire intelligence" fits in three English sentences. This is the tree's:
Q24.The tree's known instability — a resample regrows different thresholds — is cured by:
Q25.Entropy is measured in bits because:
Q26.A leaf's predicted class is:
Q27.An information gain of 0.271 bits at the root tells you:
Q28.In production, trees' practical conveniences include:
Q29.(Real) In the depth-2 champion, both leaves on the "Age > 44.5" side vote YES. The salary question there exists to:
Q30.The next chapter's classifier is: