Efron's bootstrap is the best value-for-effort idea in applied statistics. Resample your own data with replacement, recompute the statistic, read the confidence interval off the spread. Four lines, works for statistics nobody has derived a variance for.
It is also routinely sold as assumption-free, which it is not — and the honest way to show that is not a warning paragraph. It is measured coverage.
Everything below runs in your browser: https://dev48.infy.uk/ml/day65-bootstrap.html
The only test that matters
let covered = 0;
for (let t = 0; t < TRIALS; t++){
const sample = drawFrom(population, n); // truth is KNOWN
const ci = bootstrapCI(sample, statistic, B);
if (ci.lo <= truth && truth <= ci.hi) covered++;
}
coverage = covered / TRIALS; // should be 0.95
Everything else is machinery. This loop is the argument.
| statistic | population | n | measured coverage |
|---|---|---|---|
| mean | normal | 100 | 94.5% |
| variance | lognormal | 10 | 60.6% |
| maximum | uniform | 40 | 0.0% |
A nominal 95% interval that covers 60.6% of the time is not a 95% interval.
The failure that is not fixable
The maximum is not a small-sample wobble. A resample is drawn from the observed values, so:
assert(Math.max(...resample) <= Math.max(...sample)); // ALWAYS
assert(Math.max(...boot) === Math.max(...sample)); // therefore
The bootstrap distribution of the max is pinned at the sample max from above, while the truth is above it. Verified over 200 datasets: 0 resamples ever exceeded the sample maximum, and 63.8% sit exactly on that ceiling — against a theoretical 1 − (1 − 1/n)ⁿ → 1 − 1/e = 63.2%.
Every miss has the truth above the interval; none below. That direction is forced by the structure, and no B fixes it. BCa cannot rescue it either — no reweighting of a bounded distribution reaches past its bound.
The general lesson is worth more than the special case: the bootstrap works when the statistic is a smooth function of the data. Extremes are not smooth. One observation determines the answer.
Three intervals, and they are not variations of each other
Percentile is the obvious one. Basic reflects around the estimate — and the ends swap:
percentile = [lo, hi];
basic = [2 * theta - hi, 2 * theta - lo]; // note the order
The swap is the idea. If the bootstrap distribution leans right, the estimator probably does too, so the correction leans left. Copy the ends across without swapping and the interval sometimes inverts.
BCa shifts the quantiles using a bias term measured from the bootstrap distribution and an acceleration term from a real jackknife:
const z0 = probit(fraction(boot, b => b < theta));
const jack = sample.map((_, i) => statistic(without(sample, i)));
const a = sum(d³) / (6 * sum(d²)^1.5);
Both are measured from the data, so on a symmetric problem both go to ~0 and BCa collapses to the percentile interval — which is the identity the tests check first. A BCa that differs from percentile on a symmetric problem has a bug in one of its two corrections.
B is not the accuracy dial
Two errors get conflated constantly:
- Monte Carlo error — you only drew B resamples. Fixed by raising B.
- Bootstrap approximation error — your sample is not the population. Fixed only by more data.
Measured, on the same problem:
- B from 200 → 2,000 moves coverage by 1.3 points
- n from 20 → 200 moves it by 18 points
B ≈ 2,000 is enough. Spend the rest on a bigger sample.
The assertion worth stealing
assert(coverage(mean, normal, n=100) > 0.93); // it works where it works
assert(coverage(max, anything) < 0.60); // and fails where it fails
Asserting that a method fails where it should fail is how you find out your implementation is doing the thing you think it is, rather than something that merely produces plausible numbers. If my max coverage had come back at 91%, the bug would have been in my code and I would never have known.
When to reach for it
Smooth statistics — means, ratios, correlations, model metrics — yes, and BCa if the distribution looks skewed. Extremes, minima, maxima, anything determined by one observation — no. Dependent data — not the ordinary bootstrap; resampling rows destroys the dependence structure. Tiny samples — check coverage before you trust it.
Part of a from-scratch series — one ML idea a day, computed rather than quoted: https://dev48.infy.uk/machinelearningfromzero.php
Top comments (0)