Skip to content

LLN, CLT, confidence intervals, bootstrap

ModuleM07.4 · build · Python · Pass 4 · 4 to 5 h
You buildpython/tinyllm/prob/stats.py: normal_cdf, normal_ppf, t_cdf, t_ppf, standard_error, mean_ci, wilson_interval, quantile, bootstrap_ci
Contractcourse/contracts/py/tinyllm/prob/stats.pyi · the generator behind rng: spec/pcg32.md
Testscourse/tests/M07.4/ (what they check: section 4), golden values from scipy 1.17.1 in course/fixtures/M07.4/scipy_golden.json
Needsno code from earlier modules · reading: M07.1 one uniform per draw, M07.0 expectation, variance, the normal, S-M07b the variance of a sum
Used bylater: L4.5 sequence metrics with bootstrap CIs · L6.7 every evaluation metric carries a CI · L8.5 quantization verdicts · L10.7 · ag.12 re-implements the bootstrap in Go · M07.5 hypothesis tests build on it · later: L12.3, ethics.04
MilestoneMS-P4 (sequence models; the pass gate runs ol check on every math module of the pass)
Optional depthWasserman, All of Statistics, ch. 5 to 8 (convergence, the bootstrap); Efron and Tibshirani, An Introduction to the Bootstrap (1993), ch. 13; Brown, Cai, DasGupta, “Interval Estimation for a Binomial Proportion” (2001)
  • The law of large numbers says a sample mean converges to the true mean; the central limit theorem says the error is close to normal with standard deviation σ/n\sigma/\sqrt{n}, so quadrupling the data halves the error bar (test_standard_error_shrinks_like_root_n, test_mean_ci_coverage_skewed_data_by_clt).
  • When σ\sigma is estimated from the same nn values, the right quantile is Student’s tt with n−1n - 1 degrees of freedom, not the normal one; at n=10n = 10 the normal quantile covers only about 92% instead of 95% (test_hand_example_mean_ci, test_mean_ci_coverage_normal_small_n).
  • For a proportion near 0 or 1 the Wilson interval stays honest where the textbook p^±zp^(1−p^)/n\hat p \pm z\sqrt{\hat p(1-\hat p)/n} collapses to a point (test_hand_example_wilson, test_wilson_matches_scipy).
  • The percentile bootstrap gives an interval for any statistic, BLEU or a median included, by resampling the data; with one uniform per index it is reproducible across languages (test_bootstrap_matches_independent_resampling, test_bootstrap_draws_and_determinism).
Terminal window
ol start M07.4 # stubs stats.py into your repo
ol tests M07.4 # read the test catalog first
ol check M07.4 # exit code is the verdict
ol diff M07.4 # after passing: your code against the reference

Pass 4 is the first time you compare models. Your LSTM language model (L3.6) will report a bits-per-character number, your seq2seq model (L4.1) a BLEU score (L4.5), and the question is never “what is the number” but “is model A better than model B, or did I get a lucky test set”. A BLEU of 21.3 against 20.8 on 200 sentences means nothing until you know how much BLEU moves when you draw another 200 sentences. From here on every metric the course prints (L4.5, L6.7, the quantization verdicts of L8.5) carries a 95% interval, and the comparison tests of M07.5 build on the same machinery. This module gives you that interval three ways: from a formula for means, from a formula for proportions, and by resampling for everything else.

SymbolMeaningType / shape
X1,…,XnX_1, \dots, X_nindependent draws from one distribution (per-example scores)reals
μ,σ2\mu, \sigma^2the true mean and variance of that distributionreals
Xˉn=1n∑iXi\bar X_n = \frac{1}{n}\sum_i X_ithe sample meanreal
s2=1n−1∑i(Xi−Xˉn)2s^2 = \frac{1}{n-1}\sum_i (X_i - \bar X_n)^2the sample variance (Bessel’s n−1n - 1)real
se=s/n\mathrm{se} = s/\sqrt{n}the standard error: estimated sd of Xˉn\bar X_nreal
α\alphathe miss rate; 1−α1 - \alpha is the confidence level (0.95)real in (0,1)(0, 1)
Φ(z)\Phi(z), Φ−1(p)\Phi^{-1}(p)the standard normal CDF and its quantilefunctions
tνt_\nu, Fν(t)F_\nu(t), Fν−1(p)F_\nu^{-1}(p)Student’s t with ν\nu degrees of freedom, its CDF and quantilefunctions
k,n,p^=k/nk, n, \hat p = k/nsuccesses, trials, observed proportionintegers, real
BB, θb∗\theta^*_bnumber of bootstrap resamples, the statistic on resample bbinteger, reals

The law of large numbers. By linearity E[Xˉn]=μE[\bar X_n] = \mu, and because independent variances add, Var⁡(Xˉn)=nσ2/n2=σ2/n\operatorname{Var}(\bar X_n) = n\sigma^2/n^2 = \sigma^2/n (S-M07b q3 with all covariances 0). Chebyshev’s inequality then gives P(∣Xˉn−μ∣≥ε)≤σ2/(nε2)→0P(|\bar X_n - \mu| \ge \varepsilon) \le \sigma^2/(n\varepsilon^2) \to 0: the mean of more data is closer to the truth (you prove this in S-M07c q5). It also gives the rate: the typical error is σ/n\sigma/\sqrt{n}, so four times the eval set halves the error, it does not quarter it.

The central limit theorem. Whatever the distribution of each XiX_i (finite variance is all it needs), the standardized mean n(Xˉn−μ)/σ\sqrt{n}(\bar X_n - \mu)/\sigma tends to the standard normal N(0,1)\mathcal{N}(0, 1). So P(∣Xˉn−μ∣≤z σ/n)≈2Φ(z)−1P(|\bar X_n - \mu| \le z\,\sigma/\sqrt{n}) \approx 2\Phi(z) - 1, and choosing z=Φ−1(1−α/2)z = \Phi^{-1}(1 - \alpha/2) (1.96 for 95%) turns that into an interval: Xˉn±z σ/n\bar X_n \pm z\,\sigma/\sqrt{n} covers μ\mu in about 1−α1 - \alpha of repeated samples. Per-example eval scores are never normal (0/1 accuracy, skewed losses), and the interval still works once nn is in the hundreds: that is the CLT.

Quantiles from first principles. Φ(z)=12erfc⁡(−z/2)\Phi(z) = \frac{1}{2}\operatorname{erfc}(-z/\sqrt{2}), with math.erfc (it keeps full relative precision in the lower tail, where 1−Φ1 - \Phi computed by subtraction would lose every digit). The quantile Φ−1(p)\Phi^{-1}(p) has no closed form, but Φ\Phi is increasing, so bisection finds it: keep a bracket [l,h][l, h] with Φ(l)<p≤Φ(h)\Phi(l) < p \le \Phi(h) and halve it until the midpoint equals an end, which is the last bit of a double. For p>1/2p > 1/2 use the symmetry Φ−1(p)=−Φ−1(1−p)\Phi^{-1}(p) = -\Phi^{-1}(1 - p); 1−p1 - p is exact in float64 there.

Student’s t. The interval above needs σ\sigma, and you only have ss, computed from the same data. The ratio T=(Xˉn−μ)/(s/n)T = (\bar X_n - \mu)/(s/\sqrt{n}) is wider-tailed than the normal (when ss happens to be small, TT is large), and for normal data it follows Student’s t with ν=n−1\nu = n - 1 degrees of freedom exactly (Gosset, 1908). So the interval is

Xˉn±Fn−1−1(1−α/2) sn.\bar X_n \pm F_{n-1}^{-1}(1 - \alpha/2)\,\frac{s}{\sqrt{n}}.

Two details carry it: the n−1n - 1 in s2s^2 (the deviations are measured from Xˉn\bar X_n, which sits closer to the data than μ\mu does, so dividing by nn underestimates σ2\sigma^2), and α/2\alpha/2 (the interval misses on both sides). For integer ν\nu the CDF has a closed form (Abramowitz and Stegun 26.7.3 and 26.7.4). With θ=arctan⁡(∣t∣/ν)\theta = \arctan(|t|/\sqrt{\nu}) and A=P(∣T∣<∣t∣)A = P(|T| < |t|):

  • ν\nu even: A=sin⁡θ (1+12cos⁡2θ+1⋅32⋅4cos⁡4θ+⋯+1⋅3⋯(ν−3)2⋅4⋯(ν−2)cos⁡ν−2θ)A = \sin\theta\,\bigl(1 + \tfrac{1}{2}\cos^2\theta + \tfrac{1 \cdot 3}{2 \cdot 4}\cos^4\theta + \dots + \tfrac{1 \cdot 3 \cdots (\nu-3)}{2 \cdot 4 \cdots (\nu-2)}\cos^{\nu-2}\theta\bigr),
  • ν\nu odd: A=2π(θ+sin⁡θ (cos⁡θ+23cos⁡3θ+⋯+2⋅4⋯(ν−3)1⋅3⋯(ν−2)cos⁡ν−2θ))A = \tfrac{2}{\pi}\bigl(\theta + \sin\theta\,(\cos\theta + \tfrac{2}{3}\cos^3\theta + \dots + \tfrac{2 \cdot 4 \cdots (\nu-3)}{1 \cdot 3 \cdots (\nu-2)}\cos^{\nu-2}\theta)\bigr), just 2θ/π2\theta/\pi for ν=1\nu = 1,

and Fν(t)=12+12sign⁡(t) AF_\nu(t) = \tfrac{1}{2} + \tfrac{1}{2}\operatorname{sign}(t)\,A. Each term is the previous one times a ratio (2k−12kcos⁡2θ\tfrac{2k-1}{2k}\cos^2\theta or 2k2k+1cos⁡2θ\tfrac{2k}{2k+1}\cos^2\theta), so a cumulative product computes the series in O(ν)O(\nu). The quantile is bisection again, on a bracket [0,b][0, b] whose top doubles until Fν(b)≥pF_\nu(b) \ge p (ν=1\nu = 1 needs t=636.6t = 636.6 at p=0.9995p = 0.9995, so no fixed bound is safe). As ν\nu grows, tνt_\nu tends to the normal: F1000−1(0.975)=1.9623F_{1000}^{-1}(0.975) = 1.9623 against 1.96001.9600.

Proportions: the Wilson interval. An accuracy is a mean of 0/1 values, but the formula p^±zp^(1−p^)/n\hat p \pm z\sqrt{\hat p(1-\hat p)/n} (the Wald interval) fails exactly where safety evals live: with 0 failures in 10 trials it says [0,0][0, 0], certainty. Wilson (1927) inverts the test instead: the interval is every pp with ∣p^−p∣≤zp(1−p)/n|\hat p - p| \le z\sqrt{p(1-p)/n}. Squaring and solving the quadratic in pp gives

p^+z22n±zp^(1−p^)n+z24n21+z2n,\frac{\hat p + \frac{z^2}{2n} \pm z\sqrt{\frac{\hat p(1-\hat p)}{n} + \frac{z^2}{4n^2}}}{1 + \frac{z^2}{n}},

whose center is pulled toward 1/21/2 and which is never empty. At k=0k = 0 its lower end is exactly 0 and at k=nk = n its upper end exactly 1; rounding can leave them a hair off, so the code pins them.

The bootstrap. For a statistic with no variance formula (BLEU, chrF, a median, pass@k), Efron’s idea (1979) is to let the data stand in for the population: draw nn indices with replacement, recompute the statistic on that resample, repeat BB times, and read the spread of θ1∗,…,θB∗\theta^*_1, \dots, \theta^*_B. The percentile interval is their α/2\alpha/2 and 1−α/21 - \alpha/2 quantiles. Two rules make it reproducible across languages (ag.12 re-implements it in Go): each index is i=min⁡(⌊un⌋,n−1)i = \min(\lfloor u n \rfloor, n - 1) from exactly one rng.uniform(), drawn in order (resample 0’s indices first); and the quantile is type 7: sort, h=(B−1)qh = (B-1)q, i=⌊h⌋i = \lfloor h \rfloor, value si+(h−i)(si+1−si)s_i + (h - i)(s_{i+1} - s_i) (numpy’s default). The point estimate stays θ(x)\theta(x) on the original data.

A t interval. x=[2,4,4,5,7,8]x = [2, 4, 4, 5, 7, 8], n=6n = 6.

  1. Mean: 30/6=530/6 = 5.
  2. Deviations: −3,−1,−1,0,2,3-3, -1, -1, 0, 2, 3; squares sum to 9+1+1+0+4+9=249 + 1 + 1 + 0 + 4 + 9 = 24.
  3. s2=24/(6−1)=4.8s^2 = 24/(6 - 1) = 4.8, s=2.19089s = 2.19089; se=4.8/6=0.8=0.894427\mathrm{se} = \sqrt{4.8/6} = \sqrt{0.8} = 0.894427.
  4. ν=5\nu = 5, 1−α/2=0.9751 - \alpha/2 = 0.975: F5−1(0.975)=2.570582F_5^{-1}(0.975) = 2.570582 (bisection on the odd series, which for ν=5\nu = 5 is θ+sin⁡θ(cos⁡θ+23cos⁡3θ)\theta + \sin\theta(\cos\theta + \frac{2}{3}\cos^3\theta)).
  5. Half-width 2.570582×0.894427=2.2991982.570582 \times 0.894427 = 2.299198; interval [2.700802,7.299198][2.700802, 7.299198].

With the normal 1.959964 instead, the interval would be [3.246955,6.753045][3.246955, 6.753045], a quarter narrower: at n=6n = 6 that is a large overstatement of certainty.

One value of the series. F3(1)F_3(1): θ=arctan⁡(1/3)=π/6\theta = \arctan(1/\sqrt{3}) = \pi/6, sin⁡θ=1/2\sin\theta = 1/2, cos⁡θ=3/2\cos\theta = \sqrt{3}/2. The odd series with ν=3\nu = 3 stops after cos⁡θ\cos\theta: A=2π(π6+12⋅32)=13+32π=0.608998A = \frac{2}{\pi}(\frac{\pi}{6} + \frac{1}{2}\cdot\frac{\sqrt{3}}{2}) = \frac{1}{3} + \frac{\sqrt{3}}{2\pi} = 0.608998, so F3(1)=12+12A=0.804499F_3(1) = \frac{1}{2} + \frac{1}{2}A = 0.804499.

A Wilson interval. k=0k = 0, n=10n = 10, z=1.959964z = 1.959964, z2=3.841459z^2 = 3.841459: 1+z2/n=1.3841461 + z^2/n = 1.384146, center =(0+0.192073)/1.384146=0.138766= (0 + 0.192073)/1.384146 = 0.138766, half-width =1.959964×0+3.841459/400/1.384146=0.138766= 1.959964 \times \sqrt{0 + 3.841459/400}/1.384146 = 0.138766. The interval is [0,0.277533][0, 0.277533]; the Wald interval is [0,0][0, 0].

These are test_hand_example_mean_ci and test_hand_example_wilson.

python/tinyllm/prob/stats.py
def normal_cdf(z: float) -> float
def normal_ppf(p: float) -> float # bisection, symmetric for p > 1/2
def t_cdf(t: float, df: int) -> float # A&S 26.7.3 / 26.7.4
def t_ppf(p: float, df: int) -> float # bracket doubled, then bisection
def standard_error(x) -> float # s / sqrt(n), divisor n - 1
def mean_ci(x, alpha: float = 0.05) -> tuple[float, float, float] # (mean, lo, hi)
def wilson_interval(k: int, n: int, alpha: float = 0.05) -> tuple[float, float]
def quantile(x, q: float) -> float # type 7
def bootstrap_ci(x, stat, n_boot: int, alpha: float, rng) -> tuple[float, float, float] # (stat(x), lo, hi)
TestKINDChecksWhy it matters downstream
test_hand_example_mean_ciunitsection 3: se =0.8= \sqrt{0.8}, interval [2.700802,7.299198][2.700802, 7.299198], F3(1)=0.804499F_3(1) = 0.804499you and the test agree on Bessel, α/2\alpha/2, and tt
test_hand_example_wilsonunitsection 3: [0,0.277533][0, 0.277533] at k=0k = 0safety failure rates (ethics.04)
test_normal_matches_scipygoldenΦ\Phi and Φ−1\Phi^{-1} to 1e-11 relative, tails to p=10−12p = 10^{-12}every zz in the course
test_t_matches_scipygoldenFνF_\nu and Fν−1F_\nu^{-1} for 13 values of ν\nu from 1 to 1000small eval sets get the right width
test_mean_ci_matches_scipygoldenstandard_error and mean_ci equal scipy.stats.sem and t.intervalL6.7 reports
test_wilson_matches_scipygoldenscipy’s Wilson interval at the extremes and in the middleaccuracy CIs
test_quantile_matches_numpy_type7goldentype 7 quantilesGo’s bootstrap (ag.12) agrees
test_bootstrap_matches_independent_resamplinggoldensame seed, same interval as an independent implementation, mean and medianreproducible CIs for BLEU (L4.5)
test_mean_ci_coverage_normal_small_nstatistical2000 samples of n=10n = 10: coverage in [0.93,0.97][0.93, 0.97]what “95%” promises
test_mean_ci_coverage_skewed_data_by_cltstatisticalexponential data, n=200n = 200: coverage in [0.93,0.97][0.93, 0.97]the CLT on non-normal scores
test_standard_error_shrinks_like_root_npropertyfour copies of the data halve the standard errorsizing eval sets
test_ppf_inverts_cdf_and_t_tends_to_normalpropertyF(F−1(p))=pF(F^{-1}(p)) = p, symmetry, tν→t_\nu \to normalthe quantiles are consistent
test_t_closed_formsunitν=1\nu = 1 (Cauchy) and ν=2\nu = 2 closed formsboth series are right at their shortest
test_bootstrap_draws_and_determinismunitBnB n uniforms; same seed same result; point is θ(x)\theta(x)cross-language reproducibility
test_bootstrap_index_ruleboundaryu=0.74u = 0.74, n=4n = 4 is index 2, u→1u \to 1 is the last indexthe floor rule
test_bootstrap_shift_equivariancepropertyx+cx + c moves the mean’s interval by ccresampling does not look at values
test_quantile_edgesboundaryq=0q = 0, q=1q = 1, one value, unsorted inputno index past the end
test_wilson_stays_inside_zero_oneboundaryexact 0 and 1 at the ends; p^\hat p insideintervals stay probabilities
test_rejects_bad_argumentsboundaryn<2n < 2, α∉(0,1)\alpha \notin (0,1), k>nk > n, NaN, empty inputerrors at the call site
PitfallSymptomCaught by
1. the normal quantile with an estimated σ\sigmaat n=10n = 10 the “95%” interval covers 92%test_hand_example_mean_ci, test_mean_ci_coverage_normal_small_n (mutant s01)
2. dividing by nn instead of n−1n - 1, or by nn instead of n\sqrt{n}intervals too narrow, by a little or by a lottest_hand_example_mean_ci, test_standard_error_shrinks_like_root_n (mutants s02, s11)
3. the quantile at 1−α1 - \alpha instead of 1−α/21 - \alpha/2a 90% interval labelled 95%test_hand_example_mean_ci (mutant s03)
4. the Wald interval for a proportion, or unpinned ends[0,0][0, 0] after zero failures; ends a hair outside [0,1][0, 1]test_hand_example_wilson, test_wilson_stays_inside_zero_one (mutants s04, s13)
5. rounding unu n to pick a bootstrap indexthe last row drawn twice as often as the first; Go and Python disagreetest_bootstrap_index_rule (mutant s05)
6. drawing one resample and reusing itevery replicate identical: a zero-width intervaltest_bootstrap_matches_independent_resampling (mutant s06)

| Forward | L12.3 | Registered module relationship. | | Forward | ethics.04 | Registered call site uses this module. |

DirectionModuleHow it uses this
BackM07.1one uniform per draw, the same discipline as the bootstrap’s index rule
BackM07.0expectation, variance, the normal distribution
BackS-M07bthe variance of a sum, which is why the standard error is σ/n\sigma/\sqrt{n}
ForwardS-M07cthe LLN proof, the CLT approximation, and these intervals by hand
ForwardL4.5BLEU and chrF with bootstrap intervals over sentences
ForwardL6.7every evaluation metric carries mean_ci or bootstrap_ci
ForwardL8.5quantization verdicts: is the perplexity change inside the noise
ForwardM07.5permutation tests and McNemar build on the same resampling
Forwardag.12the Go eval harness re-implements bootstrap_ci from the same seed

If you skip this module, ol check L6.7 stops with L6.7 needs M07.4: build it, or rerun with --ref-deps.

Your pieceProduction equivalentWhat it addsWhere to look
t_ppf, normal_ppfscipy’s stdtrit and ndtri (Cephes)rational approximations plus Newton steps, O(1)O(1) for any ν\nuscipy/special/cephes/stdtr.c, ndtri.c
bootstrap_ci (percentile)scipy.stats.bootstrap (BCa)bias-corrected and accelerated intervals, vectorized resamplingscipy/stats/_resampling.py
wilson_intervalstatsmodels.stats.proportion.proportion_confintAgresti-Coull, Jeffreys, Clopper-Pearson side by sidestatsmodels/stats/proportion.py
interval on eval scoresHELM and lm-evaluation-harness stderrclustered standard errors when examples share a promptlm_eval/api/metrics.py (bootstrap_stderr)