Redirect Chapter 24: Decision Trees | AI Fundamentals
← Back to Tutorials Chapter 24

Decision Trees

Every model we have seen so far — linear regression, logistic regression, support vector machines — draws a smooth mathematical boundary. Decision trees take a completely different route: instead of fitting an equation, they ask a sequence of yes/no questions until the answers lead to a decision. A tree is the machine-learning cousin of a flowchart, and because every question is human-readable, trees are among the easiest models to explain to a non-technical colleague.

Introduction to Decision Trees

A decision tree is built from three kinds of components: a root node at the top, internal nodes in the middle, and leaf nodes at the bottom. Every node except the root has one parent, and every internal node asks a single question about one feature.

Imagine predicting whether a shopper will buy a product. The root might ask "is annual income above 50,000?", a child node might ask "has this user visited the site this month?", and the leaves output "will buy" or "will not buy". To predict for a new person you simply walk down the tree, answering each question with their feature values. Because each question examines only one feature at a time, trees make no assumption that the data is linearly separable — a single tree can carve the feature space into rectangles using only horizontal and vertical boundaries.

Entropy and Gini Impurity

To pick a good question, the tree needs a way to measure how mixed a group of labels is. That measure is called impurity, and the two most common impurity functions are entropy and the Gini index.

Entropy, borrowed from information theory, measures the average amount of surprise in a set. For classes with probabilities p1, p2, ..., pk, entropy is:

H = -sum( pi * log2(pi) )   for each class i

If all samples belong to one class, one pi equals 1 and its log is 0, so H = 0 — the node is perfectly pure. If two classes are split 50/50, then H = -(0.5 * -1 + 0.5 * -1) = 1 — maximum uncertainty. Entropy is measured in bits and always lands between 0 and log2(number of classes).

Gini impurity asks a simpler question: what is the probability that two items drawn at random from the node belong to different classes?

G = 1 - sum( pi^2 )   for each class i

A pure node has G = 0. A 50/50 two-class node has G = 0.5. Both functions are at their minimum when the node is pure and grow as the labels become more evenly spread.

Information Gain

Information gain measures how much a split reduces impurity. The tree picks the question that offers the largest drop, because a big drop means the children are much cleaner than the parent. For a classification tree built on entropy:

IG = H(parent) - sum( (n_child / n_parent) * H(child) )

Work through a concrete example:

The algorithm computes the information gain of every candidate question and greedily picks the winner, then repeats the process inside each child until a stopping rule fires.

Entropy vs Gini Impurity

Rule of thumb: start with Gini because it is cheap and usually works well. Try entropy only when you want to compare — the difference is rarely what decides a project.

Decision Tree Splits for Numerical Features

A continuous feature has infinitely many possible split points, so the tree needs a practical strategy:

  1. Sort the unique values of the feature.
  2. Consider each midpoint between consecutive values as a candidate threshold.
  3. Compute the impurity drop for every candidate and keep the threshold with the highest gain.

For an age column holding [18, 25, 34, 41, 55], the candidates are the midpoints 21.5, 29.5, 37.5 and 48. The tree evaluates all of them and picks the best. Because thresholds are chosen greedily, a deep tree on one numerical feature behaves like a staircase function made of many parallel cuts.

Post-Pruning and Pre-Pruning

Trees are eager learners: give them enough depth and they will memorize the training set, noise included. That is overfitting, and two families of remedies exist.

Pre-pruning stops the tree from growing too large in the first place:

Post-pruning lets the tree grow fully and then cuts branches back. The common approach is cost-complexity pruning, where each branch pays a penalty proportional to the leaves it adds; branches that do not improve validation performance enough are removed. In scikit-learn you can read the candidate alphas with cost_complexity_pruning_path, then train a tree for each alpha and keep the one with the best test score.

Decision Tree Regression

Regression trees predict a number rather than a class. A leaf's prediction is simply the mean of the target values inside that region, and the impurity measure is replaced by variance or mean squared error. Information gain becomes variance reduction:

VR = Var(parent) - sum( (n_child / n_parent) * Var(child) )

The tree keeps choosing the split that most reduces the variance of the target in its children. The result is a piecewise-constant function — a staircase — which is why deep regression trees look jagged and shallow ones look smooth.

Decision Tree Implementation

Implementing a tree in scikit-learn takes a few lines:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

clf = DecisionTreeClassifier(random_state=42)
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))

Visualize the learned rules with sklearn.tree.plot_tree, or print them as text with export_text. A rule looks like "petal width <= 0.8 → class 0", which you can read as a plain sentence.

Decision Tree Pre-Pruning in Practice

Pre-pruning is where you steer the bias-variance trade-off. A tree with no limits memorizes the training data; a tree with max_depth=1 (a single stump) is barely better than guessing on some problems. A quick experiment:

from sklearn.tree import DecisionTreeRegressor

for depth in [1, 3, 5, None]:
    tree = DecisionTreeRegressor(max_depth=depth, random_state=42)
    tree.fit(X_train, y_train)
    print(depth, round(tree.score(X_train, y_train), 3),
          round(tree.score(X_test, y_test), 3))

Typical output shows the training score climbing as depth grows while the test score peaks somewhere in the middle — that peak is your sweet spot. Use GridSearchCV over max_depth, min_samples_leaf and min_samples_split to find it systematically.

Diabetes Prediction Using a Decision Tree Regressor

We finish with a small project. The diabetes dataset bundled with scikit-learn contains ten medical measurements per patient and a target that is a quantitative measure of disease progression one year after baseline.

  1. Problem statement: given ten baseline measurements (age, BMI, blood pressure, and several blood serum readings), predict the progression score.
  2. Load and split: separate the features from the target and make a train/test split.
  3. Train: fit a regression tree, pre-pruned to keep it honest.
  4. Evaluate: compare R-squared and mean absolute error on both splits.
from sklearn.datasets import load_diabetes
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error

data = load_diabetes()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

tree = DecisionTreeRegressor(max_depth=4, min_samples_leaf=5, random_state=42)
tree.fit(X_train, y_train)

y_pred = tree.predict(X_test)
print("R2 on train:", round(tree.score(X_train, y_train), 3))
print("R2 on test :", round(tree.score(X_test, y_test), 3))
print("MAE        :", round(mean_absolute_error(y_test, y_pred), 2))
What to look for: the single tree's test R-squared will be modest. Trees are excellent explainers, but a lone tree rarely wins accuracy contests — that is exactly the motivation for the next chapter, where we combine many trees into random forests and boosting ensembles.
Practice: using the same diabetes dataset, compare three regression trees — one fully grown, one pre-pruned with max_depth=3, and one post-pruned with cost-complexity pruning. Report the train and test MAE of each and explain which generalizes best. Extra credit: use export_text to print the rules of the best tree and describe, in plain English, what its first two splits mean.