Bootstrap and jackknife: standard errors, confidence intervals and bands around a curve
How resampling the data gives a standard error, a bias estimate and confidence intervals for almost any statistic, how normal, basic, percentile and BCa intervals differ, how the jackknife compares, and how the same trick puts confidence and prediction bands around a fitted curve.
University lab From my coursework in the MSc in Statistics and Machine Learning at Linköping University, rewritten as a note to review later.
How sure am I about a sample mean? There’s a formula for that. But what about a median, a ratio, or the curve a regression tree draws? Usually there’s no formula at all. I ran into this twice in my first year at LiU: once with the mean price of 110 houses, once with a regression tree on 48 points. Both times the answer was the bootstrap. The idea is almost cheeky: pretend your sample is the population, resample from it many times, and watch how much your answer moves.
The bootstrap in one paragraph
Say I have data and some statistic . I draw values with replacement from the data, compute the statistic on that new sample, and do it again, times. Now I have . How spread out they are tells me how much would wobble if I could collect new data. From them:
where is the average of the bootstrap values and is the bias-corrected estimate. For the mean the bias is zero in theory, so just wobbles around . In the lab the mean price was about 1080.47, the average of 3000 bootstrap means 1080.48, and their standard deviation about 35.6.
Four ways to turn it into an interval
Next question: how do I get a 95% interval out of those numbers? It turns out there are several answers. Let be the -quantile of the bootstrap values, and the standard normal quantile.
- Normal (first-order): . Assumes the sampling distribution is roughly normal. Symmetric by construction.
- Percentile: . Just read off the middle 95% of the bootstrap values. It follows skewness, and it doesn’t change if you transform the parameter (for example take logs).
- Basic (reverse percentile): . This one confused me at first. It uses the bootstrap to estimate the distribution of the error and flips it around . So if the bootstrap values have a long right tail, the basic interval gets a long left tail.
- BCa (bias-corrected and accelerated): still percentiles, but at adjusted levels instead of 0.025 and 0.975:
and the same with for . Here is the standard normal CDF, corrects for median bias, and the acceleration (usually estimated from jackknife values) corrects for the standard error changing with . In theory it’s the most accurate of the four, and it needs a larger (a few thousand) because it reads quantiles far out in the tails.
For a mean of 110 fairly well-behaved prices, all four came out close. In one version of our report, percentile gave (1015, 1152), basic (1009, 1146) and normal (1011, 1150). In the other version, with , BCa gave (1017, 1157), shifted a little right, as you’d expect for right-skewed prices.
Looking back, our report had two mistakes. First, one section was titled “BCa” but the code called boot.ci(..., type = "basic"). Those are different intervals; for BCa it’s type = "bca". Second, a draft table worked out a “mean” and “variance” for each interval by taking its midpoint and assuming the width was standard deviations. That only makes sense for the normal interval. Percentile and BCa intervals don’t have to be symmetric around the estimate, and that asymmetry is exactly the extra information they give.
The jackknife
The jackknife is the older, simpler cousin. Instead of random resamples, leave out one observation at a time. With the statistic computed without , and their average:
Why the big factor ? Because the leave-one-out values are much closer to each other than independent resamples would be, so their spread has to be scaled up.
For the mean, a bit of algebra shows exactly, where is the usual sample variance with . The ideal bootstrap (infinite ) gives , slightly smaller, because resampling uses the plug-in variance with . In the lab the jackknife variance of the mean was 1320.91 and the bootstrap variances were 1268.5 and 1284.3 in the two runs. The jackknife being larger is expected. Most of the gap here is Monte Carlo noise from finite , since the factor alone only explains about 1%.
The jackknife is cheap ( evaluations) and gives the same answer every time, but it only works well for smooth statistics. For the median it’s inconsistent: removing one point barely moves the median, or moves it in jumps.
library(boot)
set.seed(7)
price <- rgamma(110, shape = 4, scale = 270) # right-skewed, like house prices
b <- boot(price, statistic = function(d, i) mean(d[i]), R = 3000)
c(estimate = b$t0, bias = mean(b$t) - b$t0, se = sd(b$t))
boot.ci(b, type = c("norm", "basic", "perc", "bca"))
n <- length(price)
theta_i <- sapply(1:n, function(i) mean(price[-i])) # leave-one-out means
se_jack <- sqrt((n - 1) / n * sum((theta_i - mean(theta_i))^2))
c(se_boot = sd(b$t), se_jack = se_jack, formula = sd(price) / sqrt(n)) # last two equal
From one number to a whole curve
In the machine learning lab the “statistic” wasn’t a number. It was a fitted regression tree, a whole curve. Same trick, though: refit the model on many fake datasets and look at how much the curve moves at each . Here I had to ask myself which question I was actually answering.
- Confidence band: where is the true curve ? Only the uncertainty in the fit counts.
- Prediction band: where will a new observation at land? That’s the fit uncertainty plus the noise around the curve:
where is the noise variance and the new noise is independent of the fit. The first term shrinks with more data; the second doesn’t. So a prediction band never collapses onto the curve. A sanity check I used: about 95% of the observed points should fall inside a 95% prediction band (in my lab 2 of 48 fell outside, which is fine). Nothing like that holds for a confidence band.
There are also two ways to make the fake datasets:
- Non-parametric: resample the rows with replacement, refit, predict on a grid of values. At each grid point, the 2.5% and 97.5% quantiles of the predictions give a pointwise 95% confidence band. No distribution assumed. But some resamples miss whole regions of , so the band can get bumpy, especially for a step-shaped model like a tree.
- Parametric: assume a noise model, for example , with the fit to the real data and the residual standard deviation. Keep the fixed, simulate new , refit, predict. The band comes out smoother, but only if the noise model is roughly right. My residuals were skewed, so the normal assumption was doubtful, and a smooth band can look more trustworthy than it is.
For a prediction band with the parametric bootstrap, add fresh noise to each bootstrap prediction, with , then take quantiles as before.
set.seed(1)
d <- data.frame(x = sort(runif(60, 0, 10)))
d$y <- sin(d$x) + rnorm(60, sd = 0.4)
grid <- data.frame(x = seq(0, 10, length.out = 50))
fit_curve <- function(data) predict(lm(y ~ poly(x, 5), data = data), newdata = grid)
np <- boot(d, function(data, i) fit_curve(data[i, ]), R = 1000) # resample rows
fit0 <- lm(y ~ poly(x, 5), data = d) # keep x, simulate y
s <- sigma(fit0)
gen <- function(data, mle) { data$y <- rnorm(nrow(data), fitted(fit0), s); data }
pb_conf <- boot(d, fit_curve, R = 1000, sim = "parametric", ran.gen = gen)
pb_pred <- boot(d, function(data) fit_curve(data) + rnorm(nrow(grid), 0, s),
R = 1000, sim = "parametric", ran.gen = gen)
band <- function(b) apply(b$t, 2, quantile, probs = c(0.025, 0.975))
width <- function(b) mean(diff(band(b)))
c(nonparam_conf = width(np), param_conf = width(pb_conf), param_pred = width(pb_pred))
# about 0.69, 0.51 and 1.64: the prediction band is roughly 3 times wider
Things that tripped me up with bands:
- Pointwise vs simultaneous. Quantiles at each separately give a pointwise band: at any single the true curve is inside with about 95% probability. The whole curve staying inside everywhere at once is less likely.
boot::envelope()returns both:$pointis pointwise and$overallis a wider simultaneous band. boot.ci()is for one number. When the statistic returns a vector of predictions,boot.ci()only looks at one element (the first, by default). For bands, take quantiles across the columns ofb$t.- Wide bands aren’t a bug. Bumpy, wide bands for a tree on 48 points mostly say “this model is unstable on this little data”, which is useful to know.
For testing a hypothesis by resampling instead, see permutation tests.
Key takeaways
- The bootstrap approximates the sampling distribution by resampling the data with replacement. It gives a standard error and a bias estimate for almost any statistic.
- Normal intervals are symmetric; percentile intervals follow the bootstrap distribution; basic intervals flip it; BCa adjusts the percentile levels for bias and skewness. Check which
typeyou actually pass toboot.ci(). - The jackknife (leave-one-out) gives exactly for the mean, slightly above the bootstrap’s value. It fails for non-smooth statistics like the median.
- Around a fitted curve, a confidence band is about the curve and a prediction band is about new points (it adds the noise variance). Non-parametric bootstrap resamples rows; parametric bootstrap simulates from a noise model and is only as good as that model.
- Quantiles at each give pointwise bands; simultaneous bands are wider.
Resources
- 732A90 Computational Statistics, Linköping University, Lab 5 (2020).
- 732A99 Machine Learning, Linköping University, Block 1 Lab 2 (autumn 2019).
- Bradley Efron and Robert J. Tibshirani, An Introduction to the Bootstrap, Chapman & Hall, 1993, Chapters 6 (standard errors, parametric bootstrap), 9 (regression), 10-11 (bias, jackknife), 12-14 (confidence intervals, BCa).
- A. C. Davison and D. V. Hinkley, Bootstrap Methods and their Application, Cambridge University Press, 1997, Chapter 5.
- Bradley Efron, “Better bootstrap confidence intervals”, Journal of the American Statistical Association 82(397), 1987.
- Trevor Hastie, Robert Tibshirani, Jerome Friedman, The Elements of Statistical Learning, 2nd ed., Section 8.2: https://hastie.su.domains/ElemStatLearn/
- R package
boot(Canty and Ripley), documentation forboot,boot.ci,envelope: https://cran.r-project.org/package=boot