Decision Trees — Loan Approval at Capital One
The algorithm that thinks in if-then questions. Gini impurity, information gain, pruning, and why decision trees are the foundation of every ensemble method.
It's 2024. You're a new ML engineer at Capital One.
250,000 loan applications come in every month. Each one has an income figure, a credit score, an employment status, a debt-to-income ratio, and a dozen other fields. A team of credit analysts reviews each application and approves or rejects it. The process takes three days per application. The bank wants to automate the first pass — flag clear approvals and clear rejections automatically, and route only the borderline cases to humans.
You could use logistic regression. But your manager asks: "When the model rejects someone, can you explain exactly why?" Logistic regression gives you a probability — not an explanation a customer or a regulator can follow. You need something interpretable. Something that says: "rejected because monthly income < $3,500 AND credit score < 650 AND existing monthly debt payments > $1,200."
That is a decision tree. It learns a flowchart of if-then questions from your data. Every prediction comes with a traceable path through the tree. Every rejection has a human-readable reason. And it takes zero feature scaling, handles missing values gracefully, and works on both classification and regression problems without changing a single line of code.
What this module covers:
How a decision tree actually works
A decision tree asks a sequence of yes/no questions about the input features. Each question splits the data into two groups. The process repeats inside each group until the groups are pure enough — mostly one class — or a stopping condition is hit. The result is a tree of decisions that ends at leaf nodes containing a class label or a predicted value.
The key question is: which feature do you split on, and at what threshold? At every node, the tree tries every possible split on every feature and picks the one that makes the resulting groups most homogeneous — most "pure". Pure means one group is mostly approvals and the other is mostly rejections. An impure group is a mix of both.
Gini impurity — measuring how mixed a group is
Gini impurity measures how often a randomly chosen element from a group would be incorrectly labelled if it was randomly labelled according to the distribution of labels in that group. A perfectly pure group (all one class) has Gini = 0. A perfectly mixed group (50% each class) has Gini = 0.5. The tree chooses the split that produces the lowest weighted average Gini impurity across the two resulting groups.
Information gain and entropy — the alternative criterion
Entropy (from information theory — Module 07) measures the same thing as Gini but using logarithms. Information gain is the reduction in entropy from a split. Both Gini and entropy produce very similar trees in practice. Gini is slightly faster to compute (no logarithm). Entropy can sometimes produce slightly more balanced trees. sklearn defaults to Gini. Use Gini unless you have a specific reason to switch.
Building a decision tree from scratch
Building a tree from scratch makes every part of the algorithm visible. The recursive structure — split a node, then recursively split each child — is why trees are called recursive partitioning algorithms. Once you see this implementation, sklearn's API will have no mysteries.
Overfitting — why trees grow too deep
An unconstrained decision tree will grow until every leaf contains exactly one training sample — a perfectly pure leaf with zero training error. This sounds great until you realise the tree has memorised the training set completely. It has learned the noise, the one applicant who got approved despite a 400 credit score because the analyst was having a good day. That pattern will not generalise.
Pruning is the process of limiting tree growth to prevent overfitting. There are three main strategies, and sklearn exposes all of them.
Feature importance — which inputs drove the decisions
A decision tree assigns importance to each feature based on how much it reduced impurity across all splits where it was used, weighted by the number of samples at those nodes. Features that appear near the root (early splits) tend to have high importance because they affect more samples. Features that only appear in deep, small-population splits get low importance.
Regression trees — the same algorithm, continuous output
Decision trees work identically for regression — the only difference is in the splitting criterion and the leaf output. Instead of Gini impurity, regression trees minimise the mean squared error of predictions within each split. Instead of a class label, each leaf outputs the mean target value of all training samples that reached it.
Decision trees are the building block of Random Forest and XGBoost
A single decision tree overfits. It is also unstable — small changes in the training data produce dramatically different trees. Both problems were solved by two different ideas that are now the dominant algorithms in production tabular ML worldwide.
Train 100–1000 trees, each on a random sample of data and a random subset of features. Average their predictions. The averaging cancels out individual tree errors — the ensemble is far more accurate and stable than any single tree.
Trees: independent, full depth
Prediction: average (regression) / vote (classification)
Train shallow trees sequentially, each one correcting the errors of the previous. The final prediction is the sum of all trees. Gradient boosting consistently wins tabular ML benchmarks and is the most widely deployed ML algorithm in fintech.
Trees: shallow (depth 3–6), sequential
Prediction: sum of all tree outputs
The key insight: once you understand how a single decision tree chooses splits, computes impurity, and makes predictions, Random Forest and XGBoost become straightforward — they are just collections of trees combined in different ways. The algorithm you built from scratch in this module IS the algorithm inside every XGBoost model at Stripe, Instacart, and every unicorn running tabular ML.
What this looks like at work — day one at Capital One
Every common decision tree error — explained and fixed
Five things people get wrong about decision trees
The depth sweep earlier in this module shows exactly why this fails: accuracy improves as depth grows from 1 to around 4–5, then reverses — depth=10 hits 99% training accuracy but only 83% test accuracy, and an unconstrained tree (depth=None) hits 100% training accuracy while test accuracy actually drops to 79%. Past the point where the tree has captured the real signal, every additional split is fitting noise specific to the training rows — a scenario built for memorising exceptions, not generalising to new applicants. Depth is a capacity dial, and more capacity only helps up to where the true pattern is fully captured.
The code comparison earlier in this module makes the actual answer explicit: "Both criteria select the same split — just different scales." Gini ranges 0–0.5 for binary classification, entropy ranges 0–1 bit, but they rank candidate splits almost identically in practice. sklearn defaults to Gini because it skips a logarithm and is marginally faster to compute — not because it produces better trees. If you are spending tuning time on this instead of max_depth, min_samples_leaf, or ccp_alpha, you are optimising the wrong hyperparameter.
True for a shallow tree with a handful of splits, which is exactly why Capital One wanted one over logistic regression in the first place. It stops being true once max_depth grows past 6–8: the tree now has dozens of splits and hundreds of leaves, and "interpretable" degrades into a rulebook too large for a regulator or customer to meaningfully audit, even though every individual rule is still printable with export_text. There is also a subtler limit even in a shallow tree: the printed threshold (credit_score <= 650) is whichever value happened to minimise weighted Gini on this training sample — a few points either side can be an arbitrary artifact of the data rather than a meaningful business cutoff.
The module explicitly frames the actual motivation as instability, not marginal accuracy: "A single tree overfits. It is also unstable — small changes in the training data produce dramatically different trees." That instability is the formal definition of high variance — swap out a handful of borderline applicants near a split boundary and the chosen feature or threshold can change, cascading into a differently-shaped tree downstream. Random Forest's bagging specifically targets this variance by averaging many trees trained on different random samples; the accuracy improvement is a side effect of fixing the instability, not the primary goal.
Feature importance measures how much impurity reduction this specific tree credited to a feature across the splits where it was greedily chosen — not how predictive that feature fundamentally is. If two features are correlated (loan_amount and loan_to_income both encode overlapping information here), the tree picks whichever one narrowly wins the Gini comparison at the first opportunity and credits it with all the importance, while the other can show near-zero importance despite carrying comparable signal. This is exactly the leakage-detection trap flagged in the errors section above — always check correlated feature groups together, and prefer permutation importance or Random Forest's averaged importance (computed across many trees seeing different feature subsets) over a single tree's numbers.
Decision trees — 5 questions interviewers actually ask
At every node the tree evaluates every feature and every candidate threshold on that feature, computes the weighted Gini impurity (or entropy) of the two resulting child groups for each candidate, and greedily picks whichever single split produces the lowest weighted impurity — equivalently, the largest reduction from the parent node's impurity. It then repeats this process recursively and independently inside each child, continuing until a stopping condition is met: max depth reached, too few samples to split further, or a node is already pure. It is a greedy, locally-optimal algorithm at every step — it never looks ahead to see if a worse split now would enable a better one two levels down.
Left unconstrained, the tree keeps splitting until every leaf is pure — in the extreme, one training sample per leaf — which drives training accuracy to 100% by memorising individual rows, including noise like one applicant approved despite a 400 credit score purely because the analyst had an inconsistent day. That memorised noise does not generalise. The fix is controlling tree capacity: pre-pruning parameters like max_depth (start at 3–5), min_samples_leaf, and min_samples_split stop the tree from growing that deep in the first place, while post-pruning with ccp_alpha grows the full tree first and then removes branches whose removal doesn't meaningfully hurt impurity, with the right alpha chosen via cross-validation.
In practice, barely. Gini impurity (1 − Σpᵢ²) and entropy-based information gain almost always select the same splits — they're just on different numeric scales (Gini tops out at 0.5 for binary classes, entropy at 1 bit). Gini is marginally cheaper to compute since it avoids a logarithm, which is why sklearn defaults to it; entropy can occasionally favour a very slightly more balanced split. I'd spend tuning effort on max_depth, min_samples_leaf, and ccp_alpha before ever touching this — those control overfitting directly, while the splitting criterion barely changes the resulting tree.
High variance means small changes to the training data produce large changes in the fitted model. A tree is a clear example: change which handful of borderline applicants near a split threshold happened to land in the training set, and the chosen feature or threshold at that node can flip, which cascades into a differently structured tree below it. Random Forest directly targets this: train many trees, each on a bootstrapped sample of rows and a random subset of features, so their individual errors are decorrelated, then average their predictions. Averaging over many high-variance, low-bias trees cancels out the instability while keeping the low bias — which is exactly why the ensemble generalises far better than any one tree in it.
Not necessarily, and this is a common trap. Feature importance in a single tree only reflects how much impurity reduction got credited to a feature at the specific splits where the greedy algorithm happened to choose it. If two features are correlated, the tree picks whichever one narrowly wins the impurity comparison first and gives it all the credit — the other can look completely uninformative despite carrying comparable signal, and this is also a classic symptom to check for label leakage. I'd validate with permutation importance, which measures the actual performance drop from shuffling a feature rather than relying on a single greedy tree's internal bookkeeping, and I'd prefer Random Forest's importance, averaged across many trees that each see different feature subsets, over any single tree's numbers.
You understand trees. Now watch what happens when you build thousands of them.
A single tree overfits, is unstable, and has high variance. The fix — discovered in the 1990s — was to train many trees and combine their predictions. Module 28 covers Random Forest: 100–1000 trees, each trained on a random sample of data and a random subset of features, their predictions averaged into something far more powerful and stable than any individual tree.
Bagging, random feature subsets, out-of-bag evaluation, and the feature importance that actually works in production.
🎯 Key Takeaways
- ✓A decision tree recursively partitions the feature space using if-then questions. At each node it tries every feature and every threshold, picking the split that produces the purest child nodes.
- ✓Gini impurity = 1 − Σpᵢ². Zero means a node is completely pure (all one class). 0.5 is maximally mixed for binary classification. The tree greedily minimises weighted Gini impurity at every split.
- ✓Information gain is the alternative criterion: it measures entropy reduction from a split. Gini and entropy produce nearly identical trees in practice. sklearn defaults to Gini because it is faster to compute (no logarithm).
- ✓Unconstrained trees always reach 100% training accuracy by memorising every sample. Control overfitting with max_depth (start at 3–5), min_samples_leaf (try 20–50), and ccp_alpha (post-pruning via cost-complexity path).
- ✓Decision trees need no feature scaling — splits are threshold-based and scale-invariant. They also handle mixed numeric and categorical features natively (after encoding) and produce interpretable if-then rules.
- ✓Feature importance from a tree = total Gini reduction attributable to each feature across all splits, weighted by samples. Features near the root have high importance because they affect more samples.
- ✓Decision trees are the foundation of Random Forest (bagging + random features) and XGBoost/LightGBM (sequential boosting). Understanding one tree completely means understanding the building block of the two most powerful tabular ML algorithms.
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.