Module 5 — Decision Trees
A decision tree is among the most interpretable models in machine learning: it classifies by applying a hierarchical sequence of binary tests on the input features — for example, "Is income > $50,000? If yes, is age < 30?" — producing a tree-structured classifier. In this module you will grow a decision tree one level at a time, observe how it partitions the feature space into axis-aligned regions, and identify the depth at which it begins to overfit.
Splits: recursive binary partitioning
At each internal node the algorithm selects a single feature-and-threshold pair — for example, "is \( x_1 < 3.2 \)?" — that best separates the classes by some splitting criterion. The data are then partitioned into two subsets and the procedure recurses within each subset, generating successive splits until a stopping condition is reached at a leaf node, which is assigned a class label.
Selecting the optimal split: impurity criteria
A split is good when each resulting subset is as pure as possible — ideally every observation in it belongs to a single class, so the leaf can predict that class with confidence. To choose between candidate splits we first need a number that measures how mixed a subset is. The standard choice is the Gini impurity:
where \( p_c \) is the fraction of the subset belonging to class \( c \). One way to read it: the probability you would mislabel a randomly drawn item if you guessed classes in proportion to how often they appear. A few values fix the scale (two classes):
- All one class \((p_c = 1, 0)\): \( 1 - (1^2 + 0^2) = 0 \) — perfectly pure.
- An 80/20 mix: \( 1 - (0.8^2 + 0.2^2) = 0.32 \) — mostly pure.
- A 50/50 mix: \( 1 - (0.5^2 + 0.5^2) = 0.5 \) — maximally impure, a coin flip.
A split sends the node's data into a left and a right child. To score it, compare the parent's impurity against its children's — weighting each child by the fraction of points that land in it, since a child holding most of the data should count for more:
This drop \( \Delta\text{Gini} \) is the split's purity gain. The algorithm greedily keeps the feature-and-threshold with the largest gain, then recurses inside each child. Note what is not happening: unlike the linear-regression and neural-network models elsewhere in this course, there is no gradient descent — a discrete feature/threshold choice is not a smooth dial to nudge. The algorithm instead simply enumerates every candidate threshold, scores each by \( \Delta\text{Gini} \), and retains the winner.
Build the tree by hand
The greedy search above is something you can drive yourself. Click any region marked with a +: the algorithm finds that region's best split — the feature and threshold that most reduce its weighted Gini — and partitions it in two, adding a node to the tree diagram. Keep clicking the mixed regions to recurse, and watch the partition refine while the overall impurity falls toward 0. Regions that are already pure stop offering a split.
This activity needs JavaScript. The lesson below still covers everything.
Grow the tree
Increase max depth using the slider. At depth 1 the tree applies a single split, producing one axis-aligned boundary. Each additional level enables subsequent splits within each subset, refining the decision boundary into a finer partition. Observe how training accuracy increases with depth, and assess whether the additional regions correspond to genuine structure in the data or instead enclose individual noisy observations.
This activity needs JavaScript. The lesson below still covers everything.
Depth and the bias–variance trade-off
A shallow tree may underfit — too few splits to represent the underlying pattern (high bias). A sufficiently deep tree can drive training accuracy to 100% by isolating each observation in its own region, but these fine-grained partitions reflect noise in the training data and generalize poorly to new observations — the model has overfit (high variance). The optimal depth represents a balance between bias and variance (Module 8) and is typically selected by evaluating performance on a held-out validation set.
from sklearn.tree import DecisionTreeClassifier clf = DecisionTreeClassifier(max_depth=3) # the slider you just moved clf.fit(X_train, y_train) # greedily picks splits by impurity clf.score(X_test, y_test) # accuracy on unseen dataimport numpy as np import matplotlib.pyplot as plt from sklearn.tree import DecisionTreeClassifier from sklearn.datasets import make_moons from sklearn.model_selection import train_test_split # Two interleaving half-moons — not separable by a straight line X, y = make_moons(n_samples=300, noise=0.3, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # Watch train accuracy climb while test accuracy peaks then drops: overfitting for depth in [1, 3, 8, 15]: clf = DecisionTreeClassifier(max_depth=depth, random_state=0).fit(X_train, y_train) print(f"max_depth={depth:<2} train={clf.score(X_train, y_train):.3f} test={clf.score(X_test, y_test):.3f}") clf = DecisionTreeClassifier(max_depth=3, random_state=0).fit(X_train, y_train) xx, yy = np.meshgrid(np.linspace(X[:, 0].min() - .5, X[:, 0].max() + .5, 250), np.linspace(X[:, 1].min() - .5, X[:, 1].max() + .5, 250)) Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) plt.figure(figsize=(5, 3.4)) plt.contourf(xx, yy, Z, alpha=0.2, cmap="coolwarm") plt.scatter(X[:, 0], X[:, 1], c=y, cmap="coolwarm", s=14, edgecolor="k", linewidth=0.3) plt.title("Decision tree, max_depth=3"); plt.tight_layout(); plt.show()
max_depth is the principal hyperparameter governing model complexity, and it directly controls the degree of overfitting — precisely the behavior illustrated by the slider above. Click Run it yourself, then increase max_depth and observe how the gap between training and test accuracy widens.
When you run it, the program prints (and draws a chart):
max_depth=1 train=0.805 test=0.822 max_depth=3 train=0.895 test=0.900 max_depth=8 train=0.986 test=0.856 max_depth=15 train=1.000 test=0.833
Check your understanding
Answer a short set of questions on splits, impurity criteria, and depth.
This activity needs JavaScript.