OS-011 · Probability

Central Limit Theorem Simulation

Take n independent draws from almost any distribution and average them. Repeat many times and the averages pile up in a normal curve centered on the true mean with spread σ/√n — even when the source is skewed, lumpy or has two peaks. How large n must be depends on the source, and the lab measures it.

Interactive model

OS-011 · central-limit-theorem · seeded
draws from the sourcetrue density or probabilitieslatest sample and its mean
sample meansnormal N(μ, σ²/n)μ
Parameters
Draws averaged into each sample mean
How many samples of size n are drawn
Same seed, same run — here, in simulation.js and in simulation.py.
Sample means
10,000
Mean of the sample means
0.9970
Source mean μ
1.0000
SD of the sample means
0.4408
Theory σ/√n
0.4472
Skewness of the means
0.877
Theory: source skewness/√n
0.894
Means within μ ± 1.96σ/√n
95.7%

Example run: default parameters, seed 42.

What the theorem says

Let X1, …, Xn be independent draws from a distribution with mean μ and finite variance σ², and let X̄ be their average. Two facts hold for every n, with no approximation:

E[X̄] = μ    SD(X̄) = σ / √n

The central limit theorem adds the shape: as n grows, the distribution of (X̄ − μ)/(σ/√n) approaches the standard normal. The lower chart overlays N(μ, σ²/n) on the histogram of means, and the readout means within μ ± 1.96σ/√n should approach 95%.

The upper chart shows the source itself. The ticks along its bottom are the latest sample and the blue line is that sample’s mean — one more count in the lower histogram. Use Step to draw samples one at a time.

How fast the means become normal

The theorem says nothing about how large n must be. Skewness is a good yardstick, because for a mean of n independent draws it is exactly the source’s skewness divided by √n:

skew(X̄) = skew(X) / √n
  • Uniform, die, bimodal — symmetric, skewness 0. Uniform means look normal by n = 5. The bimodal source is more instructive: means of two draws have three peaks, at −2, 0 and 2, before they merge.
  • Exponential — skewness 2. At n = 30, the size often quoted as “large enough”, the means still have skewness 0.37; getting it down to 0.1 takes n = 400.
  • Bernoulli, p = 0.1 — skewness (1 − 2p)/√(p(1 − p)) ≈ 2.67, and discrete: every mean is a multiple of 1/n, so the histogram bins are aligned to that grid. Even at n = 100 the skewness is 0.27.

The same σ/√n rule sets the accuracy of every Monte Carlo estimate; see how accurate a Monte Carlo estimate is.

How the simulation works

  1. Draw n independent values from the source and average them. Repeat m times.
  2. Compute the mean, standard deviation (dividing by m − 1) and skewness (third central moment over the second to the power 1.5) of the m means, and the share within μ ± 1.96σ/√n.
  3. Histogram the means over μ ± 4σ/√n. For the die and Bernoulli sources every mean is a whole number divided by n, so bin edges fall halfway between those values; otherwise some bins would catch more lattice points than others and the histogram would show false spikes.

Example result

With seed 42, 10,000 means of n = 5 exponential draws averaged 0.9970 (μ = 1.0000) with a standard deviation of 0.4408 against σ/√n = 0.4472. Their skewness was 0.877, close to the theoretical 0.894, so the histogram still has a longer right tail than the normal curve; 95.7% of the means fell inside μ ± 1.96σ/√n.

Output of central-limit-theorem with source=exponential n=5 means=10000 seed=42
Sample means10,000
Mean of the sample means0.9970
Source mean μ1.0000
SD of the sample means0.4408
Theory σ/√n0.4472
Skewness of the means0.877
Theory: source skewness/√n0.894
Means within μ ± 1.96σ/√n95.7%

Implementation

The page runs simulation.js from the package below; the Python file is a line-by-line port with the same random-number generator, so a given seed produces the same result in both languages. The excerpts show the core of each.

JavaScript — simulation.js
// Draw m samples of size n from the source and keep each sample's mean.
// The CLT says the means are approximately N(μ, σ²/n) for large n.
export function sampleMean(source, n, rng, values) {
  let sum = 0;
  for (let j = 0; j < n; j++) {
    const x = draw(source, rng);
    if (values) values[j] = x;
    sum += x;
  }
  return sum / n;
}

// Mean, standard deviation and skewness of the simulated means. Skewness is
// the third central moment over the 1.5th power of the second; the skewness
// of a mean of n independent draws is exactly (source skewness)/√n.
export function moments(xs, count) {
  let sum = 0;
  for (let i = 0; i < count; i++) sum += xs[i];
  const mean = sum / count;
  let m2 = 0, m3 = 0;
  for (let i = 0; i < count; i++) {
    const d = xs[i] - mean;
    m2 += d * d;
    m3 += d * d * d;
  }
  const sd = count > 1 ? Math.sqrt(m2 / (count - 1)) : null;
  m2 /= count;
  m3 /= count;
  return { mean, sd, skew: m2 > 0 ? m3 / (m2 * Math.sqrt(m2)) : null };
}
Python — simulation.py
def sample_mean(source, n, rng):
    """Mean of n independent draws from the source distribution."""
    total = 0
    for _ in range(n):
        total += draw(source, rng)
    return total / n


def moments(xs):
    """Mean, sample standard deviation and skewness m3 / m2**1.5 of xs."""
    count = len(xs)
    mean = sum_in_order(xs) / count
    m2 = m3 = 0
    for x in xs:
        d = x - mean
        m2 += d * d
        m3 += d * d * d
    sd = math.sqrt(m2 / (count - 1)) if count > 1 else None
    m2 /= count
    m3 /= count
    return mean, sd, (m3 / (m2 * math.sqrt(m2)) if m2 > 0 else None)

Run the full model from a terminal:

Shell
node simulation.js --source exponential --n 5 --seed 42
python simulation.py --source exponential --n 5 --seed 42

Download the model

central-limit-theorem.zip6 files · 11.7 KBDownload .zip
Files in central-limit-theorem.zip
FileContentsSize
README.mdHow to run it, parameters, example result2.4 KB
model.jsonParameters with ranges and defaults, outputs, the seed-42 example2.6 KB
simulation.jsThe model used on this page plus a command-line runner (Node 18+)10.7 KB
simulation.pyPython 3.9+ port, standard library only8.3 KB
example.csvResult table from the example run2.8 KB
LICENSE.txtMIT License1.0 KB

Assumptions & limitations

  • Independent draws from a fixed distribution. The theorem needs independence (or weak dependence) and finite variance. Every source here qualifies; a Cauchy distribution does not, and means of Cauchy draws never settle down.
  • Display ranges. The means histogram spans μ ± 4σ/√n and the exponential source is drawn up to 6; values beyond are rare and still count in every statistic.
  • Skewness is noisy. The sample skewness of the means has its own sampling error, so small differences from the theoretical value are expected, especially with few means.
  • About means, not data. The theorem is about the distribution of the average. It does not make the underlying data normal.

References

  1. Grinstead, C. M. & Snell, J. L. (1997). Introduction to Probability, 2nd ed. American Mathematical Society. math.dartmouth.edu/~prob/prob/prob.pdf
  2. Feller, W. (1968). An Introduction to Probability Theory and Its Applications, Vol. 1, 3rd ed. Wiley.
  3. Owen, A. B. (2013). Monte Carlo theory, methods and examples. Stanford University. artowen.su.domains/mc