Take any population — skewed, lumpy, weird. Draw samples of size n and compute each sample's mean. The Central Limit Theorem says the distribution of those means approaches a normal distribution as n grows, regardless of the population's shape. That single fact is why confidence intervals, t-tests, and A/B testing work at all.
| Concept | What it says | Practical use |
|---|---|---|
| Law of Large Numbers | x̄ → μ as n grows | More data → estimates converge |
| Central Limit Theorem | x̄ becomes normally distributed | Justifies CIs, t-tests, z-tests |
| Standard error | Spread of x̄ is σ/√n | Sizing experiments, error bars |
pip install numpy matplotlib
# ────────────────────────────────────────
import numpy as np
import matplotlib.pyplot as plt
population = np.random.exponential(1.0, 1_000_000)
fig, axes = plt.subplots(1, 3, figsize=(12, 3), sharex=True)
for ax, n in zip(axes, [1, 5, 30]):
idx = np.random.randint(0, len(population), (10_000, n))
ax.hist(population[idx].mean(axis=1), bins=60)
ax.set_title(f"sample means, n = {n}")
plt.tight_layout(); plt.show()
Means of exponential samples: the skewness is 1.99 for single values, 0.87 for means of 5 and 0.35 for means of 30 — falling as 2/√n, the CLT at work. Cauchy data never get there: the spread (interquartile range) of a single draw is 2.10, and of the mean of 1,000 draws still 2.06. Without a finite variance, averaging does not help at all.
- “n = 30 is enough” is a rule of thumb that fails for strongly skewed or heavy-tailed data.
- The CLT is about the mean; medians, maxima and ratios converge differently, or not at all.
- Dependent observations converge much more slowly than independent ones.
from scipy.stats import skew
rng = np.random.default_rng(20)
skew_of_mean = {n: round(skew(rng.exponential(1, (20_000, n)).mean(axis=1)), 2) for n in (1, 5, 30)}
cauchy = rng.standard_cauchy((2_000, 1_000))
spread = (round(np.subtract(*np.percentile(cauchy[:, 0], [75, 25])), 2), # one draw
round(np.subtract(*np.percentile(cauchy.mean(axis=1), [75, 25])), 2)) # mean of 1,000Assumes import numpy as np and import pandas as pd (and SciPy where imported). Each snippet simulates or makes up its own data, as the comments say.
The same idea with a library
# Python — watch the CLT happen import numpy as np import matplotlib.pyplot as plt # A very non-normal population (exponential) population = np.random.exponential(scale=1.0, size=1_000_000) for n in [1, 5, 30]: means = [np.mean(np.random.choice(population, n)) for _ in range(10_000)] plt.hist(means, bins=60, alpha=0.5, label=f"n={n}") plt.legend(); plt.title("Sampling distribution of the mean") plt.show() # Standard error shrinks as 1/sqrt(n) print(population.std() / np.sqrt(30))
- An Introduction to Probability Theory and Its Applications, Vol. 2 (2nd ed.), W. Feller, Wiley, 1971
- Introduction to Robust Estimation and Hypothesis Testing (4th ed.), R. R. Wilcox, Academic Press, 2017
Last reviewed October 2026 · How this site checks its content · Report a mistake
- LLM EngineeringContext WindowsHow much text can a model see at once — and how to push the limits
- Market PsychologyMean Reversion PsychologyExtremes revert to the mean — in statistics AND in psychology.
- Market PsychologySocial ProofUsing others' behavior as evidence of the "correct" action (Cialdini).
- Risk & PortfolioDiversificationThe only free lunch in finance — spread risk across uncorrelated bets
- ML MathCross-ValidationReliable model evaluation by rotating which data is used for testing
- ML MathDiffusion ModelsGenerating by learning to reverse a noise process (DDPM, Stable Diffusion)
- The ToolkitChoosing the Right Statistical Testt-test, chi-square, ANOVA and their nonparametric cousins — a decision map