Python · Course navigation

Decision trees: entropy and information gain

Choose the feature that best splits the data by measuring how much it reduces uncertainty.

On this page
Before you start

Logarithms and counting.

  • Choose the feature that best splits the data by measuring how much it reduces uncertainty.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

A decision tree classifies an example by asking a series of questions about its features, following a branch for each answer until it reaches a leaf with a prediction. The tree is learned from data by choosing, at each node, the question that best separates the classes.

The ID3 algorithm measures how mixed a set of labels is with entropy, H = −Σ pᵢ log₂ pᵢ, where pᵢ is the fraction of examples in class i. A set with all labels equal has entropy 0, and a half-and-half set has entropy 1 bit. The information gain of a feature is the entropy before the split minus the weighted average entropy of the groups after it. The feature with the highest gain is chosen, and the process repeats on each group. The CART algorithm used in many libraries usually uses Gini impurity instead of entropy, with similar results.

In the example, four of eight days are 'yes' and four are 'no', so the entropy is 1.0. Splitting on outlook gives groups that are nearly pure, a gain of 0.656 bits. Splitting on windy gives almost no improvement, 0.049 bits, so outlook is chosen. A tree grown until every leaf is pure fits the training data perfectly and tends to overfit, so real implementations limit depth, require a minimum number of examples per leaf, or prune the tree.

Code example

Python
import math

rows = [
    ("sunny", False, "no"),
    ("sunny", True, "no"),
    ("overcast", False, "yes"),
    ("rain", False, "yes"),
    ("rain", False, "yes"),
    ("rain", True, "no"),
    ("overcast", True, "yes"),
    ("sunny", False, "no"),
]


def entropy(labels):
    total = len(labels)
    return -sum((labels.count(v) / total) * math.log2(labels.count(v) / total) for v in set(labels))


def gain(rows, index):
    remainder = 0.0
    for value in set(row[index] for row in rows):
        subset = [row[-1] for row in rows if row[index] == value]
        remainder += len(subset) / len(rows) * entropy(subset)
    return entropy([row[-1] for row in rows]) - remainder


print(round(entropy([row[-1] for row in rows]), 3))
gains = {"outlook": gain(rows, 0), "windy": gain(rows, 1)}
print({name: round(value, 3) for name, value in gains.items()})
print(max(gains, key=gains.get))
Output
1.0
{'outlook': 0.656, 'windy': 0.049}
outlook

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1import math
2
3rows = [
4 ("sunny", False, "no"),
5 ("sunny", True, "no"),
6 ("overcast", False, "yes"),
7 ("rain", False, "yes"),
8 ("rain", False, "yes"),
9 ("rain", True, "no"),
10 ("overcast", True, "yes"),
11 ("sunny", False, "no"),
12]
13
14
15def entropy(labels):
16 total = len(labels)
17 return -sum((labels.count(v) / total) * math.log2(labels.count(v) / total) for v in set(labels))
18
19
20def gain(rows, index):
21 remainder = 0.0
22 for value in set(row[index] for row in rows):
23 subset = [row[-1] for row in rows if row[index] == value]
24 remainder += len(subset) / len(rows) * entropy(subset)
25 return entropy([row[-1] for row in rows]) - remainder
26
27
28print(round(entropy([row[-1] for row in rows]), 3))
29gains = {"outlook": gain(rows, 0), "windy": gain(rows, 1)}
30print({name: round(value, 3) for name, value in gains.items()})
31print(max(gains, key=gains.get))

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Entropy measures impurity: 0 for a pure set, 1 bit for an even two-class mix.—
—
Four yes and four no gives the maximum entropy for two classes: 1.0 bit.—
1.0
After a split, the uncertainty is the size-weighted average entropy of the groups.—
1.0
Outlook removes 0.656 bits of uncertainty. Windy removes only 0.049.—
1.0
{'outlook': 0.656, 'windy': 0.049}
The best first question is outlook.—
1.0
{'outlook': 0.656, 'windy': 0.049}
outlook

Behind the scenes

How Python processes this code

Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.

Cost and common mistakes

Finding the best split of a node costs O(n·d) for n examples and d categorical features. Growing a whole tree costs roughly O(n·d·depth).

  • Information gain favours features with many distinct values, such as an ID column. Use gain ratio or limit such features.
  • A fully grown tree memorizes the training set. Limit its depth or prune it, and check performance on held-out data.
  • Splits are chosen greedily, one node at a time. The result is good but not guaranteed to be the best possible tree.

Try it yourself

What is the entropy of a set with 3 positive and 1 negative example?

Reveal answer and reasoning

−(0.75·log₂ 0.75 + 0.25·log₂ 0.25) ≈ 0.811 bits.

Further reading