Pruning a decision tree
Cost-complexity pruning charges a tree rent for every leaf it keeps, cutting away branches that memorised noise while keeping the ones that earn their keep.
- 7 min read
- 3 reading levels
- Published
Read these first
On this page 5
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
Pruning cuts back a fully grown decision tree until only the branches that genuinely help survive.
Think of a mango tree gone wild. Left alone it sprouts hundreds of thin twigs, each carrying a leaf or two. A gardener prunes it: any twig that costs more in water than it returns in fruit gets cut. What remains is smaller, stronger and yields better.
A decision tree grown without limits does the same sprouting. It keeps adding questions until every training example sits in its own perfect little leaf.
Why it exists
A fully grown tree scores 100% on its training data — and that is a symptom, not an achievement. Many of its deepest branches exist to explain individual noisy examples: the mislabeled fruit, the one strange customer. Those branches are memorisation, and they fail on new data. This is overfitting in its most visible form.
Cost-complexity pruning fixes it with an accountant's trick: charge the tree rent for every leaf. A branch survives only if the errors it prevents are worth more than the rent its leaves cost. The rent level is a knob called alpha. Zero rent keeps the wild tree. Infinite rent prunes everything down to a stump that answers one word.
How it works
alpha = 0 alpha = a little alpha = too much
grown wild pruned sensibly a stump
╱╲ ╱╲ ╱╲ ╱╲
╱╲╱╲╱╲╱╲╱╲ ╱ ╲ │
(27 leaves, (8 leaves, (1 leaf,
memorised kept what knows
the noise) generalises) nothing)A real example you have seen
Exam preparation. One classmate memorises every past question, including the misprinted ones — brilliant on last year's paper, lost on a fresh one. Another learns the eight ideas the questions keep circling. Pruning turns the first student into the second, by charging rent per memorised detail.
Remember this
- A fully grown tree memorises noise; its perfect training score is a warning sign.
- Pruning charges rent per leaf — branches that don't pay for themselves get cut.
- Alpha sets the rent: zero keeps the jungle, too much leaves a stump.
What to learn next
- How a tree chooses a split — the growth process pruning cleans up after.
- Overfitting and underfitting — the disease pruning treats.
- Random forest — the other cure: average many unpruned trees.
Developer — Code and libraries.
Setup
pip install scikit-learnOutputs verified with scikit-learn 1.7.2 on CPU.
Watch the rent do its work
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
# synthetic tabular data with deliberately noisy labels (flip_y)
X, y = make_classification(n_samples=400, n_features=8, n_informative=4,
flip_y=0.15, random_state=42)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.5, random_state=42)
for alpha in (0.0, 0.005, 0.01, 0.02, 0.05, 0.1):
t = DecisionTreeClassifier(random_state=42, ccp_alpha=alpha).fit(Xtr, ytr)
print(f"alpha={alpha:<6}: leaves={t.get_n_leaves():3d} "
f"train={t.score(Xtr, ytr):.2f} test={t.score(Xte, yte):.2f}")alpha=0.0 : leaves= 27 train=1.00 test=0.79 alpha=0.005 : leaves= 23 train=0.99 test=0.82 alpha=0.01 : leaves= 10 train=0.93 test=0.82 alpha=0.02 : leaves= 8 train=0.91 test=0.82 alpha=0.05 : leaves= 5 train=0.84 test=0.82 alpha=0.1 : leaves= 1 train=0.51 test=0.50
The walkthrough
Read the first row like a doctor. 27 leaves, perfect training score, 0.79 on unseen data. The 0.21 gap is the tree's memorised noise — we planted that noise ourselves with flip_y=0.15, which mislabels 15% of examples on purpose.
Rows two to five are the point of this lesson. From 27 leaves down to 5 — a tree one-fifth the size — the test score holds at 0.82. Twenty-two leaves existed only to explain flipped labels. The pruned tree is also faster, smaller in memory, and shallow enough to read aloud.
The last row is the stump. Rent so high that no split pays; one leaf, coin-flip accuracy. Pruning is a dial between jungle and stump, and the useful setting lies between.
Choosing alpha honestly. The candidate alphas are not guesses — the fitted tree can enumerate every rent level at which some branch stops paying:
path = DecisionTreeClassifier(random_state=42).cost_complexity_pruning_path(Xtr, ytr)
print(len(path.ccp_alphas), "candidate alphas, e.g.", path.ccp_alphas[:3].round(4))18 candidate alphas, e.g. [0. 0.0046 0.0047]
Cross-validate over path.ccp_alphas (a GridSearchCV over ccp_alpha works) and keep the winner.
Common mistakes
Choosing alpha on the test set. Sweeping alpha, reading test scores, keeping the best — that quietly turns the test set into training data. Choose with cross-validation on the training half; touch the test set once.
Pruning instead of, rather than alongside, growth limits. max_depth and min_samples_leaf stop the jungle from growing; ccp_alpha trims what grew. They compose well — a mild depth cap plus tuned alpha is a robust default.
Expecting pruning to fix bad features. Pruning removes memorisation. It cannot create signal that the features never contained; a pruned tree on junk inputs is a small tree that is still wrong.
Pruning trees inside a random forest. Random forests counter overfitting by averaging many deliberately overgrown trees. Pruning each member usually hurts; leave forest trees wild.
Try it yourself
Set flip_y=0.0 — noise-free labels — and rerun the sweep. Predict first: will the unpruned tree's train/test gap shrink? Then find, by cross-validation over path.ccp_alphas, the alpha you would actually ship.
What to learn next
- How a tree chooses a split — the growth process pruning cleans up after.
- Overfitting and underfitting — the disease pruning treats.
- Random forest — the other cure: average many unpruned trees.
Researcher — Mathematics and papers.
The pruning objective
For a subtree $T$ of the fully grown tree $T_{max}$, define the cost-complexity criterion:
$$ R_\alpha(T) = R(T) + \alpha \, |\tilde{T}| $$
Where:
- $R(T)$ — the training error (misclassification rate or total impurity) of subtree $T$.
- $|\tilde{T}|$ — the number of terminal leaves in $T$.
- $\alpha \geq 0$ — the complexity price per leaf: the "rent".
For each $\alpha$, the subtree minimising $R_\alpha$ is the pruned tree. As $\alpha$ rises from 0, the optimal subtrees form a nested sequence $T_{max} \supset T_1 \supset \dots \supset {root}$ — a key theorem of Breiman, Friedman, Olshen and Stone (1984), Classification and Regression Trees, ch. 3. Only $O(|\tilde{T}_{max}|)$ distinct alphas matter — the values returned by cost_complexity_pruning_path.
Weakest-link computation
For any internal node $t$, collapsing its branch changes error by $R(t) - R(T_t)$ while removing $|\tilde{T}_t| - 1$ leaves, so the branch stops paying at:
$$ g(t) = \frac{R(t) - R(T_t)}{|\tilde{T}_t| - 1} $$
with $T_t$ the branch rooted at $t$ and $R(t)$ the error if $t$ became a leaf. Prune the node with the smallest $g(t)$ — the weakest link — record that $g$ as the next alpha, and repeat. The full sequence costs $O(|T|^2)$ worst case, $O(|T| \log |T|)$ typical.
Breiman's original protocol picks the final alpha by cross-validation, often with the 1-SE rule: among candidate subtrees, take the smallest whose CV error is within one standard error of the minimum — trading a statistically negligible score difference for a genuinely smaller model.
Relation to other regularisers
The $\alpha |\tilde{T}|$ penalty is an $\ell_0$-flavoured complexity charge, the tree cousin of the penalties in ridge and lasso: fit-plus-penalty with a dial trading them. Pre-pruning (depth caps, minimum leaf sizes) bounds the hypothesis space before fitting; post-pruning searches the grown tree's subtree lattice afterwards and can rescue splits that look weak early but enable strong ones below — the lookahead failure pre-pruning cannot fix.
Context: C4.5 uses a different scheme (pessimistic error-based pruning; Quinlan, 1993). Ensembles changed the economics — bagged and boosted trees regularise by averaging and shrinkage instead, which is why ccp_alpha defaults to 0 in scikit-learn and pruning is mainly used when a single interpretable tree is the deliverable.
What to learn next
- How a tree chooses a split — the growth process pruning cleans up after.
- Overfitting and underfitting — the disease pruning treats.
- Random forest — the other cure: average many unpruned trees.