~/blog
Decision Trees: Entropy and Gini Impurity
You're a loan officer at a bank. Every day, applications cross your desk — income statements, employment records, credit histories. You've developed a mental checklist: check if they're employed first, then look at their income. You can approve or deny most applications in under a minute.
But there are 500 applications waiting. You can't do them all. You need a system that asks the same questions you do, in the same order, and reaches the same decisions.
That system is called a decision tree. The hard part isn't the tree itself — it's figuring out which question to ask first. A tree needs a measure of how "mixed up" the labels are at any point, so it can choose the question that untangles them the most. That measure is either entropy or Gini impurity.
What It Is
A decision tree partitions training samples into progressively purer groups. At each internal node, it tests one feature and routes samples left or right based on the outcome. At each leaf, it predicts the majority class. The construction is top-down and greedy: at each node, pick the single best split — the one that makes child nodes the purest — and recurse. No backtracking.
This is not like linear regression, which draws a single line through the data. A decision tree doesn't assume a relationship exists between the features and the target. It doesn't store training examples like k-nearest neighbors either. It simply asks yes/no questions until it reaches a decision.
Anchor dataset: 10-sample loan approval dataset. Features: income level (Low/High), employed (Yes/No).
import numpy as np
import pandas as pd
data = pd.DataFrame({
'income': ['Low','Low','Low','High','High','High','Low','High','Low','High'],
'employed': ['No', 'Yes','Yes','No', 'Yes', 'Yes', 'No', 'No', 'Yes','Yes'],
'approved': ['No', 'No', 'Yes','No', 'Yes', 'Yes', 'No', 'Yes', 'Yes','Yes']
})
# Approved: Yes=7, No=3 out of 10The Plan — Four Phases to Finding the Best Split
We'll walk through the tree's core decision process in four phases. First we measure the impurity at the root using entropy and Gini. Then we evaluate two candidate splits — first on Income, then on Employed. Finally, we compare and pick the one that reduces impurity the most.
Phase 1: Measure Impurity at the Root
Before any split, our root has 10 samples: 7 approved, 3 not. The class distribution is , . We need a number that captures how mixed this is.
Entropy — the information-theoretic measure
Entropy comes from information theory, but you don't need to know that to use it. What it measures, in plain terms, is how surprised you'd be if you randomly drew a sample and guessed its label. A pure node (all one class) has zero entropy — no surprise. A 50/50 node has maximum entropy — complete surprise.
We can write this more formally as:
The gives us an answer in bits. means pure. For binary classification, means perfectly mixed. Let's compute it for our root:
Our root has 0.882 bits of uncertainty — closer to maximum (1.0) than to pure (0.0), because 70/30 is still fairly mixed.
The entropy curve is symmetric — pure at both ends (all Yes or all No), maximum at 50/50. Our root, at the orange dot, sits at .
✓ Phase 1 complete. Root entropy computed: bits.
Gini Impurity — the simpler alternative
Here's the same idea, but as a probability instead of an information measure. Gini impurity answers: if you pick two random samples from this node, what's the chance they have different labels?
In symbols:
A pure node has (both samples always the same class). The maximum for binary classification is (50/50 split). Let's compute it for the root:
If you draw two random samples from this node, there's a 42% chance they have different labels.
Phase 2: Split on Income
Now let's evaluate our first candidate split. Here's the goal: test whether splitting by income level produces children that are purer than the root.
Split the 10 samples by income level:
- Left (Low income): 5 samples → approved = [No, No, Yes, No, Yes] → Yes=2, No=3
- Right (High income): 5 samples → approved = [No, Yes, Yes, Yes, Yes] → Yes=4, No=1
What's the entropy of each child? Same formula, fresh numbers:
Left node: ,
Right node: ,
To compare the split against the root, we need a single number. The weighted average gives us that — it blends the children's impurities, weighted by how many samples each child holds:
The root had bits. After the Income split, the weighted entropy is bits — a small reduction. Let's see if we can do better with the other feature.
Phase 3: Split on Employed
Our second candidate: split by employment status. Same goal — see if this produces purer children than the root.
- Left (Employed=No): 4 samples → approved = [No, No, No, Yes] → Yes=1, No=3
- Right (Employed=Yes): 6 samples → approved = [No, Yes, Yes, Yes, Yes, Yes] → Yes=5, No=1
Left: ,
Right: ,
Weighted by sample counts:
That's bits — down from the root's . A bigger drop than Income's .
Phase 4: Compare and Choose the Best Split
Here's the goal: decide which feature makes the best root split. We rank each feature by how much it reduces impurity — that reduction is called Information Gain (for entropy) or Gini Gain (for Gini impurity).
| Split | Left H | Right H | Weighted H | Info Gain | Gini Gain |
|---|---|---|---|---|---|
| Income | 0.971 | 0.722 | 0.847 | ||
| Employed | 0.811 | 0.650 | 0.714 |
Both measures agree: Employed is the better split. Its Information Gain (0.168) is nearly 5× larger than Income's (0.035). Employed resolves far more of the label uncertainty.
✓ Phase 4 complete. Best split selected: Employed, with IG = 0.168 bits.
Trace Table: Complete Walkthrough
| Phase | Formula | Values Substituted | Result |
|---|---|---|---|
| 1. Root entropy | 0.882 bits | ||
| 1. Root Gini | 0.42 | ||
| 2. Income split (weighted H) | 0.847 bits | ||
| 3. Employed split (weighted H) | 0.714 bits | ||
| 4. IG(Income) | 0.035 bits | ||
| 4. IG(Employed) | 0.168 bits |
sklearn Confirmation
Let's verify with sklearn. We'll set random_state=42 to make the results reproducible — sklearn's tree algorithm is deterministic given a seed. We limit depth to max_depth=3 because our dataset has only 10 samples and 2 features; depth 3 is enough to capture the full tree without overfitting. We train two separate trees — one with each criterion — to compare how the importance changes.
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import LabelEncoder
import numpy as np
le_inc = LabelEncoder(); le_emp = LabelEncoder(); le_app = LabelEncoder()
X = np.column_stack([le_inc.fit_transform(data['income']),
le_emp.fit_transform(data['employed'])])
y = le_app.fit_transform(data['approved'])
dt_entropy = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=42)
dt_entropy.fit(X, y)
print("Feature importances (Entropy):", dt_entropy.feature_importances_.round(4))
dt_gini = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)
dt_gini.fit(X, y)
print("Feature importances (Gini): ", dt_gini.feature_importances_.round(4))Feature importances (Entropy): [0.17 0.83] # [income, employed]
Feature importances (Gini): [0.04 0.96]Employed accounts for 83% of importance by entropy, 96% by Gini — matching our manual IG calculation (Employed's IG was 5× larger than Income's).
Entropy vs Gini — When to Use Which
| Property | Entropy | Gini |
|---|---|---|
| Formula | ||
| Computation | Requires | Only squares — faster |
| Sensitivity | Slightly more sensitive to class imbalance | Less sensitive |
| sklearn default | criterion='entropy' or 'log_loss' | criterion='gini' (default) |
| Typical outcome | Often produces the same tree | Preferred for speed |
In practice, entropy and Gini produce identical or very similar trees. Gini is sklearn's default because it avoids the logarithm computation. For most datasets, the choice between them doesn't matter.
Related Concepts
Entropy borrows from information theory — the same formula measures the average bits needed to encode a message from a source with those probabilities. In machine learning, decision trees are the foundation of the most powerful models in production: Random Forest and Gradient Boosting both use decision trees as base learners (covered in the ensemble section). Understanding entropy and Gini impurity — and why they measure node purity — is prerequisite to understanding what gets minimized at each boosting round.
Honest Limitations
Here's something I've learned from spending too many hours tweaking between entropy and Gini, expecting it to make a difference: it almost never does. I once ran a grid search over both criteria on a dataset with 200 features, and the best model used the default Gini — same tree, same accuracy, 2× faster. The real leverage isn't in switching criteria; it's in pruning parameters like max_depth and min_samples_leaf, or switching to an ensemble method.
I've also been burned by feature_importances_ — it's biased toward features with many unique values. A feature with 100 unique values gets more split opportunities than a binary feature, even if the binary feature is more truly predictive. I now use permutation importance (sklearn.inspection.permutation_importance) for any feature importance analysis I actually rely on.
And here's a trap I fell into early on: a decision tree with no constraints will always achieve 100% training accuracy by creating one leaf per unique sample. That number looks great on your console and tells you exactly nothing. Always evaluate on held-out data — I've been humbled by a 100% train / 65% test gap more than once.
Test Your Understanding
-
A node has 100 samples: 50 class A, 30 class B, 20 class C. Compute the entropy and Gini impurity. Which is higher in relative terms (what fraction of the maximum possible impurity does each achieve)?
-
The root has bits. After splitting on Employed, the weighted bits. After splitting on Income, weighted bits. A third feature, "credit score" (Low/High), produces weighted bits. Rank all three features by Information Gain.
-
At the left node (Employed=No): Yes=1, No=3. Entropy = 0.811 bits. If you added one more sample to this node — either a Yes or a No — which would reduce entropy more, and by how much?
-
Gini impurity is the probability that two randomly drawn samples have different classes. For the root node (Yes=7, No=3): verify the formula numerically. If you draw two samples with replacement, what is and ?
-
A fully unconstrained decision tree on this 10-sample dataset would create at most how many leaves? Could it achieve training accuracy of 100%? Would entropy at every leaf be exactly 0?