Evaluation & Analytics — Foundations

The Statistics of Evaluation
Real Signal, or Sampling Noise?

Your model scored 84.3%; the baseline scored 83.9%. Your teammate says ship it. Is that a real improvement, or is it a coin flip dressed up as a decision? This lesson is the statistical machinery that separates signal from the jitter of a finite test set — standard errors, confidence intervals, the bootstrap, paired tests, power, and the multiplicity traps that manufacture fake wins.

Prerequisites: ML Maths (means, variance, a little probability). No prior statistics assumed.
11
Chapters
11
Live Simulations
10+
Interview Q&A

Chapter 0: Why — Is 84.3% vs 83.9% Real?

You just spent two weeks fine-tuning a question-answering model. You run it against your 1,000-question benchmark: 84.3%. The old production model scored 83.9% on the same kind of benchmark. A colleague glances at the dashboard, sees the bigger number, and says the two words that start most bad launches: "ship it."

Everyone's instinct is the same — higher number, better model. The whole point of this chapter is to break that instinct, gently and permanently. Because that 0.4-point "win" might be a real improvement, or it might be nothing at all: the statistical equivalent of one basketball team beating another by a single free throw and declaring itself the better franchise.

Here is the crack in the reasoning. Your test set is a sample — a finite handful drawn from an effectively infinite universe of possible questions of the same kind. You did not test the model on every question a user could ever ask; you tested it on the 1,000 you happened to collect. If you had drawn a different 1,000 questions of the same difficulty, both scores would have come out different. The only question that matters is: would they have moved by more than 0.4 points?

Picture ten parallel universes. In each, your team collected its own random 1,000-question test set from the same source. In each universe you run both models and record the two accuracies. In our universe A won by 0.4. But in universe 3 maybe B won by 0.6; in universe 7 they tied; in universe 9 A won by 1.5. The 0.4-point gap flips sign in several universes — and that, precisely, is what people mean when they wave their hands and say "noise."

Let us name two ideas informally now, and formalize them across the lesson. Sampling error is the amount a score jumps around simply because the test set is finite — the width of the spread across those parallel universes. And the comparison that actually decides anything is delta versus noise: a gap only means something when it is large compared to that jitter.

Here is a back-of-envelope number to hold in your head (we derive it properly in Chapter 1). For a single accuracy near 84% measured on N = 1,000 examples, the jitter is about ±1.2 points. That is three times larger than the 0.4-point gap we got excited about. The gap is a pebble; the noise is a boulder rolling past it.

This is not a beginner's mistake that professionals outgrow. Teams have reverted genuinely-better models and shipped genuinely-worse ones because nobody asked "is this delta bigger than the noise?" It is common enough at the frontier that in November 2024 a researcher at Anthropic published a whole methodology note, Adding Error Bars to Evals, precisely because top labs kept making this exact error on their own benchmarks.

Misconception: You might think a higher benchmark number always means a better model — after all, the eval is deterministic code; run it twice and you get the same score. True! But the randomness does not live in the code. It lives in which examples ended up in the test set. Rerun the same comparison on a freshly resampled test set and a 0.4-point "win" flips sign almost half the time. The model is fixed; the exam it took is the dice.

Play with the simulation below before reading on. Both dots are drawn from a fixed truth: model A has 84.3% real skill, baseline B has 83.9%. Each "Redraw test set" resamples a fresh N-question exam and replots both observed accuracies, keeping a fading trail of past draws. Watch the two clouds — at small N they overlap almost completely, and the worse model wins constantly. Drag N up and watch the clouds slowly pull apart.

The Jittering Score

Two truths: A = 84.3% and B = 83.9%. Each redraw resamples an N-example test set and plots both observed accuracies as dots on the score axis, leaving ghost trails. The tally counts how often the worse model (B) won. At N=100 the clouds are enormous; at N=10,000 they separate.

Test-set size N 1000

Now let us put arithmetic under what your eyes just saw. Treat the two scores as coming from independent test sets of the same kind, so the question is purely: is the 0.4-point gap distinguishable from noise?

Worked example — is 0.4 points bigger than the jitter?

Setup. Model A: 843/1000 = 84.3%. Baseline B: 839/1000 = 83.9%. Different random test sets of the same kind, so treat them as independent. Is the gap distinguishable from noise?

Step 1 — jitter of A's score. The formula (derived next chapter) is SE = √(p(1−p)/N).
SEA = √(0.843 × 0.157 / 1000) = √(0.132351 / 1000) = √(0.000132351) = 0.011504 — about 1.15 points.

Step 2 — jitter of B's score.
SEB = √(0.839 × 0.161 / 1000) = √(0.000135079) = 0.011622 — about 1.16 points.

Step 3 — jitter of the DIFFERENCE. Independent variances add, so the SE of A−B is the square root of the sum of the two variances (not the sum of the SEs):
SEdiff = √(SEA² + SEB²) = √(0.000132351 + 0.000135079) = √(0.000267430) = 0.016353 — about 1.64 points.

Step 4 — compare the gap to the jitter. A standardized score, or z, is "how many noise units is the gap?":
z = (0.843 − 0.839) / 0.016353 = 0.004 / 0.016353 = 0.245. The gap is a quarter of one noise unit.

Step 5 — the p-value. The two-sided p-value is the probability of seeing a gap at least this large if the models were truly equal:
p = 2 × P(Z > 0.245) = 0.807. If A and B were exactly equally good, you'd see a gap this big 81% of the time. This result says nothing.

Let us reproduce that in code — first the "parallel universes" by brute force, then the one-line formula, and confirm they agree.

python — the parallel universes, by simulation
import numpy as np
rng = np.random.default_rng(0)
N = 1000
draws = 10000                          # 10,000 parallel universes
# each universe: a fresh N-question exam for each model
accA = rng.binomial(N, 0.843, draws) / N
accB = rng.binomial(N, 0.839, draws) / N
print("baseline B wins:", np.mean(accB > accA).round(3))
# -> ~0.40 : the WORSE model tops the chart ~40% of the time
python — the one-line formula (same answer)
import numpy as np
from scipy import stats
se_diff = np.sqrt(0.843*0.157/1000 + 0.839*0.161/1000)
p = 2 * stats.norm.sf(0.004 / se_diff)
print(round(se_diff,6), round(p,3))   # 0.016353 0.807

Notice the two agree: the simulation's "40% of the time the worse model wins" and the formula's "p = 0.807 that a gap this big is pure luck" are two views of the same fact. The gap is inside the noise.

Here is the arc of the lesson. First we quantify the jitter (the standard error, Ch1). Then we put honest brackets on a single score (confidence intervals, Ch2), and get brackets for any metric — F1, BLEU, win rate — with no formula (the bootstrap, Ch3). We learn to compare two models properly (paired tests, Ch4), decide how big a test set we even need (power, Ch5), and survive the temptation of trying twenty configs (multiplicity, Ch6). Chapter 7 collides all of it in a live leaderboard simulator that lies in real time, and Chapter 8 splits the noise itself into its sources.

Interview lens. A PM shows you a dashboard where the new model beats production by 0.5% on your offline eval and asks you to sign off on the launch. What is the FIRST question you ask, and what do you scribble on the whiteboard? The first question is "how many examples is that eval, and did both models run on the same test set?" On the board, write SE = √(p(1−p)/N), plug in the scale, and compare the 0.5% delta against roughly 2×SEdiff. Strong candidates immediately add: if both ran on the same test set, a paired analysis (Ch4) has far more power than treating the scores as independent, and signing off should also weigh seed-to-seed variance of the new model (Ch8), not just one training run.
Scope & cross-links. This lesson owns the statistics. What metric to compute — precision, recall, F1, AUC — is Metric Design's job. Online testing with real users is Experiment Design's job. How to draw the error bars honestly is Plots That Tell the Truth. We take a metric and a test set as given, and ask only: how much of this number is real?
Model A beats model B by 0.4 points on a 1,000-example benchmark, where each score jitters by about ±1.2 points across resampled test sets. What can you conclude?

To decide anything, we first need a number for that jitter. Where does ±1.2 points come from — and why does quadrupling the test set only halve it? That is Chapter 1.

Chapter 1: The Eval Score IS a Random Variable

We watched the score jitter with our own eyes. Now we put a number on the jitter — and derive, from scratch, why quadrupling your test set only halves it. This one formula is the workhorse of the entire lesson.

Reframe the eval as a sequence of coin flips. On each test example the model is either right (1) or wrong (0). The accuracy is simply the average of those N zero-or-one outcomes. That single reframing is the whole trick: accuracy is a sum of random things, and sums of random things have completely understood behavior.

Before we go further, separate two quantities people constantly confuse. The estimand is p, the model's TRUE accuracy over the whole distribution of possible questions — a number you can never observe. The estimate is p-hat (written p̂), the accuracy you actually measured on your N questions — the number on your dashboard. The estimate wobbles around the estimand. Statistics is the discipline of saying how much.

Derive the variance of a single flip from scratch. Let X be 1 with probability p and 0 otherwise — a Bernoulli variable. Its mean is E[X] = p. Its variance is E[X²] − (E[X])². Because X is 0 or 1, X² = X, so E[X²] = p. Therefore the variance is p − p² = p(1−p).

Plug in a real number to feel it: at p = 0.85, the per-flip variance is 0.85 − 0.7225 = 0.1275. Notice this quantity peaks at p = 0.5 (a 50/50 coin is maximally uncertain) and shrinks toward 0 as p approaches 0 or 1 (a model that is right 99% of the time barely varies). Uncertainty is largest exactly when the model is most undecided.

Now average N independent flips. Two facts do all the work. First, variances of independent variables add: the variance of the sum of N flips is N × p(1−p). Second, dividing a quantity by a constant c divides its variance by c². We divide the sum by N to get the average, so we divide the variance by N². Net: Var(p̂) = N p(1−p) / N² = p(1−p)/N.

The standard error — the typical distance p-hat lands from p — is the square root of that variance:

SE = √( p(1−p) / N )

Stare at the N under the square root, because it governs the economics of every eval you will ever run. Because N sits inside a square root, precision improves only as 1/√N. To halve your error bar you must quadruple your test set. To get 10× the precision you need 100× the examples — and 100× the labeling or inference cost. This single fact is why eval precision is genuinely expensive, and why Chapter 8's compute budgeting matters so much.

Why is the sampling distribution shaped like a bell? The Central Limit Theorem says that for a reasonable N, the average of many independent things is approximately normal — a bell curve centered at the true p with width SE. That is why the "±2 SE covers about 95%" rule works. Hold onto the word approximately: in Chapter 2 we will watch this approximation break when p is extreme or N is small.

Plug in real scales to build muscle memory. At 85% accuracy: SE is about ±1.6 points at N=500, ±1.1 at N=1000, ±0.5 at N=5000. Look at that first number and reread the last three papers you saw claim an "improvement." Many of those deltas are smaller than the ±1.6 that a 500-example benchmark simply cannot resolve.

One caveat that sets up several later chapters: this formula assumes the examples are independent draws. If your benchmark has ten questions all about the same document, those ten share a fate — get the document wrong and you likely miss all ten. That clustering inflates the true variance beyond p(1−p)/N. Anthropic's November 2024 error-bars note makes exactly this correction with clustered standard errors; the mental model is that your effective N is smaller than your nominal N.

Sampling Distribution Builder

Left: a stream of 0/1 outcomes filling one test set (green=correct, red=wrong). Right: a histogram of observed accuracies across many simulated test sets, with the theoretical bell √(p(1−p)/N) overlaid and the ±1 SE / ±2 SE bands shaded. Hit "2× N" and watch the histogram narrow by only √2 — the 1/√N law made tactile.

True accuracy p0.85
Test-set size N500
Worked example — the honest '85%'

Setup. Your model got 425/500 = 85% on a 500-example benchmark. Compute the standard error by hand, then see what N=2000 would buy.

Step 1 — per-example variance. p(1−p) = 0.85 × 0.15 = 0.1275.

Step 2 — variance of the average. Divide by N: 0.1275 / 500 = 0.000255.

Step 3 — standard error. SE = √(0.000255) = 0.015969 — about ±1.60 points.

Step 4 — rough 95% range. p̂ ± 1.96 SE = 0.85 ± 1.96 × 0.015969 = 0.85 ± 0.0313 → roughly 81.9% to 88.1%. Your "85%" is honestly "85 ± 3".

Step 5 — quadruple to N=2000. SE = √(0.1275/2000) = √(0.00006375) = 0.007984 — exactly HALF of 0.015969. Four times the data, twice the precision. The 1/√N law in the flesh: 0.015969 / 0.007984 = 2.0.

python — from scratch, every intermediate
import numpy as np
outcomes = np.array([1]*425 + [0]*75)   # 425 right, 75 wrong
N = outcomes.size
phat = outcomes.mean()                    # 0.85
var1 = phat * (1 - phat)               # 0.1275  per-flip variance
var_mean = var1 / N                     # 0.000255
se = var_mean ** 0.5                   # 0.015969
print(phat, var1, round(se,6), round(1.96*se,4))
# Monte-Carlo check: empirical std of accuracies should match the formula
rng = np.random.default_rng(0)
sims = rng.binomial(N, phat, 100000) / N
print("empirical SE:", round(sims.std(),6))  # ~0.0160
python — the library one-liner
from scipy import stats
print(stats.sem(outcomes))          # standard error of the mean
# or the proportion shortcut: math.sqrt(p*(1-p)/N)
Misconception: You might think a bigger model or a cleverer prompt shrinks the error bar. It does not. SE depends only on p and N — the noise floor is a property of your TEST SET, not your model. A 500-example benchmark cannot certify a sub-1.5-point improvement no matter how carefully you run it. If you need tighter error bars, you need more examples, not a better model.
Interview lens. "Derive the standard error of an accuracy from first principles, and say when the formula understates the true uncertainty." Walk the chain: correctness is Bernoulli(p) with variance p(1−p); the mean of N independent outcomes has variance p(1−p)/N; SE is its square root. It understates uncertainty when independence fails — clustered examples (multiple questions per document, per user, per template) share errors, so the effective N is smaller than the nominal N; the fix is clustered standard errors. It also understates total uncertainty because it captures only test-set sampling — training-seed variance and decode-sampling variance (Ch8) are separate additive terms a single dashboard number silently ignores.
Your benchmark has N=500 and SE ≈ 1.6 points. Leadership wants error bars of ±0.4 points (SE ≈ 0.4). How many examples do you need?

We can now quantify the jitter of a single score. The next step is to wrap it into the thing every eval report should show and almost none do: an honest interval.

Chapter 2: Confidence Intervals — Honest Brackets

We have a point score and a noise scale. Combine them and you get the single most under-used artifact in evaluation: the interval. A number without an interval is a claim without evidence attached; a number with one tells the reader how far to trust it.

Define a confidence interval (CI) by its GUARANTEE, not its formula. A 95% CI is a procedure: if you reran the whole experiment — drew a fresh test set — 100 times and built the interval each time, about 95 of those 100 intervals would contain the true p. The interval is the random thing; the truth is fixed. This is subtle and worth slowing down for.

The standard recipe, called the Wald or normal-approximation interval, is p̂ ± 1.96 × √(p̂(1−p̂)/N). Where does 1.96 come from? It is the point on the bell curve that leaves 2.5% of the area in each tail, so 95% sits in the middle. In your head, "2" is a fine shortcut for 1.96.

Now the reading that separates careful practitioners from casual ones. The RIGHT reading: "the procedure that produced this interval captures the truth 95% of the time." The WRONG reading: "there is a 95% probability the true accuracy is inside THIS interval." The distinction sounds pedantic — until you notice that a fixed interval either contains the fixed truth or it does not; there is no probability left once both are fixed. The 95% describes the method, across many hypothetical reruns.

Here is where the normal approximation quietly breaks: small N, or p near 0 or 1 — which is exactly the regime of the most interesting modern evals. A hard agentic benchmark where a model solves 17 of 20 tasks. A safety eval where the violation rate hovers near zero. In these corners the Wald recipe produces nonsense. Concretely, the Wald interval for 17/20 pokes above 100% accuracy — a bracket that includes impossible values.

The Wilson interval fixes this by asking a smarter question. Instead of approximating a bell curve around p-hat, it asks: "which true values of p would make my observation unsurprising?" — it inverts the test. The result has two beautiful properties. The center shrinks toward 0.5 (a built-in skepticism about extreme small-sample scores), and the width accounts for uncertainty in the variance itself. It stays inside [0, 1] by construction.

The rule of thumb for practice: Wald is fine when both N p̂ and N(1−p̂) exceed about 10; below that, or any time the interval nears 0 or 1, reach for Wilson. If you need guaranteed conservative coverage — a regulated safety claim — use Clopper-Pearson, which is wider but never under-covers. Wilson is the default in statsmodels and most eval libraries precisely because it behaves well across the whole range.

This is not academic hygiene — the field moved. OpenAI's SimpleQA-style and swe-bench-style reports and Anthropic's error-bars methodology (Nov 2024) publish CIs; when HuggingFace relaunched the Open LLM Leaderboard v2 in June 2024 it re-ran and normalized scores explicitly because small-N subscores were being over-read as rankings.

The Coverage Game

100 repeated experiments. Each row draws a fresh test set from a hidden true p (the gold line), builds a CI, and paints it green if it captures the line, red if it misses. Set p=0.95, n=20 and toggle Wald → Wilson: Wald misses far more than 5% of the time (and spills past 100%) while Wilson holds near 95%.

True p0.95
n per experiment20
Worked example — 17/20, where Wald breaks and Wilson rescues

Setup. Your agent solved 17 of 20 hard tasks (p̂ = 0.85). Build the 95% Wald interval, watch it break, then build Wilson by hand.

Step 1 — Wald. SE = √(0.85×0.15/20) = √(0.006375) = 0.079844. Interval: 0.85 ± 1.96×0.079844 = 0.85 ± 0.156494 → [0.6935, 1.0065]. The upper end is 100.6% accuracy — impossible. The approximation has failed.

Step 2 — Wilson ingredients. z = 1.96, so z² = 3.8416; n = 20; z²/(2n) = 3.8416/40 = 0.096040; z²/n = 0.192080; z²/(4n²) = 3.8416/1600 = 0.002401.

Step 3 — Wilson center. (p̂ + z²/(2n)) / (1 + z²/n) = (0.85 + 0.096040) / 1.192080 = 0.946040 / 1.192080 = 0.793604. It moved from 0.85 toward 0.5 — built-in small-sample skepticism.

Step 4 — Wilson half-width. numerator = z × √(p̂(1−p̂)/n + z²/(4n²)) = 1.96 × √(0.006375 + 0.002401) = 1.96 × √(0.008776) = 1.96 × 0.093680 = 0.183613. Divide by the same 1.192080: half-width = 0.154028.

Step 5 — Wilson interval. 0.793604 ± 0.154028 → [0.6396, 0.9476]. Sane (inside [0,1]), honest (17/20 is compatible with true skill anywhere from 64% to 95%), and pointedly NOT centered on 85%.

python — Wilson from scratch, every intermediate
import math
def wilson(k, n, z=1.96):
    phat = k / n
    zz = z * z
    center = (phat + zz / (2*n)) / (1 + zz/n)
    half   = z * math.sqrt(phat*(1-phat)/n + zz/(4*n*n)) / (1 + zz/n)
    return center - half, center + half
print(wilson(17, 20))     # (0.6396, 0.9476)
# and watch Wald misbehave:
phat, n = 0.85, 20
se = math.sqrt(phat*(1-phat)/n)
print(phat - 1.96*se, phat + 1.96*se)   # 0.6935 1.0065  (>1!)
python — the library one-liner
from statsmodels.stats.proportion import proportion_confint
print(proportion_confint(17, 20, method='wilson'))
# -> (0.6396..., 0.9476...)  same answer
Misconception: You might think "95% CI" means "there is a 95% chance the true accuracy lies in this interval." But the true accuracy is a fixed number, not a random one — once your interval is printed, it either contains p or it doesn't. The 95% is a property of the PROCEDURE across repeated experiments: run the coverage game and count the green rows. And if your interval crosses 100% (or collapses to zero width at 0 violations), the procedure is simply wrong — switch to Wilson.
Interview lens. "Your team reports agent success as 17/20 = 85% in a launch doc. Rewrite that line, and justify the extra caution to a skeptical PM." Rewrite as "85% (95% Wilson CI: 64%–95%, n=20)." Justify with the coverage argument: with 20 samples, true skill anywhere from 64% to 95% would plausibly produce this observation, so decisions premised on "85%" (pricing, SLAs, beating an 80% competitor) are premised on noise. A strong answer also flags the Wald pathologies (crossing 100% here; collapsing to zero width at 0/N in safety evals), names Wilson or Clopper-Pearson as the fixes, and proposes the real remedy: this benchmark needs more tasks before it can support the claim.
Your safety eval finds 0 violations in 200 trials. Why is the Wald interval 0 ± 0 dangerously wrong, and what does Wilson do instead?

Wilson handles proportions beautifully. But your dashboard also shows F1, BLEU, and judge win-rates — metrics with no clean closed-form SE. For those we need a tool that needs no formula at all: the bootstrap.

Chapter 3: The Bootstrap — Error Bars for Any Metric

The formula SE = √(p(1−p)/N) works for accuracy because accuracy is an average of independent 0/1 outcomes. But your dashboard also shows F1, BLEU, and judge win-rates. Where do their error bars come from? Chapter 1's derivation gives no answer — and that is the wall this chapter climbs over.

Why do those metrics resist the formula? F1 is a ratio of sums (2·precision·recall over their sum) — it does not decompose into a per-example average. BLEU is a geometric mean of clipped n-gram precisions multiplied by a brevity penalty, computed over the whole corpus at once. Neither is "the mean of N independent things," so no closed-form standard error exists in general.

The bootstrap idea, in one sentence: we cannot rerun reality to get fresh test sets, but we can fake fresh test sets by resampling our one test set with replacement. Each fake set is N draws from the empirical distribution — our best available stand-in for the true distribution — so the spread of the metric across fake sets mimics its spread across real reruns of the world.

Make "with replacement" concrete. Each resample draws N examples from the original N, but after drawing an example we put it back, so some examples get picked twice or three times and others get picked zero times. On average about 36.8% of the original examples are left out of any given resample — that number is (1−1/N)N, which converges to 1/e as N grows. Every fake universe is a slightly reweighted version of your data.

The recipe, mechanically:

1. Real metric
Compute your metric once on the actual test set.
2. Resample ×B
For b = 1..B (B ≈ 1,000–10,000): draw N examples with replacement, recompute the metric.
3. Read the spread
The B fake metrics ARE your sampling distribution.
4. Percentile CI
Take the 2.5th and 97.5th percentiles → a 95% interval.

Why does this work, honestly rather than by hand-waving? The fluctuation of the metric across bootstrap resamples mimics its fluctuation across true resamples of the world, because the empirical distribution converges to the true one as data accrues. This is called the plug-in principle — we plug the empirical distribution in where the (unknown) true distribution belongs. It is a principled approximation, not magic.

Be clear about what the bootstrap cannot do. It never invents examples you did not collect. If your 500-example set contains no adversarial phrasings, no bootstrap interval will reflect uncertainty about adversarial phrasings. The bootstrap quantifies sampling noise within your distribution; it is silent about distribution shift. It reveals how little you know from the data you have — not what you failed to collect.

Resample the RIGHT unit. For a QA benchmark, resample questions. For a dialogue benchmark, resample whole conversations, not individual turns — turns within a conversation share user, topic, and context, so they are correlated. For a multi-document eval, resample documents. Resampling below the independence unit pretends you have more independent data than you do, and gives intervals that are too narrow — the same clustering trap from Chapter 1.

You will meet this everywhere in the wild: HELM publishes bootstrapped CIs; machine-translation shared tasks have used paired bootstrap resampling since Koehn (2004); lm-eval-harness exposes a bootstrap standard error for aggregate metrics. And the paired version of the bootstrap returns as our best model-comparison tool in the very next chapter.

The Bootstrap Machine

Top: the real test set as tiles (correct / wrong). Each crank draws N tiles with replacement — duplicates stack, omitted tiles gray out — computes the resample's accuracy, and drops a dot into the histogram below. The 2.5% and 97.5% markers slide as the histogram fills; the final CI is a bracket.

Resamples B1000
Worked example — bootstrap a 10-example set by hand

Setup. A toy test set of n=10 with correctness [1,1,0,1,1,1,0,1,1,0] — observed accuracy 7/10 = 0.70. First three resamples fully by hand, then B=10,000 in code.

Step 0 — real metric. (1+1+0+1+1+1+0+1+1+0)/10 = 7/10 = 0.70.

Step 1 — resample 1. draw indices with replacement [3,0,7,7,2,9,4,1,5,8] → values [1,1,1,1,0,0,1,1,1,1] → sum 8 → mean 0.80. Index 7 appeared twice; index 6 was never drawn.

Step 2 — resample 2. indices [6,6,1,3,0,2,9,5,4,8] → values [0,0,1,1,1,0,0,1,1,1] → sum 6 → mean 0.60. Index 6 (a wrong answer) appeared twice — a pessimistic universe.

Step 3 — resample 3. indices [0,1,2,3,4,5,6,7,8,9] (every example once, rare but legal) → mean 0.70, the original.

Step 4 — the fake universes. Three gave 0.80, 0.60, 0.70. Repeat 10,000 times (seeded): the 2.5th percentile is 0.40, the 97.5th is 1.00.

Step 5 — read it. The 95% percentile bootstrap CI is [0.40, 1.00]. Ten examples tell you almost nothing — which the bootstrap reports honestly, echoing Chapter 2's small-N warning. Sanity check: bootstrap std ≈ 0.144 matches the formula √(0.7×0.3/10) = 0.145.

python — a 12-line bootstrap, any metric
import numpy as np, math
data = np.array([1,1,0,1,1,1,0,1,1,0])
print(data.mean())                       # 0.70
for idx in ([3,0,7,7,2,9,4,1,5,8],
            [6,6,1,3,0,2,9,5,4,8],
            [0,1,2,3,4,5,6,7,8,9]):
    print(data[np.array(idx)].mean(), end=' ')  # 0.8 0.6 0.7
print()
rng = np.random.default_rng(42)
boots = np.array([rng.choice(data, size=10, replace=True).mean()
                  for _ in range(10000)])
print(np.percentile(boots,2.5), np.percentile(boots,97.5),
      round(boots.std(),3), round(math.sqrt(0.7*0.3/10),3))
# -> 0.4 1.0 0.144 0.145
python — the library one-liner
from scipy.stats import bootstrap
res = bootstrap((data,), np.mean, n_resamples=10000,
                confidence_level=0.95, method='percentile')
print(res.confidence_interval)      # ~ (0.4, 1.0)
Misconception: You might think the bootstrap "creates information from nothing" — resampling the same data thousands of times to conjure certainty. It is the opposite: it REVEALS how little information you have. Ten examples bootstrap to a [0.40, 1.00] interval. And there is a hard limit even it cannot cross: it quantifies sampling noise within your test distribution, never uncertainty about examples you never collected. Distribution shift is invisible to the bootstrap.
Interview lens. "Your team reports BLEU 31.2 for the new model vs 30.8 for the old one and wants error bars. Walk me through it, and one subtle way to get it wrong." BLEU is corpus-level, so there is no per-sentence decomposition and no closed-form SE — bootstrap the corpus: resample sentences (or documents, if sentences within a document correlate) with replacement B=1000+ times, recompute corpus BLEU each time, take percentile bounds. The classic error is computing per-sentence BLEU and averaging — that is a different, worse-behaved metric. A staff-level answer adds the paired refinement: resample the SAME sentence indices for both systems and bootstrap the DELTA — the standard MT significance test since Koehn (2004), previewing Chapter 4.
Your dialogue benchmark has 200 conversations of ~10 turns each, scored per turn. To bootstrap a CI on average turn quality, what should you resample?

The bootstrap gives one model's error bar for any metric. But the question that actually decides launches is a comparison — and there is a magic trick hiding in the fact that both models took the same exam.

Chapter 4: Comparing Two Models — Paired Beats Unpaired

Here is a magic trick. The same 2-point gap, the same 1,000 examples — one test says p=0.22, the other says p=0.023. Nothing changed but the method. The difference is simply noticing that both models took the same exam. This chapter is that trick, fully explained.

First, hypothesis testing honestly, in one beat. The null hypothesis is "the two models are equally accurate." The p-value is the probability of seeing a gap at least this large IF the null were true. It is NOT the probability the null is true, and 0.05 is a social convention, not a law of physics.

The pairing insight. When models A and B answer the SAME questions, most questions come out identical — both right on the easy ones, both wrong on the hard ones. That shared component is pure noise for the comparison: it tells you the questions were easy or hard, not which model is better. Pairing subtracts it out. An unpaired test pays for that shared difficulty twice, once in each model's variance.

Build the 2×2 disagreement table. Label each question by how the two models did: both-correct (a), A-only-correct (b), B-only-correct (c), both-wrong (d). Here is the crucial fact — the entire comparison lives in b versus c, the discordant cells. The a and d cells, however enormous, contain zero information about which model is better.

B correctB wrong
A correcta = both rightb = A only
A wrongc = B onlyd = both wrong

McNemar's test from scratch. Under the null, each discordant example is a fair coin — equally likely to favor A or B — so b should be about (b+c)/2. The statistic (|b−c|−1)² / (b+c) measures how far the observed split is from fair, and it follows a chi-square (χ²) distribution with 1 degree of freedom. The "−1" is a continuity correction — a small adjustment for using a smooth curve to approximate discrete counts. When b+c is small, skip the approximation entirely and use the exact binomial tail.

Now run the magic trick with numbers. 1,000 examples: a=810, b=45, c=25, d=120. Marginals: A = 85.5%, B = 83.5%. The unpaired two-proportion z-test gives z=1.24, p=0.216 — "not significant." McNemar on the very same data: χ²=5.16, p=0.023 — significant. Same data, same 2-point gap. The paired test simply refuses to be distracted by the 930 agreements.

The paired bootstrap generalizes this beyond accuracy. Resample QUESTION INDICES (with replacement), recompute BOTH models' metrics on the same resampled indices, and record the delta. The percentile CI of those deltas — or the fraction of resamples where the delta flips sign — is your answer. It works for F1, BLEU, win rate, anything. The key is resampling the same indices for both systems, so the shared difficulty cancels.

Practical guidance. Same test set → always pair: McNemar for accuracy on 0/1 outcomes, paired bootstrap for everything else. Different test sets → you are stuck with unpaired, and you need far more data. And always report the CI on the DELTA, not just a p-value star, so readers see effect size and uncertainty in the same glance.

Field note: Anthropic's Adding Error Bars to Evals (Nov 2024) explicitly recommends paired analyses for model comparisons on shared eval sets, and MT/LLM tooling (sacrebleu, lm-eval-harness) implements paired bootstrap for exactly this reason. Judge-based win rates get their own paired treatment in GenAI Eval.

The Disagreement Table

A grid of test questions colored by outcome: both right, both wrong, A-only, B-only. Slide the disagreement counts and watch the unpaired z-test p-value and the McNemar p-value diverge. The agreement dots dim, because they carry no comparison information. Hit "Load worked example" for 810/45/25/120.

b: A-only correct45
c: B-only correct25
a: both correct810
Worked example — same data, p=0.216 vs p=0.023

Setup. Models A and B on the same 1,000 questions: both correct on 810, A-only on 45, B-only on 25, both wrong on 120. Is A really better?

Step 1 — marginal accuracies. A = (810+45)/1000 = 85.5%; B = (810+25)/1000 = 83.5%. Gap = 2.0 points.

Step 2 — the WRONG (unpaired) test. SEdiff = √(0.855×0.145/1000 + 0.835×0.165/1000) = √(0.000123975 + 0.000137775) = √(0.000261750) = 0.016179. z = 0.020/0.016179 = 1.236 → p = 0.216. Verdict: "no evidence."

Step 3 — the paired view. Only b=45 and c=25 matter. Under the null each of these 70 discordant questions is a fair coin, expected split 35/35.

Step 4 — McNemar statistic. χ² = (|45−25|−1)² / (45+25) = (20−1)² / 70 = 361/70 = 5.157.

Step 5 — p-value from χ²(1 df). p = 0.0232. Cross-check with the exact binomial (is 45-of-70 surprising for a fair coin?): pexact = 2·P(X ≥ 45 | n=70, p=0.5) = 0.0225. Both agree: significant at 0.05.

Step 6 — the moral in numbers. The unpaired noise scale (0.0162) was inflated by the 930 agreement questions; pairing removed them, and the SAME 2-point gap went from p=0.216 to p=0.023 — roughly a 10× drop in p, purely from using the right test.

python — McNemar from scratch, then paired bootstrap
import math, numpy as np
from scipy import stats
b, c = 45, 25
chi2 = (abs(b - c) - 1)**2 / (b + c)      # 5.157
p_chi = stats.chi2.sf(chi2, 1)                # 0.0232
p_exact = 2 * stats.binom.sf(max(b,c)-1, b+c, 0.5)  # 0.0225
print(round(chi2,3), round(p_chi,4), round(p_exact,4))
# paired bootstrap on the delta (works for any metric):
rng = np.random.default_rng(0)
A = np.array([1]*855 + [0]*145)   # stand-in per-example correctness
B = np.array([1]*835 + [0]*165)
idx = np.arange(1000)
deltas = [ (A[s].mean() - B[s].mean())
           for s in (rng.choice(idx, 1000, replace=True) for _ in range(2000)) ]
print(np.percentile(deltas, [2.5, 97.5]))    # CI on the delta
python — the library one-liner
from statsmodels.stats.contingency_tables import mcnemar
tbl = [[810, 45], [25, 120]]
print(mcnemar(tbl, exact=True))     # pvalue ~ 0.0225
# or: scipy.stats.binomtest(45, 70, 0.5)
Misconception: You might think "not statistically significant" means "the models are equally good." But our unpaired test said p=0.216 while the correct paired test on the SAME data said p=0.023. Absence of evidence under a weak test is not evidence of absence. Before accepting a null result, ask whether the test had the power (and the pairing) to detect the gap you actually care about — which is exactly Chapter 5.
Interview lens. "Two candidate models each score within noise using a two-proportion z-test on your 1,000-example eval. Both ran on the same examples. What do you do before declaring a tie?" Point out the test is throwing away the pairing: build the 2×2 table and run McNemar (or a paired bootstrap for non-accuracy metrics). Because the shared examples' difficulty is common to both models, the paired test can be dramatically more powerful — a p of 0.2 unpaired can become 0.02 paired on identical data. A staff-level answer also reports the paired CI on the delta (effect size, not just a star), checks how many discordant examples exist (if b+c is tiny, use the exact binomial and expect weak power regardless), and reads a sample of the discordant examples — the b and c cells are the best qualitative error-analysis set you own.
Two models on the same 1,000 questions: A-only-correct on 45, B-only-correct on 25, agreeing on the rest. Which cells does McNemar's test use?

Pairing wrings the most out of the data you have. But no test can see a gap it is too small to resolve. How many examples do you actually need? That is power — Chapter 5.

Chapter 5: Power & Sample Size — Can Your Eval Even See the Gap?

Chapter 4 taught you to test properly. But a proper test on too few examples is a microscope with the lens cap on — you will see nothing and conclude "no difference." This chapter is about whether your eval could ever have seen the effect you are hunting.

Two ways a test can fail. A false alarm — declaring a gap that is not there — happens with probability α (alpha), which we set at 0.05 by convention. A miss — failing to detect a gap that IS there — happens with probability β (beta). The power of a test is 1−β: the chance you catch a real effect. Convention aims for 80% or more.

Build the intuition geometrically. Under the null, the observed gap is a bell curve centered at 0 with width SEdiff. Under the alternative (a real gap δ), it is a bell curve centered at δ. Power is an overlap question: how much of the alternative's curve lands beyond the significance threshold. Small δ or big SE → the curves overlap heavily → detection is a coin flip.

Derive the sample-size formula from that picture. To reject reliably, the true gap δ must exceed roughly (zα/2 + zβ) standard errors. Squaring and solving for n gives:

n = (zα/2 + zβ)² · ( p1(1−p1) + p2(1−p2) ) / δ²

Every symbol earns its place. 1.96 (that is zα/2) buys the 5% false-alarm rate. 0.8416 (zβ) buys 80% detection. The variance sum is the noise. And δ² in the denominator is the killer.

That δ² denominator is the headline of the whole chapter: halving the gap you want to detect QUADRUPLES the required test set. Detecting 5-point gaps is cheap; detecting 1-point gaps costs about 19,000 examples; detecting 0.5-point gaps costs about 78,000. This single fact explains most eval frustration you will ever feel.

Run the flagship numbers (below). To detect 85% vs 86% with 80% power you need about 19,500 examples per model. Now hold that against reality: MMLU's smallest subjects have around 100 questions, GSM8K's test split is 1,319 problems, HumanEval is 164. Per-subject "wins" on such slices are astrology.

Flip the formula into a diagnostic — the minimum detectable effect (MDE): given your N, what is the smallest gap you can see with 80% power? For N=1,000 per model at ~85% accuracy, the MDE is about 4.4 points. Any claimed improvement below your MDE deserves the response: "your eval cannot support that claim."

What underpowered comparisons do to a whole FIELD is worth pausing on. Only lucky-large deltas cross the significance bar, so published and leaderboard gaps are systematically inflated — a phenomenon called the winner's curse — and follow-up reproductions "mysteriously" shrink. This needs no fraud, just small N and selective attention. It sets up Chapter 6 directly.

Practical levers before you buy more data: pairing (Ch4) shrinks the effective noise and can cut required N dramatically; stratifying by difficulty helps; and for generative models, more samples per prompt reduces decode variance (Ch8). Online-experiment power with real users, and CUPED-style variance reduction, live in Experiment Design — same math, different battlefield.

Power Curve Explorer

Two bell curves: null at 0, alternative at δ. The area of the alternative past the threshold fills in as POWER. Drag δ and N; watch power climb. The readout tells you the N required for 80% power at the current gap — and how brutally it grows as δ shrinks.

True gap δ (points)1.0
N per model1000
baseline accuracy0.85
Worked example — sizing an eval for a 1-point gap

Setup. How many test examples per model to detect 85% vs 86% (one point) with two-sided α=0.05 and 80% power?

Step 1 — the two z multipliers. zα/2 = 1.9600 (5% false alarm, two-sided), zβ = 0.8416 (80% power). Sum = 2.8016; squared = 7.8490.

Step 2 — the noise term. p1(1−p1) + p2(1−p2) = 0.85×0.15 + 0.86×0.14 = 0.1275 + 0.1204 = 0.2479.

Step 3 — the effect term. δ² = (0.86 − 0.85)² = (0.01)² = 0.0001.

Step 4 — assemble. n = 7.8490 × 0.2479 / 0.0001 = 1.94578 / 0.0001 = 19,458 examples per model (exact 19,457.4; round up).

Step 5 — sanity checks. A 2-point gap (δ² = 0.0004) needs n ≈ 4,865 — a quarter, confirming the 1/δ² law. And the MDE at n=1,000: δ = √(7.8490×0.2479/1000) = √(0.0019458) = 0.0441 — you can only see 4.4-point differences.

python — required_n from scratch, with an empirical check
import math, numpy as np
from scipy import stats
def required_n(p1, p2, alpha=0.05, power=0.8):
    za = stats.norm.ppf(1 - alpha/2)          # 1.96
    zb = stats.norm.ppf(power)                # 0.8416
    varsum = p1*(1-p1) + p2*(1-p2)
    return (za + zb)**2 * varsum / (p2 - p1)**2
n = required_n(0.85, 0.86)
print(math.ceil(n))                          # 19458
# empirical: simulate at that N, fraction of z-tests that reject ~ 0.80
rng = np.random.default_rng(0); N = math.ceil(n); hits = 0
for _ in range(10000):
    a = rng.binomial(N, 0.86)/N; bb = rng.binomial(N, 0.85)/N
    se = math.sqrt(a*(1-a)/N + bb*(1-bb)/N)
    if abs(a-bb)/se > 1.96: hits += 1
print(hits/10000)                            # ~0.80
python — the library one-liner
from statsmodels.stats.power import NormalIndPower
from statsmodels.stats.proportion import proportion_effectsize
es = proportion_effectsize(0.86, 0.85)
print(NormalIndPower().solve_power(es, power=0.8, alpha=0.05))
Misconception: You might think running the eval and getting p > 0.05 tells you the models are equivalent. But if your N gives only 20% power for the gap you care about, a non-significant result was the EXPECTED outcome even when the gap is real. "Not significant" from an underpowered eval is a statement about your test set's size, not about your models. Always ask for the MDE before you interpret a null.
Interview lens. "You're designing the eval suite for a team that ships weekly and cares about regressions as small as 0.5 points. Size the test set, and explain the tradeoffs when the naive answer is too expensive." Naive sizing: 0.5 points near 85% needs roughly (1.96+0.84)² × 0.25 / 0.005² ≈ 78k examples per side — likely unaffordable weekly. The staff-level moves: (1) pair everything — the same test set across versions with McNemar/paired bootstrap can cut effective noise severalfold since consecutive versions agree on most examples; (2) tier the suite — a large weekly gate for the 0.5-point bar on aggregate metrics, small per-capability slices explicitly labeled as low-power smoke tests, never used for ship/no-ship; (3) reduce non-sampling variance (fixed seeds/temperature or averaged samples, Ch8); (4) publish the suite's MDE so nobody reads a 0.3-point wiggle as signal.
Your 1,000-example eval has an MDE of ~4.4 points at 80% power. A teammate claims a 1-point improvement, and the eval shows +1.1 points (not significant). What's the right conclusion?

Power protects a single comparison. But real teams do not run one test — they sweep twenty configs and report the best. That is a different, sneakier way to fool yourself, and it is Chapter 6.

Chapter 6: Multiple Comparisons & Benchmark Shopping

You tried 20 prompt variants and one beat baseline at p=0.03. Champagne? Before you pop it, compute the probability that pure noise hands you at least one such "win." The answer is going to be uncomfortable.

This is the garden of forking paths, quantified. Each test at α=0.05 has a 5% false-alarm rate. But run 20 independent tests where NOTHING works, and P(at least one false win) = 1 − 0.9520 = 64%. The dashboard almost guarantees a champagne moment. The family of tests, not each individual test, is what needs the 5% guarantee.

Name the everyday versions of this sin, because you commit them without noticing: sweeping 20 hyperparameter configs and reporting the best; evaluating on 15 benchmark subsets and highlighting the 2 that improved; rerunning a flaky eval "to be sure" and keeping the good run; trying 5 judge prompts and picking the favorable one. All are multiple comparisons wearing different clothes.

Bonferroni, the blunt instrument. To keep the FAMILY false-alarm rate at 5% across m tests, require each individual test to clear α/m. For 20 configs: 0.05/20 = 0.0025. It is simple, assumption-free, and it works — but it is brutally conservative when m is large, because it guards against even a single false positive anywhere in the family.

Benjamini-Hochberg (BH), the practical instrument, controls the false discovery rate (FDR) instead: rather than "probably zero false wins," it promises "at most ~5% of my declared wins are false." Recipe: sort the m p-values ascending, find the LARGEST k such that p(k) ≤ (k/m)·α, and reject hypotheses 1..k. Early p-values face a near-Bonferroni bar; later ones face progressively gentler bars — a rising staircase rather than a flat wall.

Seed variance is the same disease in disguise. Train the identical config with 5 random seeds and scores land at [84.1, 83.5, 84.8, 83.9, 84.4] — mean 84.14, std 0.49. Reporting your best seed (84.8) against a rival's mean is benchmark shopping against yourself. The honest unit of comparison is mean ± std over seeds. "Quantifying Variance in Evaluation Benchmarks" (Madaan et al., June 2024) measured exactly this and found seed noise swamps many claimed gaps.

The field-scale consequence, why leaderboard tops are inflated: hundreds of teams submit to the same benchmark, and the top slot is selected as the MAX over many noisy scores, so it is biased upward even if everyone is honest — the winner's curse from Chapter 5, now at ecosystem scale. The Leaderboard Illusion study (April 2025) documented the industrial version: labs privately testing many Chatbot Arena variants and surfacing only the best.

Defenses that actually work: preregister the ONE primary metric and test before running the sweep (demote everything else to exploratory); hold out a confirmation set the sweep never touches and validate the single winner on it once; apply BH to any screen you do report; and always report seed mean±std. The release-gate mechanics that operationalize this live in Regression Testing & Quality Gates.

The Benchmark Shopping Simulator

m config cards, each secretly having ZERO true improvement. Each runs a simulated eval and shows a p-value; cards crossing the current threshold light up gold with "WINNER." Uncorrected mode almost always crowns fake winners. Switch to Bonferroni or BH and watch the bar rise (BH as a staircase over the sorted p-values) and the fake winners flick off.

Number of configs m20
Worked example — family error, BH, and seed std

Setup. (a) false-win probability for 20 null configs; (b) BH on 10 sorted p-values [0.001, 0.004, 0.012, 0.021, 0.041, 0.09, 0.13, 0.22, 0.45, 0.78] at α=0.05; (c) seed mean±std for [84.1, 83.5, 84.8, 83.9, 84.4].

Step a — family false-alarm rate. P(at least one of 20 fires under the null) = 1 − (0.95)20 = 1 − 0.3585 = 0.6415. A 64% chance of fake champagne. Bonferroni bar: 0.05/20 = 0.0025 per test.

Step b1 — BH thresholds (k/m · α for k=1..10): 0.005, 0.010, 0.015, 0.020, 0.025, 0.030, 0.035, 0.040, 0.045, 0.050.

Step b2 — compare. 0.001 ≤ 0.005 PASS; 0.004 ≤ 0.010 PASS; 0.012 ≤ 0.015 PASS; 0.021 ≤ 0.020 FAIL; 0.041 ≤ 0.025 FAIL; the rest fail.

Step b3 — step-up. The LARGEST passing k is 3, so reject hypotheses 1–3 — declare exactly 3 wins. Bonferroni (bar 0.005) would declare 2 (0.001, 0.004); naive per-test 0.05 would declare 5.

Step c1 — seed mean. (84.1+83.5+84.8+83.9+84.4)/5 = 420.7/5 = 84.14.

Step c2 — deviations & squares. −0.04, −0.64, +0.66, −0.24, +0.26; squares 0.0016, 0.4096, 0.4356, 0.0576, 0.0676; sum = 0.9720.

Step c3 — sample std (divide by n−1=4). 0.9720/4 = 0.2430; std = √(0.2430) = 0.493. Report "84.14 ± 0.49 over 5 seeds" — and note this seed noise alone is bigger than Chapter 0's 0.4-point "win."

python — Benjamini-Hochberg from scratch + seed report
import numpy as np
print(round(1 - 0.95**20, 4))            # 0.6415
def benjamini_hochberg(ps, alpha=0.05):
    order = np.argsort(ps)
    m = len(ps)
    passing = [i+1 for i, j in enumerate(order)
               if ps[j] <= (i+1)/m * alpha]
    kmax = max(passing) if passing else 0
    return set(order[:kmax])              # indices of rejections
ps = [0.001,0.004,0.012,0.021,0.041,0.09,0.13,0.22,0.45,0.78]
print(len(benjamini_hochberg(ps)))         # 3
seeds = np.array([84.1,83.5,84.8,83.9,84.4])
print(round(seeds.mean(),2), round(seeds.std(ddof=1),3))  # 84.14 0.493
python — the library one-liner
from statsmodels.stats.multitest import multipletests
reject, p_adj, _, _ = multipletests(ps, alpha=0.05, method='fdr_bh')
print(reject.sum())                       # 3
Misconception: You might think each test "stands on its own" — a p=0.03 is a p=0.03 regardless of what else you tried. But selection changes the meaning. The probability that your BEST-of-20 null configs shows p ≤ 0.05 is 64%, not 5%. A p-value is only interpretable alongside the number of shots you took at the goal — which is exactly why preregistering the shot count matters.
Interview lens. "Your team swept 48 fine-tuning configs and the top one beats baseline at p=0.01 on the dev benchmark. The director wants it in the launch deck. What do you check and propose?" Frame it as a selection problem: the max of 48 noisy scores is biased upward, and under a global null the chance SOME config hits p ≤ 0.01 is 1−0.9948 ≈ 38%. Check whether p=0.01 survives correction (Bonferroni bar 0.05/48 ≈ 0.001 — it doesn't; BH depends on the other 47). Propose the clean confirmation: rerun ONLY the winning config against baseline on a held-out test set the sweep never touched, with fresh seeds — a single preregistered test where p=0.01 would mean what it says. Also report the winner as mean±std over seeds. This is the difference between exploration and confirmation — the heart of trustworthy eval culture.
You sweep 20 prompt variants (all truly useless) at per-test α=0.05. Roughly what is the chance at least one shows a "significant" improvement?

Every trap so far — jitter, weak tests, underpowered slices, selection bias — converges on one artifact everyone trusts and almost nobody error-bars: the leaderboard. Let's watch one lie in real time.

Chapter 7: Showcase — The Leaderboard Lie

Everything from Chapters 1–6 collides here, in the single artifact your whole industry stares at and almost never error-bars: the leaderboard. This is the showcase. YOU control the truth — the models' real skills — and you get to watch the rankings lie.

Set the scene: 8 models. In the default preset, all eight have IDENTICAL true accuracy 85%. Every "Run eval" draws a fresh N-example test set per model and ranks them. The podium reshuffles every single run. Gold medals are being awarded by a random number generator, and the medal ceremony looks exactly as authoritative either way.

Why are ranks noisier than scores? A rank is an order statistic — a max/ordering over several noisy quantities. Each pairwise ordering flips whenever the score gap between two models is small relative to SEdiff, and with 8 models there are 28 pairs, so SOME pair flips almost every run. The rank-1 slot is claimed by whoever got luckiest — the winner's curse from Chapter 5, made visible.

The CI-overlap lens makes it honest. Turn on confidence intervals and at N=500 all eight bars overlap heavily, saying "these are statistically indistinguishable" — yet the RANK column keeps confidently printing 1 through 8. The number that looks most authoritative (the rank) carries the least statistical support on the page.

Play the three sliders as three lessons. (1) N drives everything — churn melts as N grows (Chapter 1's law). (2) More models = more churn at fixed gaps (Chapter 6's multiplicity). (3) The true-gap slider shows the detectability threshold — gaps below about 2 SEdiff churn, gaps above lock in (Chapter 5's power).

Now the real world, 2024–2025. The Leaderboard Illusion study (April 2025) showed Chatbot Arena's dynamics rewarded labs that privately tested many variants and surfaced the best — Chapter 6's max-selection, industrialized. The Llama 4 Maverick incident (April 2025) — the Arena-submitted variant differed from the released model — showed rank-optimizing pressure in action. And GSM1k (May 2024) showed several models dropping many points on freshly rewritten test items: some leaderboard positions were measuring memorization, a bias no error bar catches.

What an honest leaderboard looks like: a score with a CI per model; ranks only where separations are significant (cluster ties into bands); paired tests on shared test sets for adjacent entries; multi-run averages for stochastic decoding. Some of this exists — HELM ships CIs; the Open LLM Leaderboard aggregates carefully — but rank-1 headlines still ignore all of it.

Carry three questions into every leaderboard you ever read: how big is N, how big are the gaps in SE units, and how many models or configs were tried? You now own the math behind all three.

The Leaderboard Lie — the showcase

Left: a live leaderboard — medal ranks, score bars with optional CI whiskers, and a gold "true skill" tick per model. Each run resamples every test set and re-ranks. Right: a rank-history strip painting each model's rank over recent runs — identical-skill models leave a churning noise pattern; separated models leave clean bands. Readouts: rank churn and P(true-best ranked #1). To break it: set the gap to 0, crank down N, and watch the podium become a slot machine.

Test-set size N500
Number of models8
True gap spread (pts)0.0
Worked example — how often does the worse model win?

Setup. Model A has TRUE accuracy 86%, model B has TRUE accuracy 85%. Each is evaluated on its OWN independent N=500 test set. How often does the leaderboard put the WORSE model (B) on top?

Step 1 — variance of each observed score. A: 0.86×0.14/500 = 0.1204/500 = 0.0002408. B: 0.85×0.15/500 = 0.1275/500 = 0.000255.

Step 2 — SE of the observed difference (independent). SEdiff = √(0.0002408 + 0.000255) = √(0.0004958) = 0.022267 — about 2.2 points of noise on a 1-point true gap.

Step 3 — the flip probability. B tops A when (B̂ − Â) > 0. That difference is approximately normal with mean −0.01 and SD 0.022267, so P(flip) = Φ(−0.01/0.022267) = Φ(−0.449) = 0.327.

Step 4 — read it out loud. The worse model wins the leaderboard roughly 1 time in 3. A coin flip is 1 in 2 — this "benchmark comparison" is barely better than flipping a coin.

Step 5 — the N cure. At N=5,000, SEdiff = √(0.86×0.14/5000 + 0.85×0.15/5000) = √(0.00004958) = 0.007041, so P(flip) = Φ(−0.01/0.007041) = Φ(−1.420) = 0.078. Ten times the eval data takes the lie from 33% to 8% — and a Monte Carlo of 100k simulated runs confirms it (~0.328 at N=500).

python — the leaderboard simulator + flip probability
import math, numpy as np
from scipy import stats
for N in (500, 5000):
    sed = math.sqrt(0.86*0.14/N + 0.85*0.15/N)
    print(N, round(sed,6), round(stats.norm.cdf(-0.01/sed),3))
# 500 0.022267 0.327   |   5000 0.007041 0.078
rng = np.random.default_rng(0)
a = rng.binomial(500, 0.86, 100000)/500
b = rng.binomial(500, 0.85, 100000)/500
print(round(np.mean(b > a) + 0.5*np.mean(b == a), 3))  # ~0.328
Misconception: You might think rank #1 is the safest fact on a leaderboard — scores might wobble, but surely the ORDER means something. It is the reverse: ranks are order statistics of noisy scores, so they are strictly noisier than the scores themselves. A 1-point-worse model takes the crown a third of the time at N=500; the podium is often the least trustworthy pixel on the page.
Interview lens. "Design an internal leaderboard for your company's models that CAN'T lie the way public ones do. Five load-bearing features?" (1) Fixed shared test sets so all comparisons are paired — adjacent ranks get McNemar/paired-bootstrap p-values, not eyeballed gaps. (2) CIs on every score, and ranks reported as statistical TIES (banded clusters) whenever adjacent intervals overlap. (3) N sized from a power analysis for the minimum gap the org cares about, with the suite's MDE printed on the page. (4) Multi-seed / multi-sample runs for stochastic models, reporting mean±std so nobody submits their lucky run — plus submission limits and a held-out confirmation set to kill variant-shopping. (5) Periodic fresh test items to detect contamination, GSM1k-style, since no error bar catches memorization. Bonus: the rank column itself should show churn (rank over last k runs), making instability visible instead of hiding it.
Eight models with IDENTICAL true skill are ranked on fresh N=500 test sets each week. What does the weekly leaderboard look like?

The leaderboard treated "noise" as a single blob. But rerun the same model on the same test set with a different seed or temperature and the score still moves. Time to split the noise into its parts.

Chapter 8: Variance Decomposition — Where Does the Noise Come From?

So far "noise" meant test-set sampling. But rerun the SAME model on the SAME test set with a different training seed or a different temperature and the score moves anyway. The single blob called "noise" is actually a stack of independent sources, and the compute budget should attack the biggest one — not the most convenient one.

Enumerate the sources an LLM eval actually has. (1) Training-seed variance — retrain the same config, get a different model (weight init, data order, dropout). (2) Test-set sampling variance — Chapter 1's √(p(1−p)/N). (3) Decode-sampling variance — temperature > 0 makes the SAME model give different answers per run. Plus honorable mentions: prompt-template sensitivity, quantization/hardware nondeterminism, and judge variance for LLM-judged metrics.

The key mathematical fact that makes this tractable: independent noise sources add in VARIANCE, not in standard deviation. Total variance = varseed + vartest + vardecode; total std = √ of the sum. The consequence is huge: the total is dominated by the largest term, and shrinking a small term is nearly invisible. The square-root arithmetic makes "fixing the wrong noise" provably futile.

How to MEASURE each term with a designed grid: train S seeds; for each seed run the eval R times with sampling on. Variance across seeds (of per-seed means) estimates seed noise; variance across repeats within a seed estimates decode noise; the analytic p(1−p)/N estimates test-set noise. This is a one-way random-effects ANOVA (analysis of variance) wearing ML clothes — Madaan et al. (June 2024) ran exactly this design across popular benchmarks and found seed variance rivals or exceeds many published deltas.

Averaging attacks each term differently — this is the budgeting lever. Averaging over s seeds divides varseed by s (test/decode untouched). Enlarging the test set k-fold divides vartest by k. Averaging r decode runs (or r samples per prompt) divides vardecode by r. And each remedy has a different unit price: a seed costs a full training run; test examples cost labeling; decode repeats cost only inference.

The temptation of a deterministic eval and its price: temperature=0 sets vardecode to zero and makes evals reproducible — but greedy decoding measures a slightly different quantity than the sampled behavior users actually experience, and single-sample pass@1 at t=0 can misrank models whose sampled distributions differ. The sensible middle ground is sampling multiple generations and averaging (the pass@k machinery itself belongs to GenAI Eval).

Reporting discipline that follows directly: state WHICH sources your error bar includes. "acc 84.1 ± 0.5 (test-set CI, single seed, t=0)" and "acc 84.1 ± 1.2 (3 seeds × 5 decode runs, all sources)" are different claims — most papers publish the first and implicitly defend the second. Regression gates need the ALL-sources number or they will fire on seed noise (Regression Testing owns that machinery).

The Variance Budget Allocator

Left: a stacked bar of the three variance components (seed / test-set / decode), with the TOTAL error bar above as a bracket whose width is the √ of the stack. A wallet (100 units) with three buys: +1 seed (cost 30), +500 test (cost 10), +1 decode repeat (cost 2). Purchases shrink their component live; the ghost bracket remembers your starting width. "Auto-optimize" greedily spends the wallet — and usually ignores seeds.

Worked example — which upgrade buys the most precision?

Setup. seed-to-seed std 0.6 points, test-set SE 1.0 point, decode-sampling std 0.4 points (independent). Compute the total error bar, then compare averaging 3 seeds vs quadrupling the test set.

Step 1 — convert every std to a variance. seed 0.6² = 0.36; test 1.0² = 1.00; decode 0.4² = 0.16.

Step 2 — total variance (independent sources add). 0.36 + 1.00 + 0.16 = 1.52. Total std = √(1.52) = 1.233 points. Note it is NOT 0.6+1.0+0.4 = 2.0 — stds don't add.

Step 3 — upgrade A: average 3 seeds (three full training runs). seed variance → 0.36/3 = 0.12. New total = √(0.12 + 1.00 + 0.16) = √(1.28) = 1.131. Three expensive retrainings bought 0.10 points of precision.

Step 4 — upgrade B: quadruple the test set (labeling/inference only). test variance → 1.00/4 = 0.25. New total = √(0.36 + 0.25 + 0.16) = √(0.77) = 0.877. A 0.36-point improvement — 3.5× the gain of upgrade A, at a fraction of the cost.

Step 5 — the rule this proves. Compare the COMPONENTS first (1.00 dominates 0.36 dominates 0.16), attack the largest. Even eliminating decode noise entirely (0.16 → 0) only moves the total from 1.233 to √(1.36) = 1.166.

python — variance decomposition, four scenarios
import math
vs, vt, vd = 0.36, 1.00, 0.16          # seed, test, decode variances
print(round(math.sqrt(vs + vt + vd), 3))       # 1.233 — all sources
print(round(math.sqrt(vs/3 + vt + vd), 3))     # 1.131 — average 3 seeds
print(round(math.sqrt(vs + vt/4 + vd), 3))     # 0.877 — 4x test set
print(round(math.sqrt(vs + vt), 3))            # 1.166 — kill decode entirely
python — recover the components from a designed S×R grid
import numpy as np
rng = np.random.default_rng(0)
S, R = 8, 6                                # seeds, decode repeats per seed
seed_eff = rng.normal(0, 0.6, S)             # true seed std 0.6
grid = seed_eff[:, None] + rng.normal(0, 0.4, (S, R))
seed_means = grid.mean(axis=1)
print("seed var est:", round(seed_means.var(ddof=1), 3))     # ~ 0.36 + small
print("decode var est:", round(grid.var(axis=1, ddof=1).mean(), 3))  # ~ 0.16
Misconception: You might think "noise is noise" and any variance-reduction work helps — so you fix seeds, pin temperature to 0, and call the eval clean. But you have only zeroed the SMALLEST terms: with test-set SE at 1.0pt, your total error bar barely moves (1.233 → 1.077 with seed AND decode gone), and you have quietly changed what is being measured (greedy decoding is not the deployed sampling behavior). Decompose first; spend second.
Interview lens. "A researcher claims their new training recipe is +0.8 points, based on one training run per recipe evaluated once at temperature 0.7 on a 1,000-example benchmark. As reviewer, what's your variance accounting, and what re-run do you request?" List the three unaccounted sources: seed variance (one run per recipe — Madaan et al. 2024 finds 0.3–1+ points for many setups), test-set SE (~1.1 points at 85%/N=1000, alone bigger than the claim), and decode variance at t=0.7 with a single sample. The claimed +0.8 sits comfortably inside plausible combined noise. Request the minimal decisive design: 3 seeds per recipe × 3+ decode runs (or multi-sample averaging), evaluated PAIRED on the same test set, reporting mean±std per recipe and a paired test on per-seed deltas. Note the cheap wins first — decode repeats cost only inference, and pairing is free — before demanding expensive extra seeds.
Your eval noise decomposes as: seed std 0.6, test-set SE 1.0, decode std 0.4. You can afford ONE upgrade. Which buys the most precision?

You now own the full toolkit. The next chapter drills it into interview reflexes; the last files everything into one cheat sheet and points to where each thread continues.

Chapter 9: The Interview Arsenal

Eval-statistics questions are staff-interview gold because they expose whether a candidate reasons about uncertainty or just reads dashboards. This chapter drills the reflexes and then hands you ten worked Q&A and three debates to argue both sides of.

The meta-skill interviewers probe: given any eval claim, instinctively locate N, the noise scale, the pairing structure, and the number of comparisons tried. Rehearse the 10-second triage — "delta vs 2×SEdiff, paired or not, how many shots were taken?"

Reflex 1 — the back-of-envelope SE. Memorize SE = √(p(1−p)/N) and its worst case √(0.25/N) = 0.5/√N. Instant anchors: N=100 → ±5pt; N=1,000 → ±1.6pt; N=10,000 → ±0.5pt. Producing these without a calculator is the single highest-value move in this topic.

Reflex 2 — the pairing question. "Same test set?" If yes, the disagreement cells b and c are everything (McNemar); paired bootstrap handles any metric. Practice re-deriving (|b−c|−1)²/(b+c) from "discordant items are fair coins under the null" — interviewers love the derivation more than the formula.

Reflex 3 — power sizing on a whiteboard. n ≈ 8 × variance-sum / δ² for 80% power at α=0.05 (since (1.96+0.84)² ≈ 7.85 ≈ 8). One-point gap near 85% → n ≈ 8×0.25/0.0001 ≈ 20k. Say the number BEFORE they ask.

Reflex 4 — multiplicity instinct. Hear "we tried k variants" and immediately compute 1−0.95k out loud; know Bonferroni (α/m) as the conservative bar and BH's staircase as the practical one; know the confirmation-set escape hatch.

Reflex 5 — variance accounting. Any single-run claim gets the three-source checklist (seed / test-set / decode); components add in variance; attack the biggest. Anchor: seed noise alone rivals many published gaps (Madaan et al., 2024).

How to structure answers at staff level: state the framework first ("this is a paired comparison with multiplicity"), do ONE crisp calculation, name the correction, then land on a DECISION recommendation. Interviewers grade the landing, not the formula recall.

The SE Reflex Trainer

Flashcards. The canvas deals a scenario ("acc 90%, N=200"); drag the slider to guess the 95% CI half-width; on Reveal the true interval animates in around the score and scores your gut (within 25% = staff-level, within 50% = solid, else recalibrate). Build the 0.5/√N anchors until they're automatic.

Your guess: half-width (pts)3.0
Worked example — the rapid-fire opener, answered in four sentences

Setup. "Our agent hits 90% on a 200-task suite; the old one hit 87%. Real?" Answer it with numbers.

Step 1 — SE of the new score. √(0.9×0.1/200) = √(0.09/200) = √(0.00045) = 0.0212 → ±2.1 points.

Step 2 — noise on the difference (unpaired). √(0.9×0.1/200 + 0.87×0.13/200) = √(0.00045 + 0.0005655) = √(0.0010155) = 0.0319 → ±3.2 points. The observed gap is 3.0 — LESS than one noise unit, far short of the ~2 units needed.

Step 3 — the pairing rescue. If both agents ran the SAME 200 tasks, ask for the disagreement counts. If the split is 14 vs 4 among 18 discordants, the exact binomial gives p = 2·P(X ≥ 14 | n=18, p=0.5) = 0.031 — significant where the unpaired view was not.

Step 4 — the spoken landing. "Unpaired, 90 vs 87 on 200 tasks is one sigma — noise. Give me the paired disagreement table; if it splits like 14–4 it's real (p≈0.03). Otherwise we need roughly 4× the tasks to adjudicate a 3-point gap properly."

python — the eval_triage helper you'd keep in your snippets
import math
from scipy import stats
def eval_triage(k1, n1, k2, n2, discordant=None):
    p1, p2 = k1/n1, k2/n2
    se_diff = math.sqrt(p1*(1-p1)/n1 + p2*(1-p2)/n2)
    z = (p1 - p2) / se_diff
    print(f"unpaired: gap={p1-p2:.3f} SE={se_diff:.4f} z={z:.2f} p={2*stats.norm.sf(abs(z)):.3f}")
    if discordant:
        b, c = discordant
        p_ex = 2 * stats.binom.sf(max(b,c)-1, b+c, 0.5)
        print(f"paired (McNemar exact): {b} vs {c}, p={p_ex:.3f}")
eval_triage(180, 200, 174, 200, discordant=(14, 4))
# unpaired: gap=0.030 SE=0.0319 z=0.94 p=0.347
# paired (McNemar exact): 14 vs 4, p=0.031

Ten interview questions with model answers

Expand each for a staff-grade answer and a follow-up the interviewer will actually ask next.

Q1. Your new model beats production 84.3% to 83.9% on a 1,000-example benchmark. The team wants to ship. Walk me through your full statistical assessment.
Model answer

First quantify the noise floor: SE of each score is √(0.84×0.16/1000) ≈ 1.16 points, so the difference has SE ≈ 1.64 points unpaired — the observed 0.4-point gap is a quarter of a noise unit, p ≈ 0.81. Before concluding, I'd check whether both models ran on the SAME 1,000 examples: if so, pairing via McNemar on the disagreement table can be dramatically more powerful and might rescue the comparison. I'd also ask how many candidate models were compared to pick this one — if it's the best of several, selection bias inflates the gap further. Then I'd compute what the eval CAN detect: at N=1,000 the MDE at 80% power is roughly 4 points, so a 0.4-point true improvement is invisible to this suite by construction. Recommendation: either pair and re-test, or accept that shipping requires non-statistical grounds (cost, latency, qualitative wins) and say so explicitly.

Follow-up: If the paired disagreement table shows 52 vs 48, what do you conclude and what do you tell the team? (Answer: b+c=100, split 52/48 is utterly unremarkable for a fair coin — p ≈ 0.76; tell them it's a tie and the suite can't see this gap.)

Q2. Derive the standard error of an accuracy metric and give me the three memorized anchors you use for instant triage.
Model answer

Each example's correctness is Bernoulli(p): mean p, variance E[X²]−(E[X])² = p − p² = p(1−p). The accuracy is the mean of N independent such variables; independent variances add, and dividing the sum by N divides variance by N², so Var(p̂) = p(1−p)/N and SE = √(p(1−p)/N). Worst case p=0.5 gives SE = 0.5/√N, yielding the anchors: N=100 → ±5 points, N=1,000 → ±1.6 points, N=10,000 → ±0.5 points (and the ±2SE 95% half-widths: 10, 3, 1). The key assumption is independence — clustered questions (same document, same template) violate it and require clustered standard errors, which shrink the effective N. These anchors let me triage any claimed delta in seconds: a gap below one anchor unit is noise until proven otherwise.

Follow-up: How do the anchors change if 10 questions are generated per source document? (Answer: effective N drops toward number-of-documents; if the 10 are highly correlated, treat N as ~N/10, so error bars roughly triple.)

Q3. When do you use Wilson instead of the normal-approximation interval, and what does Wilson actually do differently?
Model answer

Use Wilson when N is small or p-hat is near 0 or 1 — the practical trigger is N·p̂ or N·(1−p̂) below ~10, and ALWAYS for safety-style rates near zero. The Wald interval approximates around p-hat and uses p-hat's own variance estimate, which fails two ways: it can cross 0 or 1 (17/20 gives an upper bound of 100.6%), and at p̂=0 it collapses to zero width, claiming perfect certainty from finite data. Wilson inverts the hypothesis test instead — it asks which true p values would make the observation unsurprising — producing a center pulled toward 0.5 by a z²/2n term and a width that stays inside [0,1]. For 17/20 it gives [64%, 95%], an honest statement that 20 tasks can't distinguish a 65% agent from a 94% one. For guaranteed conservative coverage (regulatory settings), Clopper-Pearson; otherwise Wilson is the default in statsmodels for good reason.

Follow-up: Your red-team eval shows 0 jailbreaks in 300 attempts. What upper bound do you report? (Answer: the "rule of three" — upper 95% bound ≈ 3/300 = 1%; report "0 observed, but consistent with up to ~1% true rate.")

Q4. Explain how you'd put error bars on a corpus-level BLEU score, and the single most common way people botch it.
Model answer

BLEU has no per-example decomposition — it's a geometric mean of corpus-level clipped n-gram precisions times a brevity penalty — so there's no closed-form SE; the tool is the bootstrap. Resample the corpus's sentences (or documents, if sentences cluster) with replacement B=1,000+ times, recompute corpus BLEU per resample, take the 2.5th/97.5th percentiles. The classic botch is computing sentence-level BLEU and averaging — that's a different, worse-behaved metric, not corpus BLEU with error bars. The second botch is resampling below the independence unit (segments when documents are the correlated unit), which gives intervals that are too narrow. For comparing two MT systems, the standard since Koehn (2004) is the PAIRED bootstrap: resample the same sentence indices for both systems and examine the distribution of the BLEU delta, or count sign flips.

Follow-up: Your bootstrap CI for the BLEU delta is [−0.1, +0.9], mean +0.4. What do you report? (Answer: "+0.4 BLEU, 95% CI [−0.1, +0.9] — not significant; the interval straddles zero, so we can't claim an improvement.")

Q5. Two models on the same test set: the unpaired z-test says p=0.22, McNemar says p=0.023. Explain how both can be true and which to believe.
Model answer

Both are correct answers to different questions. The unpaired test treats the two accuracies as independent measurements, so its noise term includes the full example-difficulty variance TWICE — but the models took the same exam, so difficulty is shared and cancels in the comparison. McNemar conditions on the agreements (which contain zero information about which model is better) and tests only the discordant split — 45 vs 25 against a fair coin — capturing the true uncertainty of the paired design. Believe McNemar: it matches how the data was actually generated. The general lesson is that pairing is free statistical power whenever it's available (a 10× p-value improvement on identical data here), and the paired bootstrap extends the same idea to any metric. The unpaired test isn't wrong math; it's the right math for an experiment we didn't run.

Follow-up: When would McNemar and the unpaired test roughly agree? (Answer: when the models rarely agree — low a+d, so the shared-difficulty savings vanish — or at very large N where both have ample power.)

Q6. Size an eval suite that must detect 1-point regressions near 85% accuracy with 80% power. Give the number, then three ways to shrink it.
Model answer

n = (zα/2 + zβ)² · (p1(1−p1)+p2(1−p2)) / δ² = (1.96+0.8416)² · (0.1275+0.1204) / 0.0001 ≈ 19,500 examples per model — call it 20k, and note the brutal 1/δ² scaling that makes 0.5-point detection cost ~78k. Three shrinkers: (1) pairing — consecutive versions agree on the vast majority of examples, and a McNemar/paired-bootstrap design's effective noise depends only on the discordant rate, often cutting required N severalfold; (2) variance reduction outside sampling — averaging multiple decode runs and fixing the harness removes non-test-set noise (and stratified sampling by difficulty helps at the margin); (3) relax the target honestly — gate on a 2-point MDE (needing only ~5k) and explicitly declare sub-2-point changes statistically invisible. The anti-pattern is running the underpowered suite anyway and reading non-significance as safety.

Follow-up: Your suite is 2,000 examples and can't grow. What's its MDE? (Answer: δ = √(7.85×0.25/2000) ≈ 0.031 → ~3.1 points; gate only on 3+ point changes, label the rest smoke tests.)

Q7. A team swept 48 configs and the best shows p=0.01 against baseline. Describe the failure mode, the two corrections, and what you'd actually do.
Model answer

The failure mode is max-selection under multiplicity: with 48 tests, the chance at least one null config reaches p ≤ 0.01 is 1−0.9948 ≈ 38%, so "best of sweep at p=0.01" is weak evidence, and its effect size is inflated by the winner's curse. Bonferroni controls the family-wise error rate by requiring p ≤ 0.05/48 ≈ 0.001 — safe but harsh, and this fails it. Benjamini-Hochberg controls the false discovery rate via the staircase p(k) ≤ k·α/m — more powerful when several configs are truly good, the right choice for screening. What I'd actually do is neither alone: treat the sweep as exploration, then run the ONE winning config against baseline on a held-out confirmation set the sweep never touched, with fresh seeds, as a single preregistered test. If p=0.01 reproduces there, it means what it says. This exploration/confirmation split is cheaper than correcting across 48 tests and produces a cleaner launch narrative.

Follow-up: The confirmation run comes back p=0.06 and the team says "so close — let's just add more data to it." Respond. (Answer: that's optional-stopping p-hacking; either preregister the new N up front or accept the confirmation failed — you can't peek and extend.)

Q8. What are the main variance sources in a modern LLM eval, how do you measure each, and how do you decide where to spend compute?
Model answer

Three dominant sources: training-seed variance (retrain same config → different model), test-set sampling variance (the √(p(1−p)/N) term), and decode-sampling variance (temperature > 0 → different outputs per run); plus prompt-template sensitivity and judge variance where applicable. Measure with a designed grid: S seeds × R decode repeats on the fixed test set — between-seed variance of per-seed means estimates seed, within-seed variance across repeats estimates decode, the analytic formula gives the test term (one-way random-effects ANOVA; Madaan et al. 2024 ran it at scale and found seed noise rivals many published deltas). Because independent variances ADD, total std is the √ of the sum, so the total is dominated by the largest component — the budget rule is to compare components and buy down the biggest: decode repeats are cheapest (inference only), test examples next (labeling), seeds most expensive (full training). Spending on a minority term is provably near-useless: eliminating a 0.4-point component under a 1.0-point one moves the total bar by ~5%.

Follow-up: Leadership wants ONE number per model. What number and footnote? (Answer: report the all-sources mean±std, e.g. "84.1 ± 1.2 (3 seeds × 5 samples, incl. test-set CI)," and footnote which sources it includes.)

Q9. Explain why public LLM leaderboard rankings are less trustworthy than the scores they're computed from, citing a real 2024–2025 incident.
Model answer

Ranks are order statistics of noisy scores: a pairwise ordering flips whenever the true gap is small relative to SEdiff, and a 1-point-worse model tops a 500-example comparison about a third of the time. With many models, SOME adjacent pair flips almost every refresh, so rank churn is the norm even with static skills — and the #1 slot specifically is a max over noise, biased upward. Real incidents show the human amplifiers: The Leaderboard Illusion (April 2025) documented labs privately testing many variants on Chatbot Arena and surfacing only the best — best-of-k selection laundered into a single public rank; the Llama 4 Maverick incident (April 2025) involved an Arena-submitted variant that differed from the released model; GSM1k (May 2024) showed some models dropping double digits on freshly rewritten items, meaning certain rankings partly measured memorization. The trustworthy reading: check N, check whether adjacent CIs overlap, check how many submissions were tried, and treat rank #1 vs #3 as a tie unless a paired test says otherwise.

Follow-up: If you ran a public leaderboard, what submission policy would prevent variant-shopping? (Answer: cap submissions per org, require pre-registration of the exact model, publish CIs and rank-as-ties, and rotate a private held-out slice.)

Q10. Statistics aside, what can perfectly-executed CIs and paired tests still NOT tell you about your model?
Model answer

Everything in the sampling-statistics toolkit conditions on the test distribution being (a) the distribution you care about and (b) genuinely unseen. It cannot detect contamination — GSM1k showed models scoring high on memorized items while dropping sharply on fresh rewrites, with tight CIs around the inflated numbers. It cannot detect distribution shift — a CI on yesterday's traffic says nothing about next month's users. It cannot detect metric invalidity — optimizing a proxy that diverges from the outcome you want (Goodhart), which is metric-design's territory. And it cannot detect harness bugs — a mis-parsed answer format produces precisely repeatable wrong scores. So the mature workflow pairs statistical rigor WITH validity practices: periodic fresh test items, contamination checks, qualitative reads of discordant examples, and metric audits. Precision and validity are orthogonal axes; this lesson maxes out one of them.

Follow-up: Design a cheap quarterly "validity audit" for a benchmark you gate releases on. (Answer: rewrite 50 items from scratch, re-score top models, and flag any that drop > 2× their CI — a contamination smoke test for ~one day of work.)

Three debates — argue both sides

Debate 1: "Below N=1,000, benchmark results should be banned from model cards and launch decks entirely."
PRO: sub-1,000-example evals have ±3-point-or-worse error bars, larger than most claimed deltas; publishing them invites over-reading, and small-N agentic suites (20–200 tasks) are the worst offenders — Wilson on 17/20 spans 30 points.
CON: banning numbers doesn't ban decisions — teams decide anyway, with vibes instead of data; the fix is mandatory CIs and MDE disclosure, not suppression; for expensive agentic evals, small N with honest Wilson intervals is strictly better than nothing.
MIDDLE: allow small-N numbers only with intervals rendered at equal visual weight and a ban on cross-model rank claims below a power threshold.
Debate 2: "Bonferroni is malpractice in ML evaluation — Benjamini-Hochberg or nothing."
PRO-BH: ML sweeps test dozens-to-thousands of hypotheses where several are truly good; Bonferroni's family-wise guarantee murders power (α/1000 per test), discarding real improvements, and FDR control matches what teams actually care about — a low fraction of fake wins.
PRO-BONFERRONI: release gates and safety claims need "probably zero false positives," which is exactly FWER; BH's guarantee degrades under positive dependence of correlated configs; and Bonferroni's simplicity makes it auditable in incident reviews.
MIDDLE: Bonferroni for ship/no-ship and safety claims, BH for exploratory screens, and the held-out confirmation set beats both when affordable.
Debate 3: "Evals should run at temperature 0 for reproducibility, full stop."
PRO: determinism makes regressions attributable — any score change is a model or harness change, not decode luck; it eliminates one variance component for free and simplifies CI machinery.
CON: users experience sampled behavior, so t=0 measures a distribution nobody ships; greedy decoding can systematically misrank models whose sampled outputs differ in quality; the honest approach is multiple samples per prompt with averaged metrics — decode repeats are the CHEAPEST variance to buy down.
MIDDLE: t=0 for fast CI smoke gates, sampled multi-run protocols for launch evals, and always disclose which regime a number came from.
Rapid fire, no calculator: the worst-case 95% CI half-width for an accuracy measured on N=1,000 examples is closest to:

The reflexes are yours. The last chapter files every tool into a one-screen cheat sheet and shows where each thread continues across the family.

Chapter 10: Connections & The Cheat Sheet

You started with "84.3 vs 83.9 — real or noise?" You can now answer it five different ways. Let's file the tools, draw the decision tree, and point to where each thread continues.

Retell the lesson as one story: a score is a random variable (Ch1) → honest brackets via Wilson (Ch2) → brackets for any metric via bootstrap (Ch3) → comparisons via pairing (Ch4) → test sets sized via power (Ch5) → sweeps disciplined via multiplicity corrections (Ch6) → leaderboards read with rank-skepticism (Ch7) → noise budgeted via decomposition (Ch8). One toolkit, one thread.

The Statistical Triage Tree

Tap a branch to walk from "what are you claiming?" down to the right tool, its formula, and the chapter to revisit. Visited paths stay lit. Hit "Quiz me" to be dealt a random scenario and score whether you tap the right leaf.

Worked example — the capstone capacity-planning number

Setup. What test-set size guarantees a ±1-point 95% CI for ANY accuracy — the worst case?

Step 1 — worst-case variance. p(1−p) is maximized at p=0.5, giving 0.25. Any other accuracy has SMALLER variance, so sizing for p=0.5 covers all cases.

Step 2 — require half-width 0.01. 1.96 × √(0.25/N) = 0.01.

Step 3 — solve. √(0.25/N) = 0.01/1.96 = 0.005102; square: 0.25/N = 0.00002603; N = 0.25/0.00002603 = 9,604.

Step 4 — the memorable form. N = (1.96/0.01)² × 0.25 = 196² × 0.25 = 38,416 × 0.25 = 9,604. Call it 10k: "ten thousand examples buys a ±1-point guarantee, anywhere on the scale."

Step 5 — scale it. ±2 points → N=2,401 (a quarter); ±0.5 points → N=38,416 (four times). The 1/δ² law, one last time.

The one-screen cheat sheet

QuantityFormulaWhen it breaks / when to use
Standard error of accuracySE = √(p(1−p)/N)Assumes independent examples; clustered data inflates true variance
Worst-case SE (anchor)0.5/√NN=100→±5pt, 1k→±1.6pt, 10k→±0.5pt
Wald CIp̂ ± 1.96·SEOnly when N·p̂ and N·(1−p̂) both > ~10
Wilson CIcenter (p̂+z²/2n)/(1+z²/n)Small N or p near 0/1; the default
Bootstrap CIpercentiles of B resamplesAny non-decomposable metric; resample the independence unit!
McNemar (paired accuracy)χ² = (|b−c|−1)²/(b+c)Same test set; use exact binomial when b+c small
Sample size for 80% powern ≈ 8·varsum/δ²Halving δ quadruples n
MultiplicityBonferroni α/m; BH k·α/mAny time you tried more than one thing
Variance addsσtot = √(σseed²+σtest²+σdecode²)Variances add, stds don't; attack the biggest term

The decision tree in prose. One model → CI (Wilson if small/extreme, bootstrap if the metric is exotic). Two models, same test set → McNemar / paired bootstrap. Two models, different test sets → unpaired z, and check power first. Many configs → correct for multiplicity or confirm on held-out data. Any single-run claim → variance decomposition.

Honest limitations of everything taught here. All of it quantifies SAMPLING uncertainty within a fixed distribution — none of it detects distribution shift, test-set contamination (GSM1k showed models dropping points on freshly rewritten items, May 2024), metric gaming, or a metric that measures the wrong thing entirely. Statistics tells you the measurement is precise; it cannot tell you it is the right measurement.

Where each thread continues

Metric Design →Owns metric DEFINITIONS (precision/recall/F1, ROC/AUC, calibration). This lesson takes any metric as given and quantifies its uncertainty. Plots That Tell the Truth →Owns the visualization of the intervals computed here — error bars, bands, and honest leaderboard displays. Experiment Design & A/B Testing →Takes the same power/testing machinery online: A/B tests with real users, CUPED, sequential testing. Evaluating Generative Models →Applies this foundation to pass@k, Bradley-Terry/Elo, FID, and judge agreement and bias. Regression Testing & Quality Gates →Turns the CIs and paired tests built here into release gates, CI pipelines, and monitors. Evaluating Estimators, Fusion & Robots →Applies the same uncertainty discipline to estimators, fusion stacks, and robot benchmarks. The Metrics Ladder →Translates statistically-honest numbers up to product and exec audiences. AI Evaluation (harness) →The process-level eval harness (human eval, judges, dashboards) that these statistics plug into. CS336: LLM Evaluation →Benchmark mechanics (perplexity, MC scoring, contamination) — the substrate whose scores this lesson error-bars.

Further reading, all load-bearing above: Miller, Adding Error Bars to Evals (arXiv 2411.00640, Nov 2024); Madaan et al., Quantifying Variance in Evaluation Benchmarks (June 2024); Singh et al., The Leaderboard Illusion (April 2025); Zhang et al., GSM1k, A Careful Examination of LLM Performance on Grade School Arithmetic (May 2024); Koehn, Statistical Significance Tests for Machine Translation Evaluation (2004).

Interview lens. "Give me your 60-second summary of how a model team should handle eval statistics, as if onboarding a senior hire." Every reported score carries N and a CI (Wilson at small N, bootstrap for exotic metrics); comparisons are paired on shared test sets with McNemar or paired bootstrap; the suite's MDE is computed once and printed so nobody over-reads sub-MDE wiggles; sweeps are exploratory until the single winner survives a held-out confirmation (or BH across the sweep); important claims report mean±std over seeds with decode repeats; and none of this detects contamination or shift, so fresh test items rotate in periodically. Bonus: say which of these you'd automate into CI gates versus leave as human judgment — a bridge straight to Regression Testing.
Which claim can NONE of this lesson's tools support, no matter how large N gets?

"An approximate answer to the right question is worth a good deal more than an exact answer to the wrong question." — John Tukey

An error bar is exactly that: approximate, honest, and worth more than any exact-looking point score.