Bagging (Bootstrap Aggregating): Complete Guide — Principles, Examples, and Python Implementation
Summary
Bagging (Bootstrap Aggregating) is an ensemble method introduced by Leo Breiman in 1996. Its principle is simple yet powerful: train multiple identical models on bootstrap samples (draws with replacement) from the training set, then aggregate their predictions by mean (regression) or majority vote (classification). Bagging significantly reduces the variance of unstable models — such as decision trees — without increasing bias. It forms the theoretical foundation of many modern ensemble methods, including Random Forest.
Mathematical principle of bootstrap bagging
Bootstrap sampling
Let a training set D = {(x₁, y₁), …, (xₙ, yₙ)} of size n. Bootstrap sampling consists of generating B samples D₁, D₂, …, D_B of the same size n, each obtained by drawing with replacement from D.
For each sample D_b, a model f_b is trained. After B training rounds, we have an ensemble of models {f₁, f₂, …, f_B}.
Prediction aggregation
In regression, the final prediction is the arithmetic mean:
ŷbagging(x) = (1/B) × Σ{b=1}^{B} f_b(x)
In classification, majority vote is used: the predicted class is the one that receives the most votes among the B models.
Variance reduction
Suppose each model f_b(x) has the same variance σ² and that the models are independent (approximation). The variance of the aggregated predictor is:
Var(ŷ_bagging) = σ² / B
Variance therefore decreases proportionally to the number B of models. In practice, the models are not perfectly independent (they share the same original dataset), but the variance reduction remains substantial — especially for high-variance models such as unpruned decision trees.
Important note: bagging has little effect on low-variance, high-bias models (linear regression, for example). Its effectiveness is maximal for unstable algorithms: those whose predictions vary strongly in response to small changes in the training data.
Bootstrap inclusion probability
Each observation has a probability of 1 − (1 − 1/n)ⁿ ≈ 1 − e⁻¹ ≈ 0.632 of being selected in a given bootstrap sample. Approximately 63.2% of the original data appears in each sample; the remaining 36.8% constitutes the Out-of-Bag (OOB) sample, usable for error estimation without a separate validation set.
Intuition: why bootstrap bagging works
Imagine consulting B independent doctors for a complex diagnosis. Each doctor has their own reasoning, their own biases, their own blind spots. Individually, each can be wrong. But if you take the majority decision among the B opinions, the collective error is generally less than that of a single doctor.
Bootstrap bagging applies exactly this logic to machine learning models:
- Each bootstrap sample is each model’s unique “lived experience.”
- The models learn slightly different patterns because they see different subsets of the data.
- By aggregating, we smooth the idiosyncratic errors of each model.
It is the algorithmic equivalent of the wisdom of crowds: the average of many imperfect opinions is often better than the most enlightened opinion taken in isolation.
Python implementation with scikit-learn
BaggingClassifier on DecisionTreeClassifier
Let’s compare a single decision tree with a bagged ensemble of 50 trees:
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
# Synthetic data
X, y = make_classification(
n_samples=5000, n_features=20, n_informative=15,
n_redundant=5, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Single decision tree (unlimited depth)
tree = DecisionTreeClassifier(random_state=42)
tree.fit(X_train, y_train)
tree_pred = tree.predict(X_test)
tree_acc = accuracy_score(y_test, tree_pred)
print(f"Single tree : {tree_acc:.4f}")
# Bagging of 50 decision trees
bagging = BaggingClassifier(
estimator=DecisionTreeClassifier(),
n_estimators=50,
max_samples=1.0,
bootstrap=True,
random_state=42,
n_jobs=-1
)
bagging.fit(X_train, y_train)
bag_pred = bagging.predict(X_test)
bag_acc = accuracy_score(y_test, bag_pred)
print(f"Bagging (50 trees) : {bag_acc:.4f}")
# Comparison with Random Forest
rf = RandomForestClassifier(n_estimators=50, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
rf_acc = accuracy_score(y_test, rf.predict(X_test))
print(f"Random Forest : {rf_acc:.4f}")
The difference is often striking: a deep tree overfits and may achieve decent accuracy on training but poor accuracy on test. Bootstrap bagging reduces this overfitting by averaging the predictions of 50 trees that each learned on slightly different data.
BaggingRegressor for regression
from sklearn.datasets import make_regression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import BaggingRegressor
from sklearn.metrics import mean_squared_error, r2_score
# Regression data
X_reg, y_reg = make_regression(
n_samples=3000, n_features=15, noise=15.0, random_state=42
)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
# Single tree
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(X_train_r, y_train_r)
print(f"Single tree (MSE) : {mean_squared_error(y_test_r, tree_reg.predict(X_test_r)):.2f}")
# BaggingRegressor
bag_reg = BaggingRegressor(
estimator=DecisionTreeRegressor(),
n_estimators=100,
random_state=42
)
bag_reg.fit(X_train_r, y_train_r)
bag_pred_r = bag_reg.predict(X_test_r)
print(f"Bagging (MSE) : {mean_squared_error(y_test_r, bag_pred_r):.2f}")
print(f"Bagging (R²) : {r2_score(y_test_r, bag_pred_r):.4f}")
Out-of-Bag (OOB) estimation
The Out-of-Bag sample allows estimating the generalization error without a separate validation set. Each observation is predicted by the models that did not see it during training:
bagging_oob = BaggingClassifier(
estimator=DecisionTreeClassifier(),
n_estimators=100,
oob_score=True,
random_state=42,
n_jobs=-1
)
bagging_oob.fit(X_train, y_train)
print(f"OOB score : {bagging_oob.oob_score_:.4f}")
print(f"Test score : {bagging_oob.score(X_test, y_test):.4f}")
The OOB score is an unbiased estimate of generalization performance, comparable to cross-validation but much faster since it reuses existing training.
Visualizing bagging convergence
import matplotlib.pyplot as plt
# Convergence analysis as a function of the number of estimators
scores = []
for n in range(1, 101):
model = BaggingClassifier(
estimator=DecisionTreeClassifier(),
n_estimators=n,
random_state=42
)
model.fit(X_train, y_train)
scores.append(accuracy_score(y_test, model.predict(X_test)))
plt.figure(figsize=(10, 6))
plt.plot(range(1, 101), scores, marker='o', markersize=3,
linewidth=1.5, color='steelblue')
plt.axhline(y=tree_acc, color='red', linestyle='--',
label=f'Single tree ({tree_acc:.3f})')
plt.xlabel("Number of estimators (B)")
plt.ylabel('Accuracy')
plt.title('Bootstrap bagging convergence as a function of B')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
This curve typically shows rapid improvement up to B ≈ 20-30, then a plateau where adding more estimators no longer provides significant gains.
Key hyperparameters of bootstrap bagging
| Parameter | Description | Default value | Impact |
|---|---|---|---|
n_estimators |
Number of models in the ensemble | 10 | ↑ improves variance reduction, but gains become marginal beyond a certain threshold |
max_samples |
Proportion or number of samples per model | 1.0 (100%) | Smaller = more diversity, but each model is less accurate |
max_features |
Proportion or number of features per model | 1.0 | Limiting increases diversity (the principle behind Random Forest) |
bootstrap |
Sampling with replacement | True | Without bootstrap, bagging loses its regularization effect |
bootstrap_features |
Sampling with replacement of features | False | Useful when the number of features is very high |
oob_score |
Compute Out-of-Bag score | False | Enables error estimation without cross-validation |
Practical tuning guide
- Start with
n_estimators=50to100. Increasing beyond 100 rarely provides significant gains. - max_samples=0.8 can improve model diversity at the cost of a slight decrease in individual performance.
- max_features < 1.0 progressively transforms bootstrap bagging into Random Forest: it is the double randomization mechanism (samples × features) that makes Random Forest so powerful.
- Enable
oob_score=Trueto get a free estimate of generalization during training.
Advantages of bootstrap bagging
- Massive variance reduction: especially effective for unstable models (deep decision trees, k-NN with small k, neural networks).
- Trivial parallelization: each f_b model is trained independently of the others — bootstrap bagging adapts perfectly to distributed computing with
n_jobs=-1. - No increased overfitting: unlike boosting, bagging does not overfit when B is increased.
- Free OOB estimation: the Out-of-Bag sample provides integrated validation at no additional cost.
- Conceptual simplicity: the principle is easy to explain and implement.
Limitations of bootstrap bagging
- Ineffective for stable models: linear regression, logistic regression, or linear SVMs benefit little from bootstrap bagging because their variance is already low.
- Reduced interpretability: an ensemble of 100 trees is far less interpretable than a single tree.
- Computational cost: training B models costs B times more in time and memory.
- No bias correction: if base models have high bias (underfitting), bagging does not correct it — it only reduces variance.
- Less powerful than boosting: on many benchmarks, gradient boosting and XGBoost surpass simple bagging in raw accuracy, at the cost of greater complexity.
- No feature subsampling by default: unlike Random Forest, standard bagging uses all features for each model, which limits ensemble diversity.
4 real-world use cases of bootstrap bagging
1. Banking fraud detection
Fraudulent transactions are rare and data is imbalanced. A single decision tree tends to overfit on patterns specific to the training set. Bagging 100 trees, each trained on a different bootstrap subsample, captures a wider variety of fraud patterns while reducing false positives:
bagging_fraud = BaggingClassifier(
estimator=DecisionTreeClassifier(max_depth=8),
n_estimators=100,
max_samples=0.7,
oob_score=True,
random_state=42
)
bagging_fraud.fit(X_train_fraud, y_train_fraud)
2. Real estate price prediction
Real estate data contains many non-linear interactions between variables (neighborhood × area × year of construction). A BaggingRegressor with 200 trees captures these interactions better than a linear model, while remaining more robust than a single tree prone to overfitting.
3. Medical diagnosis assistance
The multiple-doctor analogy is literal here: training 50 classifiers on bootstrap samples of medical data allows combining different “perspectives” on the same symptoms, reducing the risk that an anomalous pattern in the training set influences the final diagnosis. The OOB score provides rigorous validation without reducing the size of the training set.
4. Text classification with weak classifiers
Bootstrap bagging can be applied to any classifier. For example, individually mediocre Naïve Bayes classifiers on a text corpus can achieve remarkable accuracy when 50 are aggregated by majority vote — each classifier capturing different aspects of vocabulary through bootstrap samples.
Bootstrap bagging in the ensemble method ecosystem
Bagging occupies a central place in the ensemble method family:
- Random Forest = bagging + random feature selection (article #025). This is the most famous and most widely used case of bootstrap bagging.
- AdaBoost = adaptive weighting of misclassified samples (article #030). Unlike bagging, samples are drawn without replacement with adjusted weights.
- Gradient Boosting = sequential learning of residual errors (article #027). Each model corrects the errors of the previous one, whereas bagging trains all models in parallel.
The fundamental distinction is clear: bagging seeks to reduce variance by aggregating independent models, while boosting seeks to reduce bias by sequentially learning from errors.
Conclusion
Bootstrap bagging is one of the most elegant ideas in modern machine learning. By leveraging the power of averaging over models trained on different bootstrap samples, it transforms unstable algorithms into robust and reliable predictors. While more sophisticated methods like gradient boosting surpass it in raw accuracy on certain benchmarks, bagging remains indispensable for its simplicity, native parallelization, and resistance to overfitting.
For any problem where a single decision tree overfits, bootstrap bagging is the first improvement to try — often with remarkable results.
See also
- Solving the Maximum Subarray Interview Problem with Python
- Implementing Dekker’s Algorithm in Python: Efficient Thread Synchronization

