Contents
Figure 1: Many imperfect opinions, one better answer
One fork in the road: average independent models, or chain correcting ones
Ask one expert to predict something and you get one opinion, with all its quirks. Ask a hundred and combine their answers, and the quirks tend to cancel out. That's ensemble learning, and it powers most of the strongest models on tabular data.
But "build many models and combine them" hides a fork in the road. Bagging trains many models independently and averages them, which mainly reduces variance. Boosting trains models one after another, each focused on the previous ones' mistakes, which mainly reduces bias. They look similar from a distance and behave very differently in practice. This guide explains both, runs an experiment that shows the contrast, and helps you choose.
Why Ensembles Work
A single model makes errors for two reasons. Bias is systematic error from being too simple to capture the real pattern. Variance is sensitivity to the exact training sample, so small data changes produce different models.
Combining models helps when the individual models make different mistakes and each is better than random guessing. Averaging diverse errors cancels some of them, and sequentially correcting errors removes systematic ones. Bagging and boosting are two ways of getting that benefit.
A useful picture: bagging is a committee of independent experts who vote, while boosting is a relay team in which each runner picks up where the last one stumbled.
Bagging: Independent Models, Averaged
Bagging (bootstrap aggregating) works in three steps:
- Draw many bootstrap samples from the training data: random rows chosen with replacement, the same size as the original.
- Train one model on each sample, independently and in parallel.
- Combine predictions by majority vote (classification) or averaging (regression).
Each model sees a slightly different dataset, so each makes slightly different errors, and averaging them smooths those errors out. Bagging works best with high-variance, low-bias base learners, which is why deep, unpruned decision trees are the classic choice. A stable model like a linear regression gains little, because bootstrap samples produce nearly identical models.
Random forests are bagging plus one more trick: each split considers only a random subset of features, which decorrelates the trees and lowers the variance floor, as covered in the random forest guide in this series. Bagging also gives you a free validation estimate through out-of-bag scoring, since each model skips about 37% of the rows.
Properties worth remembering:
- Bagging barely reduces bias. If your base model underfits, bagging it won't fix that.
- Adding more models almost never causes overfitting — it just shows diminishing returns.
- Training parallelizes trivially.
- It's relatively robust to noisy labels, because outlier-driven mistakes get averaged away.
Boosting: Sequential Models, Each Fixing the Last
Boosting builds an ensemble stage by stage, where each new model concentrates on what the current ensemble gets wrong. The base learners are deliberately weak, often shallow trees.
AdaBoost was the original: after each round, it increases the weights of misclassified examples so the next learner focuses on them, then combines learners in a weighted vote, giving better ones more say.
Gradient boosting generalizes the idea. At each stage it fits a new tree to the residual errors of the current ensemble, which is equivalent to taking a gradient descent step on the loss function, but in function space. A small learning rate (shrinkage) scales each tree's contribution, so the ensemble moves in many small steps, which generalizes better than a few large ones.
Modern implementations add histogram-based split finding for speed, row and column subsampling (borrowed from bagging), L1 and L2 regularization on leaf values, native handling of missing values, and early stopping. The major libraries are XGBoost, LightGBM, CatBoost, and scikit-learn's HistGradientBoosting.
Properties worth remembering:
- Boosting reduces bias and can reach very high accuracy, but it can overfit if you add too many stages.
- It's more sensitive to noisy labels and outliers, since it keeps trying to fit the hard examples.
- It's inherently sequential across trees, though each tree's construction can still be parallelized.
- It has more hyperparameters, so it rewards tuning.
Head to Head
- Main effect: bagging reduces variance, boosting reduces bias (and, with shrinkage and subsampling, some variance too).
- Training: bagging builds models independently in parallel, boosting builds them sequentially.
- Base learners: bagging uses strong, deep, low-bias models, boosting uses weak, shallow ones.
- Overfitting: more bagged models are safe, more boosting rounds can hurt without early stopping or regularization.
- Noise robustness: bagging is more forgiving, boosting can chase noisy labels.
- Tuning effort: bagging works well near defaults, boosting rewards careful tuning.
- Accuracy ceiling: boosting often wins on well-tuned tabular problems, bagging gets you most of the way with less effort.
- Interpretability: both are black boxes made of many trees, so use importance and explanation tools.
An Experiment: See the Difference
This script compares a single tree, bagging, a random forest, AdaBoost, and gradient boosting, first on clean data and then with 20% random label noise:
from sklearn.datasets import make_classification
from sklearn.ensemble import (AdaBoostClassifier, BaggingClassifier,
HistGradientBoostingClassifier, RandomForestClassifier)
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.tree import DecisionTreeClassifier
def make_data(noise):
return make_classification(n_samples=3000, n_features=20, n_informative=8,
n_redundant=4, flip_y=noise, class_sep=0.8,
random_state=0)
models = {
"Single deep tree": DecisionTreeClassifier(random_state=0),
"Bagging (deep trees)": BaggingClassifier(DecisionTreeClassifier(), n_estimators=200,
n_jobs=-1, random_state=0),
"Random forest": RandomForestClassifier(n_estimators=300, n_jobs=-1, random_state=0),
"AdaBoost (stumps)": AdaBoostClassifier(n_estimators=300, random_state=0),
"Gradient boosting": HistGradientBoostingClassifier(random_state=0),
}
cv = StratifiedKFold(5, shuffle=True, random_state=0)
for noise in (0.0, 0.2):
X, y = make_data(noise)
print(f"\nlabel noise = {noise:.0%}")
for name, model in models.items():
score = cross_val_score(model, X, y, cv=cv, scoring="accuracy").mean()
print(f" {name:<22} {score:.3f}")
Expect the single tree to trail every ensemble. On clean data, boosting often matches or beats bagging, and with label noise the gap typically narrows or flips, because boosting spends capacity fitting mislabeled points. Your exact numbers will vary, so rerun with other seeds and noise levels.
Watch Them Overfit (or Not)
The cleanest demonstration is plotting test accuracy as you add more models. Bagging should plateau, while boosting can peak and then decline on noisy data:
import matplotlib.pyplot as plt
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
X, y = make_data(0.15)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
gb = GradientBoostingClassifier(n_estimators=600, learning_rate=0.1,
max_depth=3, random_state=0).fit(Xtr, ytr)
gb_curve = [accuracy_score(yte, p) for p in gb.staged_predict(Xte)]
rf = RandomForestClassifier(n_estimators=600, n_jobs=-1, random_state=0).fit(Xtr, ytr)
probs = np.array([t.predict_proba(Xte)[:, 1] for t in rf.estimators_]) # trees x samples
rf_curve = [accuracy_score(yte, (probs[:k].mean(axis=0) > 0.5).astype(int))
for k in range(1, 601)]
plt.plot(range(1, 601), rf_curve, label="Random forest (bagging)")
plt.plot(range(1, 601), gb_curve, label="Gradient boosting")
plt.xlabel("number of trees"); plt.ylabel("test accuracy"); plt.legend(); plt.show()
The forest's curve climbs and flattens. The boosting curve climbs faster, may peak early, and can drift downward as later trees fit noise. That downward drift is why boosting needs a stopping rule.
Fix It With Early Stopping
Boosting libraries can stop automatically when validation performance stops improving. Scikit-learn's histogram gradient boosting, for example, supports early stopping, native missing-value handling, categorical features, and monotonic and interaction constraints, per its documentation:
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(
max_iter=2000, learning_rate=0.05,
early_stopping=True, validation_fraction=0.15, n_iter_no_change=30,
random_state=0,
).fit(Xtr, ytr)
print("trees actually used:", hgb.n_iter_)
print("test accuracy:", round(hgb.score(Xte, yte), 3))
Set max_iter high and let early stopping pick the real number. One caveat from the scikit-learn documentation: the inner validation split used by early stopping isn't ideal for time series, so for temporal data, split by time and handle stopping yourself.
Tuning Cheat Sheet
Bagging and random forests are forgiving. Tune max_features first (lower means more diversity), then min_samples_leaf and max_samples. Use plenty of trees, since more rarely hurts.
Gradient boosting has more interacting knobs:
- Learning rate and number of trees trade off: a smaller rate needs more trees but usually generalizes better. Pick a modest rate like 0.03 to 0.1 and let early stopping choose the count.
- Tree complexity: keep trees shallow (depth 3 to 8, or a limited number of leaves). Deep trees in boosting overfit quickly.
- Subsampling: row and column subsampling (typically 0.5 to 0.9) add bagging-style regularization.
- Regularization: L2 or L1 penalties on leaf values, minimum child weight or samples per leaf, and minimum gain to split.
- Imbalance: class weights or a positive-class weight, then re-tune the decision threshold on validation data.
- Categorical data: CatBoost and LightGBM handle categorical features natively, and scikit-learn's histogram boosting supports them too, as its documentation notes. XGBoost typically needs encoding, though its support has been evolving, so check your version.
Beyond Bagging and Boosting
Stacking trains a meta-model on the out-of-fold predictions of several different base models, such as a linear model, a forest, and a boosted model. It can add a little accuracy by combining different model families, but it must be built with out-of-fold predictions or it leaks. Voting and blending average or weight several models more simply. Many AutoML systems lean on layered stacking for exactly this reason.
The two main ideas cross-pollinate: forests can be built with boosting libraries, and boosting borrows row and column subsampling from bagging.
Which Should You Use?
- Start with a random forest for a strong, low-maintenance baseline that rarely needs much tuning and handles noisy data well.
- Move to gradient boosting when you want the best accuracy and can afford tuning and validation, with early stopping as standard practice.
- Prefer bagging when labels are noisy or you can't afford careful tuning.
- Prefer boosting when the signal is complex and clean enough to reward fitting residuals closely.
- Always keep a simple baseline. One published tutorial's comparison on a particular dataset found plain logistic regression beating both boosted ensembles, 0.9807 to 0.9648 and 0.9596, a good reminder that ensembles aren't automatically better. See the logistic regression guide for building that baseline.
- For small tabular datasets, also try the tabular foundation models mentioned in the algorithms guide.
Common Pitfalls
- Training boosting without early stopping or a validation set.
- Using a high learning rate with deep trees, then wondering about overfitting.
- Bagging a stable, low-variance model and expecting a big gain.
- Expecting bagging to fix underfitting, which it won't.
- Comparing models at default settings only, then declaring a winner.
- Ignoring label noise, which hurts boosting more.
- Evaluating on training data, which is near-perfect for both.
- Leaking information in stacking by using in-sample predictions to train the meta-model.
- Applying random-split early stopping to time series.
Best Book to Read
| Cover | Book | Description | Get it |
|---|---|---|---|
![]() |
An Introduction to Statistical Learning | - bagging, boosting, and random forests explained with the bias-variance lens that makes the whole picture click. | View on Amazon |
![]() |
Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow | - practical gradient boosting walkthroughs, tuning recipes, and the exercises that build intuition. | View on Amazon |
![]() |
The Elements of Statistical Learning | - the theory behind why averaging reduces variance and how boosting relates to gradient descent in function space. | View on Amazon |
As an Amazon Associate I earn from qualifying purchases. Links on this page are affiliate links, and I may earn a commission at no extra cost to you.
Unlock AI That Actually Works
Get lifetime access to GPT-6 Astra, Claude Fable 5.1, Gemini 3.5, Grok 4.5, and more — all in one platform. Build websites, apps, videos, content, and digital products from a single command. No monthly fees. No tool-hopping.
Click here to get GPTAstra Max now — one-time payment, lifetime access.
Frequently Asked Questions
What is the difference between bagging and boosting?
Bagging trains many models independently on bootstrap samples and averages their predictions, which mainly reduces variance. Boosting trains models sequentially, each new one focused on the previous models' mistakes, which mainly reduces bias. Bagging models see different data and never communicate; boosting models are built one after another and each stage corrects the last.
Does bagging reduce bias or variance?
Mainly variance. Averaging diverse, high-variance base learners smooths out sample-specific errors, but it barely reduces bias — if your base model underfits, bagging it won't fix that. This is why bagging pairs with deep, unpruned decision trees: the trees carry low bias on their own, and bagging cancels their variance. Boosting works the other way, attacking bias with weak learners stage by stage.
Why does boosting need early stopping?
Because boosting can overfit: test accuracy typically climbs quickly, may peak early, and then drifts downward as later trees start fitting noise and mislabeled points. Early stopping halts training when validation performance stops improving, so you don't pay for those extra rounds. Set max_iter high, let a validation fraction (e.g. 0.15) and n_iter_no_change (e.g. 30) pick the real number — and for time series, split by time yourself instead of using the inner random validation split.
Which should I use, random forest or gradient boosting?
Start with a random forest: it's a strong, low-maintenance baseline that rarely needs tuning and handles noisy labels well. Move to gradient boosting when you want the best accuracy and can afford tuning with early stopping as standard practice. On noisy data or with little time to tune, prefer bagging; on complex, clean signal, boosting usually wins. Always keep a simple baseline like logistic regression in the comparison.
Wrapping This Up
Bagging and boosting both turn many imperfect models into one better one, through opposite strategies. Bagging trains independent, high-variance models on bootstrap samples and averages them, cutting variance, staying robust, and rarely overfitting as you add models. Boosting trains weak models sequentially, each correcting the last, cutting bias and often reaching higher accuracy, at the cost of more tuning and a real risk of overfitting without early stopping and regularization.
Which wins? Often a tuned boosted model on clean data, and often a random forest on noisy data or with little time to tune. Run the comparison above on your own dataset, plot the number-of-trees curves, and let validation results make the call.


