The bootstrap, permutation tests and A/B testing
Before this lesson: lesson 11 (p-values, power, the two-proportion z-test, multiple testing), lesson 04 (the variance of a sum, correlation), lesson 06 (standard errors and confidence intervals). Time: about 80 minutes, plus the notebook.
Why this matters #
- Error bars for any ML metric. Accuracy, AUC, F1, BLEU or an LLM benchmark score: the bootstrap over test examples gives an interval for each, and a paired bootstrap gives an interval for the difference of two models.
- A/B tests are how ML systems are launched. New ranking, recommendation, search and ads models, and new LLM features, go to real users through A/B tests. Data scientist and ML engineer interviews ask about sample size, peeking and CUPED.
- Bagging and random forests train each model on a bootstrap resample; the points left out of a resample (about 37%) give the out-of-bag error.
- Small randomised studies in science (a few patients, plants or field plots) use permutation tests, because they need no normality assumption and are exact.
1. Predict first #
2. The picture #
Permutation: shuffle the labels. Six students prepare for an exam. Three of them, chosen at random, use a new study app. Their score gains are 7, 9 and 12 points. The other three gain 3, 5 and 6 points. The statistic is the difference of the group means, "mean of the app group minus mean of the other group":
Under the app does nothing. Then each student would have the same gain with or without the app, and the labels "app" and "no app" are just a random split of six numbers into two groups of three. There are such splits, all equally likely under . Compute for each one:
Only the observed split reaches , so the one-sided p-value is . Two splits reach , so the two-sided p-value is . No formula and no normality assumption were used. Notice also the floor: with 3 against 3, no outcome can give a one-sided p-value below .
Press "Shuffle labels once" a few times: the blue dots move to a random new split, and the orange marks show the two group means. Each shuffle adds one value to the violet histogram of the shuffled statistic . Then press All splits: the histogram shows all 20 splits, and the magenta bars beyond the observed (yellow) give the exact p-value. Switch to 20 users (time to finish an old and a new sign-up form): there are 184,756 splits, so the widget samples 1,000 of them and uses .
Bootstrap: resample the data. You want the spread of a statistic (a mean, a median, an AUC) over repeated samples from the unknown distribution . You cannot draw new samples from . So draw them from the closest thing you have: the empirical distribution , which puts mass on each observation. A sample from is draws with replacement from the data: some points appear twice or more, others not at all.
| real world | bootstrap world |
|---|---|
| unknown | known |
| sample from | resample from (with replacement) |
| estimate ; true value | replicate ; "true" value |
| spread of : unknown | spread of : simulate it |
The bootstrap assumes that the right column behaves like the left column. It approximates the sampling distribution of , not the population.
Press "One resample" and count: the blue dots are the 20 data points; the small dots above each point show how often it was drawn (aqua = once, yellow = twice or more), and faded points were not drawn. Then press 1,000 resamples: the aqua histogram is the bootstrap distribution, and the orange lines are the 95% percentile interval. The dashed line is the true , which we know here because we generated the data. Check coverage repeats everything on 200 new data sets. Now switch to the maximum: the interval turns magenta, because it can never contain .
The data are 20 values from , and the sample maximum is . Where is the 95% percentile bootstrap interval for ?
Every resample uses only the observed values, so every resample maximum is at most . But always, so the interval never contains : its coverage is 0. The exact interval comes from a different method, the pivot (section 3).
3. Definitions and main results #
Permutation tests #
Two samples and are pooled into , . A statistic compares the groups, for example the difference in means. The null hypothesis is that the pooled vector is exchangeable: its joint distribution does not change when you permute its entries. Two i.i.d. samples from the same distribution are exchangeable. So are units that a randomised experiment assigned at random to two treatments that have no effect.
Permutation p-values. For the exact test, compute on all splits and let be the fraction with (the observed split is one of them). When is too large, draw random permutations, compute , count , and use
Theorem (permutation tests are exact). Under exchangeability, both p-values satisfy for every , every , and (for the Monte Carlo version) every .
Proof (Monte Carlo version; the exact version uses the same argument with all splits).
- Condition on the multiset of pooled values. Under exchangeability, the observed labelling is a uniformly random labelling of these values. Each random permutation gives another uniformly random labelling, independent of the first.
- So are i.i.d. given the multiset, hence exchangeable: is equally likely to take each of the ranks.
- Without ties, for , because means that is among the largest. Ties can only make larger, so in general .
- exactly when . So .
The "+1" counts the observed labelling as one of the permutations, which it is. Without it, can be 0, a p-value that claims the impossible, and the test can reject too often. The smallest possible p-value is . The Monte Carlo error of is about , so use for a quick look, or more for a final answer, and many more for a tiny threshold (Bonferroni over many tests).
Practical points.
- Two-sided p-values. Count , or take twice the smaller one-sided p-value (SciPy does this). They agree when the permutation distribution is symmetric, as in section 2.
- Any statistic works: a difference in medians, a t-statistic, a correlation. To test the independence of two variables, permute one column and keep the other fixed.
- Paired data: under the sign of each within-pair difference is random, so flip signs instead of shuffling labels.
- What really says: the two groups have the same distribution, not only the same mean. If the variances differ under , the labels are not exchangeable, and the permutation test of means is no longer exact. Use Welch (lesson 11), or permute a studentised statistic.
- Dependent data (time series, users with many sessions) are not exchangeable unit by unit. Permute whole blocks or whole clusters.
The bootstrap #
Let be a statistic. The bootstrap distribution is the distribution of when the are drawn i.i.d. from . You approximate it with resamples .
- Bootstrap standard error: the standard deviation of .
- Percentile interval at level : the and quantiles of the .
- How many resamples: about 1,000 for a standard error, and 10,000 or more for interval endpoints, so that two analysts with the same data get the same answer.
Who is in a resample? A given point is missed by one draw with probability , so it appears in the resample with probability . This is 0.651 for , 0.634 for , and it tends to . A resample contains about 63% of the distinct points; the rest of its entries are repeats.
The mean, exactly. For , the bootstrap variance is with the plug-in variance . So the bootstrap standard error is times the usual : slightly too small. This "narrowness bias" matters only for small .
Why the bootstrap works, and how well
For means (with finite variance), smooth functions of means, and quantiles of a distribution with a continuous
positive density, the distribution of given the data converges to the limit
distribution of . So the bootstrap gets the standard error and the shape right to first
order. The percentile interval would be exact if some monotone transformation made
symmetric about 0, with the same distribution in both worlds. Otherwise each of its two tails has a coverage error of
order ; for skewed statistics most misses fall on one side. The bootstrap-t and BCa (bias-corrected and
accelerated) intervals reduce this error to order . BCa is the default of scipy.stats.bootstrap.
Checking coverage by simulation. Pick a known and a true . Repeat many times: draw a sample of size from , compute the interval, record whether it contains . The share of hits estimates the coverage; with repetitions its standard error is about . Section 5 does this: the 95% percentile interval covers the mean of Exp(1) data with in about 93% of data sets, the median with in about 95%, and the maximum of uniform data in 0%.
When the bootstrap fails, and what to use instead.
- Extremes. For the maximum of data, every resample has , so the percentile interval never contains . Also , while the true distribution of is continuous. Instead: the exact pivot , whose CDF is on ; it gives the interval with coverage exactly . Or extreme-value theory, or an -out-of- bootstrap.
- Heavy tails. For the mean of data with infinite variance (Cauchy, Pareto with tail index below 2), the bootstrap is inconsistent. Even with finite but huge variance (revenue per user), one extreme value decides each resample. Instead: a robust statistic (median, trimmed mean), capping (winsorising) the metric, or a parametric tail model.
- Dependent data. Resampling single time steps of an autocorrelated series destroys the correlation, so the standard error is far too small. For the mean of an AR(1) series of length 500, the true SE is 0.150 and the i.i.d. bootstrap gives 0.062 (notebook, exercise 3). Instead: a moving block bootstrap: resample blocks of consecutive values, with much longer than the correlation time (blocks of 25 give 0.135 here). Or a cluster bootstrap that resamples whole units (users, stations).
- Very small . With the empirical distribution has at most 5 points, and the percentile interval undercovers. Instead: a t interval, a bootstrap-t interval, an exact or parametric method, or more data.
Permutation vs bootstrap. The permutation test is exact for finite samples, because it uses a symmetry that implies. The bootstrap is approximate, because is close to only for large . Use a permutation test to test "no difference" in a randomised comparison; use the bootstrap for standard errors and intervals.
Comparing two ML models on one test set #
Two classifiers are scored on the same test examples. The two accuracies are strongly correlated (both models find the same examples easy), so treat them as paired data:
- For each example , compute the per-example score difference (for accuracy: ).
- Draw bootstrap resamples of the example indices. Use the same resample for both models.
- The percentile interval of the resampled mean difference is the interval for the gain. Or run a permutation test that swaps the two models' outputs on each example (a sign flip of ).
Resample the independent unit (documents, users, patients), not tokens or pixels. The test set is only one source of noise: retrain with several seeds too, and report the spread over seeds. A gain smaller than the seed-to-seed variation is not a result.
A/B tests: sample size #
Users are randomised to control (arm 1) and treatment (arm 2), users per arm. The metric has standard deviation per user. The minimum detectable effect (MDE) is the smallest true difference you want to detect, with a two-sided test at level and power .
Theorem (users per arm, equal variances). With the normal approximation,
Proof.
- The difference in means has standard error . The test rejects when .
- If the true difference is , then . Ignore the tiny chance of rejecting in the wrong direction: .
- Set the power to : , so .
- Solve for .
Rule of thumb. For and power 0.8, , so
(Kohavi et al., 2009). For power 0.9 the constant is 21.
Two proportions. For conversion rates (baseline) and , the variance of one user is , and it is not the same under and under . The test uses the pooled variance under , with ; the power uses the variances under . The same four steps give
with (from ) and (from ).
Move the MDE slider and watch the users per arm. The orange curve is the power as a function of (log scale); the yellow lines mark the planned and the target power. Halve the MDE: grows about 4 times, because . Raise the baseline rate toward 50%: the variance grows, so grows too. Simulate runs 1,000 whole A/B tests at the planned ; the magenta dot is the share that reject, close to the target power.
What the formula says.
- : half the MDE needs about 4 times the users.
- : a metric with less noise (conversion instead of revenue) or less variance (CUPED, below) needs fewer users.
- An MDE is absolute (4.0% → 4.5%, ) or relative (+12.5%). Keep and in the same units.
- An unequal split costs power: with a share of users in treatment, the test needs about times as many users as a 50/50 test (2.8 times for 90/10).
At baseline 20%, and power 0.8, which is closest to the users per arm needed to detect +2 percentage points (to 22%)?
The rule of thumb gives , and the exact two-proportion formula gives about 6,510. 1,600 is the answer for an MDE of 4 points (half the MDE needs 4 times the users, so twice the MDE needs a quarter). 65,000 is the answer for an MDE of about 0.63 points, and 650 is ten times too small.
A/B tests: the main pitfalls #
- Peeking (optional stopping). If you check the p-value repeatedly and stop at the first , the type I error grows with the number of looks. With equally spaced looks at a fixed planned sample it is about 0.08 with 2 looks, 0.14 with 5, 0.19 with 10 and 0.25 with 20 (section 5). The z-statistics at different looks share most of their data, so they are strongly but not perfectly correlated; each new look adds a new chance to cross 1.96. With unlimited looks the error tends to 1. Prevent: fix the sample size in advance and analyse once, or use a sequential design built for many looks (group-sequential boundaries such as Pocock or O'Brien–Fleming, or always-valid p-values).
- Sample-ratio mismatch (SRM). The observed split differs from the design (say 50/50) more than chance allows. Test the assignment counts with a χ² goodness-of-fit test and a strict threshold such as . Example: 50,600 vs 49,400 users gives and . An SRM means a bug in assignment, logging or filtering (for example, the treatment crashes for some browsers, and those users are never logged). The remaining users of the two arms are then not comparable. Do not trust the result until you know the cause.
- Novelty and primacy effects. Users react to the change itself: they click a new feature because it is new (novelty), or they are slower with a new layout at first (primacy). Detect: plot the effect by day, and compare new with returning users. Prevent: run whole weeks (this also handles day-of-week effects), long enough for the effect to settle.
- Many metrics or many variants. With 20 independent metrics at , the chance of at least one false positive is . Prevent: choose one primary metric (the overall evaluation criterion, OEC) before the test, correct the others for multiplicity (Holm or BH, lesson 11), and treat surprising secondary results as ideas for a new test.
- Interference. The analysis assumes that one user's outcome does not depend on the other users' assignments (SUTVA, the stable unit treatment value assumption). Social networks, two-sided marketplaces and shared ad budgets break it. Prevent: randomise clusters (friend groups, regions), use switchback designs (switch the whole system between versions over time), or randomise markets.
- Unit of randomisation vs unit of analysis. You randomise users but compute the metric per page view. The views of one user are correlated, so the naive variance, which treats them as independent, is too small, and so are the p-values. With views per user and intra-user correlation , the variance is larger by the factor . Prevent: analyse at the unit of randomisation (one value per user), or use the delta method or a user-level bootstrap for ratio metrics such as clicks per view.
A healthy experimentation system also runs A/A tests: about 5% of them should be significant at , and their splits should match the design.
Press "Run one A/A test" a few times. is true; the blue path is the z-statistic as the data arrive, and the yellow dots are the looks. A test that crosses the dashed ±1.96 lines at a look stops with a false positive. Then press Run 1,000 A/A tests: the lower chart shows the false-positive rate for 1 to 100 looks. With one look it is 0.05, the planned level; with 20 looks it is about 0.25.
CUPED: variance reduction with pre-experiment data #
Let be the outcome of a user during the experiment, and a covariate measured before the experiment started, often the same metric in the weeks before. CUPED (controlled-experiment using pre-experiment data; Deng et al., 2013) replaces by
Theorem (CUPED). Let , and let be the true treatment effect on the mean of . (a) is smallest at , where it equals . (b) For every fixed , the estimate is unbiased for .
Proof.
- , a parabola in (lesson 04: the variance of a difference).
- Its minimum is at , with value . This proves (a).
- In the difference of the two arms, cancels: . So you never need to know .
- is measured before the random assignment, so it has the same distribution in both arms: .
- Hence . This proves (b).
The variance of the estimate also falls by the factor , so the users needed fall by the same factor. With the factor is 0.51: about half the users, or half the duration. Deng et al. report variance reductions of about 50% for some metrics at Bing with 1–2 weeks of pre-experiment data.
Press "Run 500 experiments" and compare the two histograms. The scatter shows one experiment: control users (blue) and treatment users (aqua), with the common slope . The violet histogram is the plain difference in means, the aqua histogram the CUPED estimate: same centre , but narrower by about . Move to 0 and to 0.9. Then switch the covariate to in-experiment Z, a value that the treatment changes: the magenta histogram is centred at a negative effect.
Details that matter.
- Use one for both arms, estimated from the pooled data. Estimating adds a bias of order only.
- Never use a covariate that the treatment can change. Then , and step 4 fails: the correction removes part of the real effect, or even flips its sign (section 5).
- Users without pre-period data: add an indicator "has pre-period data" as a second covariate.
- The same idea elsewhere: CUPED is a control variate (lesson 14), with cancelled by randomisation instead of being known. It is also close to regression adjustment (ANCOVA): regress on the treatment indicator and (lesson 13).
A team wants to use CUPED with "number of sessions during the experiment" as the covariate, because it has correlation 0.9 with revenue. What is wrong?
Unbiasedness needs , which holds only for covariates fixed before the assignment. If the treatment raises sessions, the correction subtracts part of the real effect. The variance is small, but the estimate is centred at the wrong value.
Answers to section 1. (1) About 63: each point appears with probability . (2) About 25%, not 5% (the peeking simulation in section 5 gives 0.247). (3) About 4 times as many, because : halving multiplies by 4 (the exact formula gives 3.9 at this baseline).
4. Worked example #
A. A bootstrap by hand. Take observations , with mean 3. There are equally likely ordered resamples. Their means and counts:
| resample mean | 1 | 4/3 | 5/3 | 2 | 8/3 | 3 | 10/3 | 13/3 | 14/3 | 6 |
|---|---|---|---|---|---|---|---|---|---|---|
| count (of 27) | 1 | 3 | 3 | 1 | 3 | 6 | 3 | 3 | 3 | 1 |
- Step 1, the mean: the bootstrap distribution has mean 3, the sample mean.
- Step 2, the variance: the plug-in variance is , so the bootstrap variance is , and the bootstrap SE is .
- Step 3, compare: the usual SE is (with ). The ratio is : the narrowness bias.
- Step 4, who is in a resample: each point appears with probability .
- Step 5, the interval: the extremes 1 and 6 each have probability . So the 2.5% and 97.5% quantiles of the exact bootstrap distribution are 1 and 6, and the 95% percentile interval is the whole range of the data. With the bootstrap has almost nothing to work with.
B. An A/B test from plan to decision. A new ranking model should raise the conversion rate from the baseline to at least (absolute MDE ), with a two-sided test at and power 0.8 (, ).
Plan. , so , and . So
The rule of thumb gives . With 4,000 new users per arm per day, the test needs 6.4 days. Plan 7 days: a full week, and some time for novelty effects to fade. At 28,000 users per arm the power is 0.83. Fix this plan before the start.
Check the split. After 7 days, control has 28,112 users and treatment 27,888. Under 50/50, each: and . No sample-ratio mismatch.
Analyse once. Control converts 1,125 users (), treatment 1,255 ().
- Pooled rate: .
- Standard error under : .
- Statistic: , so .
- 95% interval (the unpooled SE is also ): percentage points, that is pp. Relative to the baseline, this is a lift of (about to ).
- Decision: launch the change, if the guardrail metrics (latency, errors, complaints) are fine.
With CUPED. Suppose the pre-period conversion of each user has correlation with the outcome. The variance factor is . The plan would then need users per arm (4.8 days). Or keep 7 days and detect an MDE times as large.
5. In code #
SciPy and simulation check the claims of sections 2–4. The CUPED simulation and the block bootstrap are in the notebook.
import numpy as np
from scipy import stats
# exact permutation test: app (7, 9, 12) vs no app (3, 5, 6)
a, b = np.array([7.0, 9.0, 12.0]), np.array([3.0, 5.0, 6.0])
def diff(x, y, axis):
return np.mean(x, axis=axis) - np.mean(y, axis=axis)
for alt in ("greater", "two-sided"):
r = stats.permutation_test((a, b), diff, permutation_type="independent",
alternative=alt, n_resamples=np.inf) # inf = all splits
print(alt, round(r.statistic, 3), round(r.pvalue, 3))
# greater 4.667 0.05
# two-sided 4.667 0.1
# coverage of 95% percentile bootstrap intervals, 1000 simulated data sets each
rng = np.random.default_rng(0)
cases = {"mean, Exp(1), n=30": (lambda: rng.exponential(size=30), np.mean, 1.0),
"median, Exp(1), n=60": (lambda: rng.exponential(size=60), np.median, np.log(2)),
"max, U(0,1), n=50": (lambda: rng.uniform(size=50), np.max, 1.0)}
for name, (draw, stat, truth) in cases.items():
hits = 0
for _ in range(1000):
ci = stats.bootstrap((draw(),), stat, n_resamples=999, method="percentile",
rng=rng).confidence_interval
hits += ci.low <= truth <= ci.high
print(name, hits / 1000)
# mean, Exp(1), n=30 0.928
# median, Exp(1), n=60 0.95
# max, U(0,1), n=50 0.0
# users per arm for 4.0% -> 4.5%, and an SRM check
z = stats.norm.ppf([0.975, 0.8])
p1, p2 = 0.040, 0.045
pb = (p1 + p2) / 2
n = (z[0] * np.sqrt(2 * pb * (1 - pb)) + z[1] * np.sqrt(p1 * (1 - p1) + p2 * (1 - p2))) ** 2 / (p2 - p1) ** 2
print(np.ceil(n), 16 * p1 * (1 - p1) / (p2 - p1) ** 2) # 25551.0 24576.0
print(stats.chisquare([50_600, 49_400])) # statistic 14.4, pvalue 0.00015
With 1,000 data sets, each coverage estimate has a standard error of about 0.007. The mean of skewed data at undercovers a little (0.928 instead of 0.95), the median is fine, and the maximum is never covered.
Peeking in an A/A test, and CUPED with a good and a bad covariate:
import numpy as np
# A/A test (H0 true) with k equally spaced looks; stop at the first |z| > 1.96
rng = np.random.default_rng(4)
for looks in (1, 2, 5, 10, 20, 100):
S = np.cumsum(rng.normal(size=(100_000, looks)), axis=1) # data arrive in equal batches
Z = S / np.sqrt(np.arange(1, looks + 1)) # z-statistic at each look
print(looks, np.mean(np.any(np.abs(Z) > 1.96, axis=1)))
# 1 0.04946 2 0.08159 5 0.14084 10 0.19272 20 0.24672 100 0.36978
# CUPED: 2000 simulated experiments, n users per arm, true effect tau
rng = np.random.default_rng(5)
reps, n, tau = 2000, 5000, 0.1
treat = np.arange(2 * n) >= n # users are i.i.d., so a fixed split is random
X = rng.gamma(2.0, 1.0, size=(reps, 2 * n)) # pre-experiment activity
Y = 1.0 + X + rng.normal(0.0, 1.44, size=X.shape) + tau * treat # corr(X, Y) = 0.70
Z = X + 0.3 * treat + rng.normal(0.0, 0.2, size=X.shape) # in-experiment: moved by the treatment
def estimate(Y, cov=None):
if cov is not None: # CUPED with one theta from the pooled data
c, y = cov - cov.mean(1, keepdims=True), Y - Y.mean(1, keepdims=True)
theta = (c * y).sum(1) / (c * c).sum(1)
Y = Y - theta[:, None] * c
return Y[:, treat].mean(1) - Y[:, ~treat].mean(1)
for name, d in (("plain", estimate(Y)), ("CUPED with X", estimate(Y, X)), ("CUPED with Z", estimate(Y, Z))):
print(f"{name:13s} mean = {d.mean():+.4f} SD = {d.std():.4f}")
# plain mean = +0.1016 SD = 0.0406
# CUPED with X mean = +0.1007 SD = 0.0292
# CUPED with Z mean = -0.1914 SD = 0.0291
With the pre-period covariate, the estimate stays centred on , and the standard deviation falls by the factor . The in-experiment covariate is just as precise, but it reports a negative effect: the correction removed the real effect.
6. Where it shows up #
- Evaluating ML models. A bootstrap interval over test examples for accuracy, AUC or a benchmark score; a paired bootstrap or a sign-flip permutation test for the difference of two models. Leaderboards of many models on one test set also need a multiple-testing view.
- Online experiments. New ranking, recommendation and search models are launched through A/B tests with a primary metric, guardrail metrics (latency, errors) and SRM checks. Rankers are also compared by interleaving the result lists of two models for the same user, which needs far fewer users.
- Bagging and random forests. Each tree is trained on a bootstrap resample; the 36.8% of points left out give the out-of-bag error.
- Weather and climate. Differences in forecast skill between two systems are paired by date and autocorrelated in time, so use a block bootstrap with blocks of a week or more.
- Science. Randomised field trials and lab experiments use permutation tests for small studies, and covariate adjustment (ANCOVA, the CUPED idea) with baseline measurements.
7. Common mistakes #
- instead of . It can report and can reject too often.
- Too few permutations for a small threshold. With the smallest p-value is 0.001, so a Bonferroni threshold of can never be reached.
- Permuting when does not give exchangeability: unequal variances, paired data (flip signs instead), time series (permute blocks).
- Resampling the wrong unit: page views instead of users, tokens instead of documents, time steps instead of blocks.
- The percentile bootstrap for a maximum, a minimum or an extreme quantile, or for the mean of heavy-tailed data.
- Thinking the bootstrap creates information. It estimates the sampling distribution from the one sample you have; with it cannot do much.
- Peeking: stopping at the first , extending a test "a few more days" because , or stopping before a full week.
- Ignoring an SRM because the imbalance looks small: 1.2% of 100,000 users is far outside chance.
- CUPED with a covariate measured after the start, or with a different in each arm.
- Mixing units in the sample size: a relative MDE with an absolute , or users per arm vs users in total.
8. Check yourself #
- Group A has 4 values and group B has 4 values, all 8 distinct, and A holds the 4 largest. What are the exact
one-sided and two-sided permutation p-values for the difference in means?
Answer
There are equally likely splits. Only the observed one reaches , so the one-sided . The mirror split (B holds the 4 largest) gives , so the two-sided p-value is . - A Monte Carlo permutation test with finds 23 permutations with . Give the p-value
and its Monte Carlo standard error. Why not ?
Answer
, with Monte Carlo SE . The observed labelling is itself one of the equally likely permutations. Counting it gives a p-value with for every , and never . - Why does the percentile bootstrap fail for in , and which interval should you use?
Answer
Every resample maximum is at most the sample maximum , and , so the interval never covers . The bootstrap distribution also puts mass on itself, while the true distribution of is continuous. Use the pivot: , so , and the interval has coverage exactly . - Baseline conversion 20%, MDE 2 percentage points (to 22%), two-sided , power 0.8. How many users per
arm? Compare with the rule of thumb.
Answer
, and . So per arm. Rule of thumb: . - A pre-period covariate has correlation 0.8 with the outcome. By what factors do CUPED's variance, standard error and
required sample size change? Why is the estimate still unbiased?
Answer
The variance and the sample size both scale by (2.8 times fewer users); the standard error by . It is unbiased because the covariate was measured before randomisation, so and the correction term has mean zero. - Users are randomised, but the metric is the click rate per page view; each user has about 5 views, with intra-user
correlation 0.3. What goes wrong with the naive analysis?
Answer
It treats the views as independent. The true variance is larger by , so the naive standard error is too small by the factor , and the p-values are too small. Analyse per user, or use the delta method or a user-level bootstrap.
Why is a permutation test exact under ?
Under the pooled values are exchangeable, so the observed labelling is one uniformly random labelling among all of them; its statistic is equally likely to have any rank.
Give the Monte Carlo permutation p-value with shuffles and shuffles at least as extreme as observed.
.
State the bootstrap principle in one sentence.
Approximate the sampling distribution of under by the distribution of computed on resamples drawn with replacement from the empirical distribution .
What share of the distinct data points does one bootstrap resample contain, for large ?
.
How is the 95% percentile bootstrap interval computed?
The 2.5% and 97.5% quantiles of the bootstrap replicates .
Why does the percentile bootstrap fail for the maximum of data?
Every resample maximum is at most the sample maximum, which is below , so the interval never contains .
What is the rule of thumb for the users per arm of an A/B test (, power 0.8)?
, with the per-user variance and the MDE.
What is peeking, and what does it do to an A/B test?
Looking at the p-value repeatedly and stopping at the first ; it inflates the type I error (about 0.25 with 20 looks instead of 0.05).
By what factor does CUPED reduce the variance, and with which ?
By , with , where .
Why must the CUPED covariate be measured before the experiment?
Then the treatment cannot change it, so and the correction has mean zero; otherwise the estimate is biased.
9. Interview angle #
See also the interview track, A/B testing.
- "Design an A/B test for a new ranking model." — The primary metric and guardrails, the unit of randomisation, the MDE and the sample size, the duration in whole weeks, SRM and A/A checks, one pre-registered analysis, no peeking.
- "The test is significant after 3 days. The PM wants to launch." — Peeking inflates the type I error; the planned power assumed the full duration; novelty effects fade. Wait for the planned end, or use a sequential method that was planned for early stopping.
- "Explain the bootstrap. When does it fail?" — Resample with replacement from the data to mimic sampling from . It fails for extremes, heavy tails, dependent data and very small .
- "Permutation test or t-test?" — The permutation test is exact under exchangeability and needs no normality; the t-test is cheaper and gives an interval directly. With large they agree.
- "How do you reduce variance in an experiment?" — CUPED or regression adjustment, stratification, analysing only the triggered users, a less noisy metric (conversion instead of revenue, capped revenue).
- "Model B beats model A by 0.4% accuracy on 10,000 test examples. Is it real?" — Paired bootstrap of the per-example differences for an interval, or McNemar's test; also check the spread over training seeds.
10. Practice #
The notebook for this lesson has five exercises, each with a check cell and a folded solution:
- Write
permutation_test(difference in means, ) and check it on the exact six-student example. - Write
bootstrap_percentile_ci, compare it withscipy.stats.bootstrap(method="percentile"), and measure its coverage for the mean, the median and the maximum. - Write a moving block bootstrap for the mean of an AR(1) series and compare it with the i.i.d. bootstrap and the true standard error.
- Write
ab_sample_sizeandtwo_proportion_power, and check the plan of section 4B by simulating whole A/B tests. - Write
cupedand check on simulated A/B data that the variance falls by about and that the estimate stays unbiased.
11. Watch and read #
- Tim Hesterberg, What Teachers Should Know about the Bootstrap (2015): the permutation test, the bootstrap idea, how many resamples, and the accuracy of bootstrap intervals.
- Ron Kohavi, Roger Longbotham, Dan Sommerfield and Randal Henne, Controlled experiments on the web: survey and practical guide (2009): sample size, the rule, A/A tests, and limitations.
- Alex Deng, Ya Xu, Ron Kohavi and Toby Walker, Improving the sensitivity of online controlled experiments by utilizing pre-experiment data (CUPED, 2013).
- Evan Miller, "How not to run an A/B test": why peeking breaks the significance level.
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.