はじめに
データを集めたとき、まず「平均値」を計算することは多いと思います。
しかし、同じ母集団からデータを取り直すと、平均値は毎回少しずつ変わります。では、何度も標本を取り、そのたびに平均値を計算したとき、その平均値はどのように分布するのでしょうか。
この問いに答えるのが中心極限定理(Central Limit Theorem; CLT)です。
中心極限定理の興味深いところは、元のデータが正規分布でなくても、一定の条件のもとで、標本平均の分布が正規分布に近づくことです。
本記事では、あえて右に大きく歪んだダミーデータを作り、Google Colabで次のことを確認します。
- 母集団、標本、標本平均、標本分布という用語の意味
- 元のデータが正規分布でなくてもよいこと
- 標本サイズ$n$を増やすと、標本平均の分布が正規分布に近づくこと
- 標本平均のばらつきが$\sigma/\sqrt{n}$に従って小さくなること
- 「$n=30$なら必ず大丈夫」という理解が正確ではないこと
コードはGoogle Colabでそのまま実行できます。
TL;DR
中心極限定理の最も基本的な形は、次のようにまとめられます。
独立で同じ分布に従い、平均と分散が有限なデータから標本を取る。標本サイズ$n$を大きくすると、標本平均の分布は、元の分布の形によらず正規分布に近づく。
そのとき、標本平均$\bar{X}$の分布は、おおよそ次のようになります。
$$
\bar{X}
\approx
\mathcal{N}\left(\mu, \frac{\sigma^2}{n}\right)
$$
したがって、標本平均の中心は母平均$\mu$、標準偏差(standard error; SE)は次の値です。
$$
\mathrm{SE}(\bar{X}) = \frac{\sigma}{\sqrt{n}}
$$
まず用語を整理する
母集団
調べたい対象全体、またはデータを生み出す確率分布を母集団と呼びます。
たとえば、ある測定を無限に繰り返したときに得られる値の分布を考えると、その背後にある確率分布が母集団分布です。
母集団の平均を母平均$\mu$、標準偏差を母標準偏差$\sigma$と書きます。
標本
母集団から実際に取り出したデータの集まりを標本と呼びます。
標本に含まれるデータ数を標本サイズと呼び、通常$n$で表します。
たとえば、母集団から5個の値を取り出したなら、標本サイズは$n=5$です。
標本平均
標本$X_1, X_2, \dots, X_n$の平均を標本平均と呼び、$\bar{X}$で表します。
$$
\bar{X}=
\frac{1}{n}
\sum_{i=1}^{n} X_i
$$
標本平均は母平均$\mu$を推定するために使われます。ただし、標本を取り直せば、含まれるデータが変わるため、標本平均も変わります。
標本平均の標本分布
次の操作を何度も繰り返すことを考えます。
- 母集団から$n$個のデータを取る
- その$n$個の平均を計算する
- 得られた標本平均を記録する
こうして集めた多数の標本平均が作る分布を、標本平均の標本分布と呼びます。
ここが本記事の主役です。
元データの分布と、標本平均の分布は別物です。
中心極限定理とは
この記事では、中心極限定理の最も基本的な形を扱います。
$X_1, X_2, \dots, X_n$が、
- 互いに独立である
- 同じ分布に従う
- 母平均$\mu$が有限である
- 母分散$\sigma^2$が有限である
とします。
このとき、標本サイズ$n$を十分大きくすると、標本平均$\bar{X}$の分布は、おおよそ正規分布になります。
$$
\bar{X}
\approx
\mathcal{N}\left(\mu, \frac{\sigma^2}{n}\right)
$$
標準化して書くと、次の形です。
$$
\frac{\bar{X}-\mu}{\sigma/\sqrt{n}}
\approx
\mathcal{N}(0,1)
$$
ここで$\mathcal{N}(0,1)$は、平均0、標準偏差1の標準正規分布です。
中心極限定理から読み取れる重要な点は、次の3つです。
- 標本平均の分布は正規分布に近づく
- 標本平均の分布の中心は母平均$\mu$になる
- 標本平均のばらつきは$\sigma/\sqrt{n}$になる
ただし、少し厳密に言うと、独立な標本では
$$
E[\bar{X}] = \mu
$$
$$
\mathrm{Var}(\bar{X}) = \frac{\sigma^2}{n}
$$
という関係自体は、$n$が大きくなくても成り立ちます。中心極限定理の核心は、標本平均の分布の形が正規分布に近づくことです。
実行環境
Google Colabの新しいノートブックを開き、以下のセルを上から順に実行してください。
使用するライブラリは、Google Colabに標準で用意されているNumPy、Matplotlib、SciPyです。
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
from pathlib import Path
# 再現性のために乱数シードを固定
SEED = 20260624
# 「標本を取り、平均を計算する」操作の反復回数
N_REPEATS = 20_000
# Colabではカレントディレクトリが通常 /content になる
OUTPUT_DIR = Path(".")
plt.rcParams.update({
"figure.dpi": 120,
"savefig.dpi": 180,
})
実験1:正規分布ではない母集団を作る
中心極限定理を確認するために、最初から正規分布を使ってしまうと面白くありません。
そこで今回は、指数分布からダミーデータを生成します。
尺度パラメータを1とした指数分布の確率密度関数は、$x \ge 0$で次のようになります。
$$
f(x)=e^{-x}
$$
この分布の母平均と母標準偏差は、どちらも1です。
$$
\mu = 1, \qquad \sigma = 1
$$
指数分布は右側に長い裾を持つ、強く歪んだ分布です。正規分布とはかなり異なる形なので、中心極限定理を観察するのに向いています。
rng = np.random.default_rng(SEED + 1)
# 母集団分布の形を可視化するためのダミーデータ
population = rng.exponential(scale=1.0, size=100_000)
x = np.linspace(0, 6, 600)
plt.figure(figsize=(9, 5))
plt.hist(
population,
bins=100,
density=True,
alpha=0.65,
label="Dummy data (100,000 values)",
)
plt.plot(
x,
np.exp(-x),
linewidth=2,
label="Theoretical exponential density",
)
plt.axvline(
1.0,
linestyle="--",
linewidth=2,
label="Population mean = 1",
)
plt.xlim(0, 6)
plt.xlabel("Value")
plt.ylabel("Density")
plt.title("Original distribution: strongly right-skewed")
plt.legend()
plt.tight_layout()
filename = OUTPUT_DIR / "fig01_exponential_distribution.png"
plt.savefig(filename, bbox_inches="tight")
plt.show()
print(f"dummy-data mean = {population.mean():.3f}")
print(f"dummy-data SD = {population.std(ddof=0):.3f}")
print(f"saved: {filename.resolve()}")
ヒストグラムは左右対称ではなく、右側に長い裾を持っています。破線は母平均$\mu=1$です。
10万個のダミーデータから計算した平均と標準偏差は、理論値の1に近い値になります。
ここで重要なのは、1個ずつの測定値が作る分布は、正規分布ではないという点です。
実験2:標本平均を2万回作る
次に、指数分布から$n$個の値を取り、その平均を計算します。
この操作を2万回繰り返し、2万個の標本平均を作ります。標本サイズは$n=1, 2, 5, 30$と変化させます。
$n=1$では平均を取っていないのと同じなので、元の指数分布と同じ形になります。$n$を増やしたときに、分布の形がどう変わるかに注目してください。
sample_sizes = [1, 2, 5, 30]
sampling_results = {}
rng = np.random.default_rng(SEED + 2)
fig, axes = plt.subplots(2, 2, figsize=(12, 8), sharex=True)
x_grid = np.linspace(0, 4, 800)
for ax, n in zip(axes.ravel(), sample_sizes):
# 1行が1回の標本に対応する
samples = rng.exponential(
scale=1.0,
size=(N_REPEATS, n),
)
# 各行の平均を取り、2万個の標本平均を作る
sample_means = samples.mean(axis=1)
sampling_results[n] = sample_means
empirical_mean = sample_means.mean()
empirical_sd = sample_means.std(ddof=1)
# 今回は母標準偏差 sigma = 1 なので、標準誤差は 1/sqrt(n)
theoretical_se = 1 / np.sqrt(n)
ax.hist(
sample_means,
bins=80,
density=True,
alpha=0.65,
label="Simulated sample means",
)
# 中心極限定理による正規近似
ax.plot(
x_grid,
norm.pdf(x_grid, loc=1.0, scale=theoretical_se),
linewidth=2,
label="Normal approximation",
)
ax.axvline(
1.0,
linestyle="--",
linewidth=1.5,
label="Population mean",
)
ax.set_xlim(0, 4)
ax.set_title(
f"n = {n}\n"
f"mean = {empirical_mean:.3f}, SD = {empirical_sd:.3f}"
)
ax.set_xlabel("Sample mean")
ax.set_ylabel("Density")
handles, labels = axes[0, 0].get_legend_handles_labels()
fig.legend(
handles,
labels,
loc="upper center",
ncol=3,
bbox_to_anchor=(0.5, 1.02),
)
fig.suptitle(
"Sampling distributions of the mean",
y=1.06,
fontsize=15,
)
fig.tight_layout()
filename = OUTPUT_DIR / "fig02_sampling_distributions.png"
fig.savefig(filename, bbox_inches="tight")
plt.show()
print(" n | empirical mean | empirical SD | theoretical SE")
print("---|----------------|--------------|---------------")
for n in sample_sizes:
means = sampling_results[n]
print(
f"{n:>2} | "
f"{means.mean():>14.3f} | "
f"{means.std(ddof=1):>12.3f} | "
f"{1 / np.sqrt(n):>13.3f}"
)
print(f"saved: {filename.resolve()}")
筆者の実行結果は、次のようになりました。
| $n$ | 標本平均の平均 | 標本平均の標準偏差 | 理論上の標準誤差$1/\sqrt{n}$ |
|---|---|---|---|
| 1 | 1.004 | 0.989 | 1.000 |
| 2 | 1.005 | 0.711 | 0.707 |
| 5 | 0.999 | 0.446 | 0.447 |
| 30 | 1.000 | 0.180 | 0.183 |
乱数を使うため、実行環境によって末尾の値がわずかに異なる可能性があります。ただし、全体の傾向は同じになります。
図から読み取れること
n=1
標本平均は元の値そのものです。そのため、分布は右に大きく歪んだままです。正規近似の曲線とも合っていません。
n=2
まだ右に歪んでいますが、$n=1$より山型に近づきます。
n=5
歪みがかなり小さくなり、正規分布に近い形が見え始めます。
n=30
標本平均の分布はほぼ左右対称になり、重ねた正規分布の曲線とよく一致します。
元の指数分布はまったく左右対称ではありません。それでも、平均を取って標本サイズを大きくすると、標本平均の分布は正規分布に近づきました。
これが中心極限定理の力です。
実験3:平均の中心とばらつきを確認する
中心極限定理と標本平均の性質から、次の2点が期待されます。
$$
E[\bar{X}] = \mu = 1
$$
$$
\mathrm{SD}(\bar{X})=
\frac{\sigma}{\sqrt{n}}=
\frac{1}{\sqrt{n}}
$$
標本サイズを1から100まで変化させ、シミュレーション結果と理論値を比較します。
n_values = np.array([1, 2, 3, 5, 10, 20, 30, 50, 100])
empirical_means = []
empirical_sds = []
rng = np.random.default_rng(SEED + 3)
for n in n_values:
sample_means = rng.exponential(
scale=1.0,
size=(N_REPEATS, n),
).mean(axis=1)
empirical_means.append(sample_means.mean())
empirical_sds.append(sample_means.std(ddof=1))
empirical_means = np.array(empirical_means)
empirical_sds = np.array(empirical_sds)
n_smooth = np.arange(1, 101)
theoretical_se = 1 / np.sqrt(n_smooth)
fig, axes = plt.subplots(1, 2, figsize=(12, 4.8))
# 左:標本平均の中心
axes[0].plot(
n_values,
empirical_means,
marker="o",
label="Simulation",
)
axes[0].axhline(
1.0,
linestyle="--",
linewidth=2,
label="Population mean = 1",
)
axes[0].set_xlabel("Sample size n")
axes[0].set_ylabel("Mean of sample means")
axes[0].set_title("The center stays near the population mean")
axes[0].set_ylim(0.97, 1.03)
axes[0].legend()
# 右:標本平均のばらつき
axes[1].plot(
n_values,
empirical_sds,
marker="o",
label="Simulation",
)
axes[1].plot(
n_smooth,
theoretical_se,
linewidth=2,
label=r"Theory: $1/\sqrt{n}$",
)
axes[1].set_xlabel("Sample size n")
axes[1].set_ylabel("SD of sample means")
axes[1].set_title("The spread shrinks as sample size grows")
axes[1].legend()
fig.tight_layout()
filename = OUTPUT_DIR / "fig03_mean_and_standard_error.png"
fig.savefig(filename, bbox_inches="tight")
plt.show()
print(f"saved: {filename.resolve()}")
左図では、標本サイズが変わっても、標本平均の分布の中心が母平均1の近くにあります。
右図では、シミュレーションで得た標本平均の標準偏差が、理論値$1/\sqrt{n}$とほぼ重なっています。
データ数を2倍にしても、標準誤差は半分にならない
標準誤差は$n$ではなく$\sqrt{n}$に反比例します。
$$
\mathrm{SE}=\frac{\sigma}{\sqrt{n}}
$$
そのため、標準誤差を半分にしたいなら、標本サイズは4倍必要です。
$$
\frac{\sigma}{\sqrt{4n}}=
\frac{1}{2}
\frac{\sigma}{\sqrt{n}}
$$
これは実験計画やデータ収集を考えるときに重要な関係です。
実験4:元の分布が違っても成り立つのか
中心極限定理が強力なのは、元の分布を正規分布に限定しない点です。
次の3種類の母集団分布を用意します。
- 一様分布:平らな分布
- 指数分布:右に大きく歪んだ分布
- 二峰性分布:山が2つある分布
形だけを比較しやすくするため、3つの分布はいずれも母平均1、母標準偏差1になるように設定します。
それぞれから$n=30$個のデータを取り、標本平均を2万回計算します。
def draw_uniform(rng, size):
"""平均1、分散1の一様分布から乱数を生成する。"""
return rng.uniform(
1 - np.sqrt(3),
1 + np.sqrt(3),
size=size,
)
def draw_exponential(rng, size):
"""平均1、分散1の指数分布から乱数を生成する。"""
return rng.exponential(scale=1.0, size=size)
def draw_bimodal(rng, size):
"""平均1、分散1の二峰性分布から乱数を生成する。"""
within_sd = 0.3
offset = np.sqrt(1 - within_sd**2)
# 左右どちらの山から値を取るかを同じ確率で選ぶ
signs = rng.choice([-1.0, 1.0], size=size)
return (
1.0
+ signs * offset
+ rng.normal(0.0, within_sd, size=size)
)
samplers = [
("Uniform", draw_uniform),
("Exponential", draw_exponential),
("Bimodal", draw_bimodal),
]
rng = np.random.default_rng(SEED + 4)
n = 30
n_parent = 100_000
n_repeats = 20_000
fig, axes = plt.subplots(2, 3, figsize=(14, 7.5))
for col, (name, sampler) in enumerate(samplers):
# 上段に表示する元データ
parent = sampler(rng, n_parent)
# 2万組の標本を一度に生成し、それぞれの平均を計算
samples = sampler(rng, (n_repeats, n))
sample_means = samples.mean(axis=1)
# 上段:元の分布
axes[0, col].hist(
parent,
bins=100,
density=True,
alpha=0.7,
)
axes[0, col].axvline(
1.0,
linestyle="--",
linewidth=1.5,
)
axes[0, col].set_title(f"{name}: original distribution")
axes[0, col].set_xlabel("Value")
axes[0, col].set_ylabel("Density")
# 下段:標本平均の分布
axes[1, col].hist(
sample_means,
bins=70,
density=True,
alpha=0.7,
label="Simulation",
)
x_grid = np.linspace(0.2, 1.8, 600)
axes[1, col].plot(
x_grid,
norm.pdf(
x_grid,
loc=1.0,
scale=1 / np.sqrt(n),
),
linewidth=2,
label="Normal approximation",
)
axes[1, col].axvline(
1.0,
linestyle="--",
linewidth=1.5,
)
axes[1, col].set_xlim(0.2, 1.8)
axes[1, col].set_title(f"Sample means (n = {n})")
axes[1, col].set_xlabel("Sample mean")
axes[1, col].set_ylabel("Density")
axes[1, col].legend()
fig.suptitle(
"Different original shapes, similar sampling distributions of the mean",
y=1.02,
fontsize=15,
)
fig.tight_layout()
filename = OUTPUT_DIR / "fig04_different_parent_distributions.png"
fig.savefig(filename, bbox_inches="tight")
plt.show()
print(f"saved: {filename.resolve()}")
上段を見ると、3つの母集団分布の形は大きく異なります。
- 一様分布はほぼ平ら
- 指数分布は右に歪んでいる
- 二峰性分布には2つの山がある
一方、下段の標本平均の分布は、いずれも平均1を中心とする釣鐘型に近づいています。標本平均の標準偏差も、共通して
$$
\frac{1}{\sqrt{30}} \approx 0.183
$$
となります。
元の分布の形が大きく違っても、平均と分散が有限で、独立な標本を十分な数だけ平均すると、標本平均の分布は正規分布に近づきます。
中心極限定理はなぜ強いのか
1. 元データそのものが正規分布でなくてもよい
実際のデータは、左右対称とは限りません。正の値しか取らないデータ、外れ値を含むデータ、山が複数あるデータなど、さまざまな形があります。
中心極限定理は、一定の条件のもとで、元データではなく標本平均の分布を正規分布で近似できることを示します。
2. 平均値の不確かさを定量化できる
標本平均の標準誤差は、母標準偏差$\sigma$が分かっているなら、
$$
\mathrm{SE}(\bar{X})=
\frac{\sigma}{\sqrt{n}}
$$
で表せます。
実際の解析では通常、母標準偏差$\sigma$は未知なので、標本標準偏差$s$を使って
$$
\widehat{\mathrm{SE}}(\bar{X})=
\frac{s}{\sqrt{n}}
$$
と推定します。
この考え方は、平均値の信頼区間や仮説検定を理解する土台になります。
3. 複雑な分布を扱いやすい分布へ橋渡しできる
正規分布は数学的に扱いやすく、多くの統計手法で利用されています。
中心極限定理は、「元の分布が複雑でも、平均の分布は正規分布で近似できる」という橋渡しをします。そのため、統計学の非常に広い範囲で中心的な役割を持ちます。
大数の法則との違い
中心極限定理と混同されやすいものに、大数の法則があります。
大数の法則が説明するのは、標本サイズ$n$を大きくしたとき、標本平均$\bar{X}$が母平均$\mu$に近づくことです。
$$
\bar{X} \to \mu
$$
一方、中心極限定理が説明するのは、標本平均と母平均の差を適切に拡大したとき、その分布が正規分布に近づくことです。
$$
\frac{\bar{X}-\mu}{\sigma/\sqrt{n}}
\approx
\mathcal{N}(0,1)
$$
大まかに言えば、次の違いです。
- 大数の法則:平均値がどこへ近づくか
- 中心極限定理:平均値のずれがどのように分布するか
両者は関連していますが、同じ内容ではありません。
注意点:「n=30なら必ず正規分布」は誤り
入門書では「$n \ge 30$なら中心極限定理を使える」と説明されることがあります。しかし、30は普遍的な境界ではありません。
正規分布への近づき方は、元の分布によって異なります。
- 元の分布が正規分布なら、標本平均は$n$が小さくても正規分布になる
- 強く歪んだ分布や裾の重い分布では、より大きな$n$が必要になることがある
- 平均や分散が有限でない分布には、この記事で扱った基本形の中心極限定理を適用できない
- データ間に強い相関がある場合、独立標本を仮定した単純な式をそのまま使えない
したがって、実データでは$n$だけで機械的に判断せず、元データの分布、外れ値、依存関係、標本抽出方法も確認する必要があります。
よくある誤解
誤解1:標本サイズを増やすと、元データが正規分布になる
正規分布に近づくのは、元データそのものではなく、標本平均の標本分布です。
指数分布から何個データを集めても、個々の値の分布は指数分布のままです。
誤解2:1回だけ計算した平均値を見れば、中心極限定理を確認できる
中心極限定理は標本平均の分布についての定理です。
1回の標本から得られる平均値は1個だけなので、分布の形は見えません。本記事では標本抽出を2万回繰り返すことで、標本平均の標本分布を可視化しました。
誤解3:$n$を4倍にすれば、標準誤差も4分の1になる
標準誤差は$1/n$ではなく、$1/\sqrt{n}$に比例して小さくなります。
$n$を4倍にすると、標準誤差は2分の1です。
まとめ
本記事では、中心極限定理をダミーデータとPythonシミュレーションで確認しました。
- 母集団から標本を取り、その平均を繰り返し計算すると、標本平均の標本分布が得られる
- 元データが右に歪んだ指数分布でも、標本サイズを増やすと標本平均の分布は正規分布に近づく
- 標本平均の分布の中心は母平均$\mu$になる
- 標本平均の標準偏差は$\sigma/\sqrt{n}$になる
- 一様分布、指数分布、二峰性分布でも、条件を満たせば同じ現象が見られる
- 正規分布に十分近づくための$n$は、元の分布によって異なる
- 中心極限定理は「元データ」ではなく「標本平均の分布」についての定理である
中心極限定理が強いのは、複雑な母集団分布と、扱いやすい正規分布との間に橋を架けてくれるからです。
平均値、標準誤差、信頼区間、仮説検定を学ぶとき、この「平均値にも分布がある」という視点が重要になります。
発展課題
コードの一部を変更して、次の実験も試せます。
-
sample_sizes = [1, 2, 5, 30]を変更し、$n=10, 50, 100$を比較する -
N_REPEATSを100、1000、10000と変え、ヒストグラムの滑らかさを比較する - 指数分布の代わりに一様分布やベルヌーイ分布を使う
- 外れ値が出やすい分布を使い、正規分布への近づき方を観察する
- 標準誤差を半分にするために必要な標本サイズを、図から確かめる



