🤖 Section 5 · Machine Learning 🟡 Intermediate MODULE 26

Decision Trees & Random Forests

⏱️ 50 min
📖 Tree-Based Models
🧩 3 Quiz Questions
🏗️ 1 Challenge · 3 tasks
Your progress in Section 543%
🎯 A completely different shape of model. Linear and logistic regression both fit a single straight-line-shaped relationship across the whole dataset. Decision trees work nothing like that — they repeatedly split the data into smaller and smaller groups by asking a sequence of yes/no questions about the features, until each group is (ideally) dominated by one class. A single tree overfits easily, which is exactly what makes Random Forest — an ensemble of many trees voting together — one of the most reliable go-to algorithms in practice.

How a Decision Tree Splits Data

A decision tree builds itself by repeatedly asking the single best yes/no question it can find about the data — the question that does the most to separate the classes — then repeating that process on each resulting group.

1
Start with all the data at the root
Every training example begins in one big, mixed group — the "root" of the tree.
2
Try every possible split
For each feature and possible threshold (e.g. "is age > 45?"), the algorithm checks how much purer the two resulting groups would be.
3
Pick the split that reduces impurity the most
The question that best separates the classes becomes that node's actual question.
4
Repeat on each child group
Each resulting group is split again the same way, recursively, forming branches.
5
Stop at a leaf
Splitting stops once a group is pure enough, too small, or a maximum depth is reached — that final group becomes a "leaf," and its majority class is the tree's prediction for anything landing there.
Gini Impurity
Gini = 1 − Σ(pᵢ)²
pᵢ is the proportion of each class in a group. Gini = 0 means a group is perfectly pure (only one class present); higher values mean the group is more mixed. A tree picks whichever split lowers the (weighted) Gini impurity of the resulting groups the most.
📝
Gini impurity vs. entropy — two ways to measure the same idea
Gini impurity and entropy (from information theory) both measure "how mixed is this group," and both hit their minimum (0) when a group contains only one class. scikit-learn's DecisionTreeClassifier uses Gini by default (criterion='gini'), with entropy available as criterion='entropy'. In practice the two rarely produce very different trees — this course sticks with the default, Gini.

No deep math is required to use a tree — scikit-learn computes every candidate split's impurity internally. The intuition to keep is simple: at every step, the tree asks whatever single question best separates the remaining classes.

DecisionTreeClassifier in Scikit-Learn

This lesson uses load_breast_cancer(), one of scikit-learn's small built-in real-world datasets — 30 numeric measurements from breast mass scans, with a binary target (malignant / benign). It's a genuine, commonly-used teaching dataset bundled directly with the library, not synthetic data.

decision_tree.py
PYTHON
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

data = load_breast_cancer()
X, y = data.data, data.target
print("Features:", X.shape[1], "| Samples:", X.shape[0])
print("Classes:", data.target_names)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# max_depth caps how many questions deep the tree can go
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(X_train, y_train)

print(f"Train accuracy: {dt.score(X_train, y_train):.3f}")
print(f"Test accuracy:  {dt.score(X_test, y_test):.3f}")
.score() works the same way across classifiers
Just like LogisticRegression.score() in Lesson 25, calling .score(X, y) on a fitted classifier returns accuracy — the fraction of predictions that matched the true labels. That consistent interface is one of scikit-learn's biggest strengths: swapping one classifier for another usually means changing one line.

Overfitting Risk in Deep Trees

A tree with no depth limit will keep splitting until every leaf is perfectly pure — often down to leaves containing just one or two training examples. That's a textbook case of overfitting: the tree has essentially memorized the training set's noise, rather than learning a pattern that generalizes.

🌱
Shallow tree (small max_depth)
Fewer questions asked, simpler boundaries. Risks underfitting if too shallow to capture real patterns.
🌳
Deep, unrestricted tree
Keeps splitting until leaves are pure. Often reaches ~100% training accuracy — and noticeably worse test accuracy, the overfitting signature from Lesson 24.
🎛️
max_depth is your main lever
Capping tree depth is the simplest way to fight overfitting in a single tree — scikit-learn also supports min_samples_leaf and min_samples_split for finer control.
🔍
Watch the train/test gap
The same diagnostic from Lesson 24 applies directly: a wide train-vs-test accuracy gap on a tree usually means it's too deep for the amount of data available.
⚠️
A tree with unlimited depth WILL fit its training data almost perfectly
Leave max_depth unset on DecisionTreeClassifier and, given enough distinct feature combinations, it can split all the way down to single-example leaves — driving training accuracy close to 100%. That's not a sign of a good model; it's the clearest possible overfitting warning sign, and it's exactly why the tree in Section 2 restricts max_depth=3.

Random Forest — An Ensemble of Trees

A Random Forest trains many decision trees — often hundreds — and combines their predictions, usually by majority vote for classification. The trick that makes this actually help, rather than just repeating the same overfit tree many times, is called bagging.

1
Bootstrap sampling
Each tree in the forest is trained on a random sample of the training rows, drawn with replacement — so every tree sees a slightly different subset of the data.
2
Random feature subsets at each split
At every split, each tree only considers a random subset of the available features, not all of them — this forces the trees to be different from each other, rather than all converging on the same dominant pattern.
3
Aggregate the votes
For classification, the forest's final prediction is whichever class the majority of individual trees voted for.
Why averaging many overfit trees helps
Each individual tree in a forest is usually still allowed to overfit its own bootstrap sample somewhat — but because every tree overfits to different noise (thanks to the random sampling and random feature subsets), those individual errors tend to cancel out when votes are combined. The forest as a whole typically generalizes much better than any single deep tree in it.
random_forest.py
PYTHON
from sklearn.ensemble import RandomForestClassifier

# n_estimators = how many trees to train and vote together
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)

print(f"Train accuracy: {rf.score(X_train, y_train):.3f}")
print(f"Test accuracy:  {rf.score(X_test, y_test):.3f}")

# Compare directly to the single tree from Section 2 — illustrative,
# not a guaranteed result on every dataset, but forests are frequently more robust
# than a lone unrestricted tree, especially with many features like this one.
print(f"Single tree test accuracy: {dt.score(X_test, y_test):.3f}")

Feature Importance

A useful side benefit of tree-based models: after fitting, they can report how much each feature contributed to reducing impurity across all the splits that used it — a rough but genuinely useful measure of which inputs mattered most.

feature_importance.py
PYTHON
import numpy as np

importances = rf.feature_importances_
# argsort() gives ascending order; [-5:] takes the top 5; [::-1] reverses to descending
top5 = importances.argsort()[-5:][::-1]

print("Top 5 most important features:")
for i in top5:
    print(f"  {data.feature_names[i]}: {importances[i]:.4f}")
📊 Illustrative feature importance ranking
On this kind of dataset, measurements describing cell size and shape uniformity (e.g. features like "worst area" or "worst concave points") tend to rank among the most important predictors — this is a plausible, illustrative pattern for teaching purposes, not a claimed benchmark result. The exact ranking depends on the random seed, the number of trees, and the specific train/test split.
📝
feature_importances_ is available on both classes
Both DecisionTreeClassifier and RandomForestClassifier expose a .feature_importances_ array (one value per input feature, summing to 1.0) after .fit() — but the forest's version is generally considered more reliable, since it's averaged across many trees rather than depending on the specific splits of one.
🧩 Knowledge Check — Lesson 26
3 questions on trees and forests before you move on.
1. What does a Gini impurity of 0 for a group mean?
2. A DecisionTreeClassifier with no max_depth set scores 100% on training data but only 68% on test data. What does this suggest?
3. What is the core idea behind bagging in a Random Forest?
💪
Try It Yourself — Lesson 26
Explore depth and ensemble size · Intermediate Level

Reuse the X_train/X_test/y_train/y_test split from Section 2's breast cancer dataset for all three tasks.

Task 1: Sweep max_depth 🌳

Train four separate DecisionTreeClassifier models with max_depth set to 1, 3, 6, and None (unlimited). For each, print the train accuracy and test accuracy. At what depth does the train/test gap start to widen noticeably?
Task 2: Sweep n_estimators 🌲🌲🌲

Train RandomForestClassifier models with n_estimators set to 10, 50, and 200 (keep random_state=42 for all three). Does test accuracy keep improving meaningfully past 50 trees, or does it plateau?
Task 3: Compare feature importance across models 📊

Print the top 3 features by .feature_importances_ for one of your depth-6 trees from Task 1, and separately for your 200-tree forest from Task 2. Are the top features similar between the two?
💡 Show hints if you're stuck
  • Task 1: DecisionTreeClassifier(max_depth=None, random_state=42) — passing None explicitly is the same as leaving the argument out; both mean "no depth limit."
  • Task 2: RandomForestClassifier(n_estimators=200, random_state=42) — more trees generally costs more compute for a shrinking accuracy benefit past a certain point.
  • Task 3: importances.argsort()[-3:][::-1], same pattern as Section 5's code sample, applied to each model's own .feature_importances_.
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 26 Complete!

You can now explain how a tree splits data with Gini impurity, why unrestricted trees overfit, and how Random Forest's bagging ensemble fixes that — plus reading feature importance. Next: properly measuring how good any of these classifiers actually are.

Module 26 of 30 Section 5 — Machine Learning with Scikit-Learn