Classic Algorithms in Depth

How a tree chooses a split

A decision tree tries every possible question, scores how cleanly each one sorts the labels, and keeps the question with the purest result — here is that scoring, by hand.

Read these first

On this page 5
  1. Why it exists
  2. How it works
  3. A real example you have seen
  4. Remember this
  5. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

A tree chooses a split by trying every possible question and keeping the one that sorts the labels most cleanly.

Think of sorting a mixed basket of apples and oranges using a weighing scale. You could ask "is it heavier than 120 grams?" or "heavier than 140?". Each question splits the basket into two piles. A good question makes each pile nearly one fruit; a lazy question leaves both piles mixed.

A decision tree plays this game with brute force. It tries every question the data allows and measures the mess left in each pair of piles.

Why it exists

The tree needs a number for "how mixed is this pile" — otherwise it cannot compare questions. That number is called impurity.

The common recipe is Gini impurity: a pile of all apples scores zero — perfectly pure. A fifty-fifty pile scores the maximum. The tree scores a question by the impurity left over, averaged across both piles, giving the bigger pile more say.

One question rarely finishes the job. So the tree asks the best question, then repeats the whole search inside each pile, and again inside those — which is how a tree grows.

How it works

basket: 4 oranges, 4 apples  (very mixed)

ask "weight > 141g?"
        ├── lighter pile:  ○○○○●      almost pure — one small apple slipped in
        └── heavier pile:  ●●●        perfectly pure

ask "weight > 127g?"
        ├── lighter pile:  ○○●        mixed
        └── heavier pile:  ○●●●●     mixed — a worse question

the tree keeps "weight > 141g" and moves on

A real example you have seen

Loan approval screens work this way: "income above X? → then, EMI above Y?". Triage at a clinic — fever above 102? breathing difficulty? — follows the same sorted-questions shape. That familiar shape is why trees are the model businesses accept without argument: every split is a readable question.

Remember this

  • The tree tries every candidate question and keeps the cleanest split.
  • "Clean" is measured by impurity — zero for a pure pile, maximum for a fifty-fifty mix.
  • Trees grow by repeating this greedy search inside each new pile.

What to learn next

Developer — Code and libraries.

Setup

bash
pip install scikit-learn

Outputs verified with scikit-learn 1.7.2 on CPU.

Score every split by hand

split_by_hand.py
import numpy as np

# fruit weights in grams; 0 = orange, 1 = apple (one apple is unusually small)
weights = np.array([110, 118, 125, 130, 138, 145, 150, 160])
labels  = np.array([0,   0,   1,   0,   0,   1,   1,   1])

def gini(y):
    if len(y) == 0:
        return 0.0
    p = y.mean()                       # fraction of apples in this pile
    return 1 - p ** 2 - (1 - p) ** 2

n = len(labels)
for t in (114.0, 121.5, 127.5, 134.0, 141.5, 147.5, 155.0):
    left, right = labels[weights <= t], labels[weights > t]
    score = (len(left) * gini(left) + len(right) * gini(right)) / n
    print(f"split at {t:5.1f} g -> weighted impurity {score:.3f}")
Output
split at 114.0 g -> weighted impurity 0.429
split at 121.5 g -> weighted impurity 0.333
split at 127.5 g -> weighted impurity 0.467
split at 134.0 g -> weighted impurity 0.375
split at 141.5 g -> weighted impurity 0.200
split at 147.5 g -> weighted impurity 0.333
split at 155.0 g -> weighted impurity 0.429

One split wins outright at 0.200. Does scikit-learn agree with our arithmetic?

confirm_with_sklearn.py
from sklearn.tree import DecisionTreeClassifier, export_text

tree = DecisionTreeClassifier(max_depth=1, random_state=0)
tree.fit(weights.reshape(-1, 1), labels)
print(export_text(tree, feature_names=["weight"]))
Output
|--- weight <= 141.50
|   |--- class: 0
|--- weight >  141.50
|   |--- class: 1

The library picked 141.5 g — the same winner our arithmetic found.

The walkthrough

Where do candidate thresholds come from? Sort the feature; the midpoint between each pair of neighbouring values is a candidate. Eight fruits give seven candidates — no other threshold can produce a different split, so nothing else is worth trying.

Score the 141.5 split yourself. Left pile: [0, 0, 1, 0, 0] — one apple among five fruits, Gini 0.32. Right pile: [1, 1, 1] — pure, Gini 0. Weighted: (5 × 0.32 + 3 × 0) / 8 = 0.200. Our printed number, recovered by hand.

Note what the tree did with the odd small apple. The 125 g apple sits in the "orange" pile and the tree accepted that mistake. Chasing it with an extra question would carve out a one-fruit pile — the seed of overfitting, and the reason pruning exists.

Greedy means never looking back. The tree keeps the best question now and recurses. It never revisits a split in light of later ones, so the final tree can miss a globally better arrangement. Finding the optimal tree is computationally infeasible; greedy is the affordable compromise.

Common mistakes

Believing the tree tests every real number. People imagine infinite thresholds. Sorted midpoints are the complete list — which is why numeric precision of thresholds in exported trees looks odd (141.5, not 140).

Confusing Gini with entropy. criterion="entropy" swaps the impurity formula for information-gain. The two disagree on real data only occasionally; neither is reliably better (see researcher notes). Do not burn tuning budget here.

Reading feature importances as causal truth. Importances add up impurity reductions. Correlated features split credit unpredictably, and high-cardinality features get inflated scores. Treat importances as hints, not findings.

Letting the tree grow unlimited. Default DecisionTreeClassifier grows until leaves are pure — memorising noise like our odd apple. Set max_depth, min_samples_leaf, or prune afterwards.

Try it yourself

Change the odd apple's label at 125 g to orange, rerun both scripts, and confirm one split now scores 0.0. Then add a second feature — skin smoothness from the KNN lesson — and check whether the depth-1 tree prefers it over weight.

What to learn next

Researcher — Mathematics and papers.

Impurity measures

For a node with class proportions $p_1, \dots, p_K$:

$$ G = 1 - \sum_{c=1}^{K} p_c^2, \qquad H = -\sum_{c=1}^{K} p_c \log_2 p_c $$

Where:

  • $G$ — Gini impurity: the probability two random draws from the node disagree.
  • $H$ — entropy in bits; "information gain" is the reduction in $H$ from a split.
  • $p_c$ — fraction of node samples in class $c$; $K$ — number of classes.

A split of node $S$ into $S_L, S_R$ is scored by weighted child impurity:

$$ \Delta = I(S) - \frac{|S_L|}{|S|} I(S_L) - \frac{|S_R|}{|S|} I(S_R) $$

with $I$ either $G$ or $H$; the split maximising $\Delta$ wins. Both measures are concave in $p$, guaranteeing $\Delta \geq 0$. Empirically they disagree on ~2% of splits (Raileanu and Stoffel, 2004); Gini is marginally cheaper (no logarithm) and is CART's default.

For regression trees the impurity is the node variance, $I(S) = \frac{1}{|S|}\sum_{i \in S}(y_i - \bar{y}_S)^2$, and splits minimise summed child variance.

Complexity

At a node with $m$ samples and $d$ features: sort each feature, $O(d\, m \log m)$, then sweep thresholds updating class counts incrementally — $O(m)$ per feature. Total tree construction is $O(d\, n \log^2 n)$ typical, and modern gradient-boosting libraries cut the constant with histogram binning: features pre-bucketed into ~256 bins, splits scored on bin statistics (LightGBM; Ke et al., 2017).

Categorical features and axis-alignment

Optimal binary partition of an unordered categorical with $L$ levels naively costs $2^{L-1} - 1$ evaluations; for binary classification, sorting levels by their positive rate finds the optimum in $O(L \log L)$ (Breiman et al., 1984). Axis-aligned questions are a real representational limit — a diagonal boundary costs a staircase of splits. Oblique trees (Murthy et al., 1994) split on linear combinations; the idea resurfaces in tree-based neural hybrids.

The greedy gap and honest history

Learning an optimal decision tree is NP-complete (Hyafil and Rivest, 1976) — greedy induction is a heuristic, not a shortcut to optimality. Canon: CART (Breiman, Friedman, Olshen, Stone, 1984), ID3 and C4.5 (Quinlan, 1986; 1993) — the entropy lineage. Single trees are high-variance learners; their modern life is as base learners inside random forests and gradient boosting, where the split search above runs millions of times per model.

What to learn next