1標本t検定の理論と実装 — 母平均の検定

ある飲料メーカーが「内容量500ml」と表示した商品を販売しています。消費者団体が20本をランダムに抽出して測定したところ、平均内容量は $\bar{x} = 497.3$ ml、標本標準偏差は $s = 4.2$ ml でした。

この結果から「表示の500mlより少ないのではないか」と主張できるでしょうか。20本のうちたまたま少ない瓶が多かっただけかもしれません。母集団の真の平均内容量が500mlかどうかを統計的に検定する方法が1標本t検定(one-sample t-test)です。

1標本t検定は、ウィリアム・シーリー・ゴセットが「Student」のペンネームで1908年に発表したStudent’s t分布に基づく検定で、小標本でも母分散が未知でも適用できるのが特徴です。

1標本t検定を理解すると、以下のような場面で活用できます。

  • 品質管理: 製品の規格値からの逸脱の検定
  • 臨床試験: 治療前後の差の検定(対応ありの場合)
  • 教育: テストスコアが基準値と異なるかの検定
  • 環境科学: 汚染物質の濃度が基準値を超えているかの検定

本記事の内容

  • 1標本t検定の仮説設定
  • 検定統計量の導出とt分布への従属性
  • 両側検定と片側検定
  • 信頼区間との双対性
  • 正規性の仮定と頑健性
  • Pythonでの実装と可視化

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

1標本t検定の設定

問題設定

$X_1, X_2, \ldots, X_n$ が正規母集団 $N(\mu, \sigma^2)$ からの独立な標本とします。$\mu$ と $\sigma^2$ はともに未知です。

母平均 $\mu$ について仮説を検定したい。検定したい値を $\mu_0$ とします。

仮説の設定

両側検定: $H_0: \mu = \mu_0$ vs $H_1: \mu \neq \mu_0$

片側検定(上側): $H_0: \mu \leq \mu_0$ vs $H_1: \mu > \mu_0$

片側検定(下側): $H_0: \mu \geq \mu_0$ vs $H_1: \mu < \mu_0$

冒頭の飲料の例では、「少ないのではないか」を検定したいので、$H_0: \mu = 500$ vs $H_1: \mu < 500$ の片側検定になります。

なぜZ検定ではなくt検定を使うのでしょうか。次にその理由を理論的に見ていきましょう。

検定統計量の導出

Z検定からt検定へ

もし母分散 $\sigma^2$ が既知なら、$Z = \frac{\bar{X} – \mu_0}{\sigma/\sqrt{n}} \sim N(0,1)$ を使うZ検定が可能です。

しかし実際には $\sigma^2$ は未知であるため、標本標準偏差 $S = \sqrt{\frac{1}{n-1}\sum_{i=1}^n(X_i – \bar{X})^2}$ で置き換えます。

t統計量

$$ \begin{equation} T = \frac{\bar{X} – \mu_0}{S / \sqrt{n}} \end{equation} $$

$H_0: \mu = \mu_0$ のもとで、$T \sim t_{n-1}$ です。

この結果を導出しましょう。正規分布からのiid標本に対して、

$$ \bar{X} \sim N\left(\mu, \frac{\sigma^2}{n}\right), \quad \frac{(n-1)S^2}{\sigma^2} \sim \chi^2_{n-1} $$

であり、$\bar{X}$ と $S^2$ は独立です(正規分布の重要な性質)。

$\mu = \mu_0$ のもとで $Z = \frac{\bar{X} – \mu_0}{\sigma/\sqrt{n}} \sim N(0,1)$ と $V = \frac{(n-1)S^2}{\sigma^2} \sim \chi^2_{n-1}$ を用いると、

$$ T = \frac{Z}{\sqrt{V/(n-1)}} = \frac{(\bar{X} – \mu_0)/(\sigma/\sqrt{n})}{\sqrt{S^2/\sigma^2}} = \frac{\bar{X} – \mu_0}{S/\sqrt{n}} $$

これはt分布の定義そのものであり、$T \sim t_{n-1}$ が示されました。

t分布は正規分布より裾が重いため、Z検定より広い棄却域を設定することになります。これは $\sigma$ の推定の不確実性を補正するための代償です。$n$ が大きくなると $t_{n-1} \to N(0,1)$ なので、t検定はZ検定に近づきます。

棄却域

有意水準 $\alpha$ のもとで、

両側検定: $|T| > t_{\alpha/2, n-1}$ のとき $H_0$ を棄却

片側検定(上側): $T > t_{\alpha, n-1}$ のとき $H_0$ を棄却

片側検定(下側): $T < -t_{\alpha, n-1}$ のとき $H_0$ を棄却

ここで $t_{\alpha, n-1}$ は自由度 $n-1$ のt分布の上側 $\alpha$ 点です。

棄却域が定まったところで、信頼区間との関係を見ていきましょう。

信頼区間との双対性

検定と信頼区間の一対一対応

1標本t検定と信頼区間には美しい双対性があります。

95%信頼区間:

$$ \bar{X} \pm t_{0.025, n-1} \cdot \frac{S}{\sqrt{n}} $$

両側5%t検定: $\mu_0$ が95%信頼区間に含まれるなら $H_0$ を棄却しない。含まれないなら棄却する。

つまり、「$H_0: \mu = \mu_0$ を有意水準 $\alpha$ で棄却する」ことと「$\mu_0$ が $100(1-\alpha)\%$ 信頼区間に含まれない」ことは同値です。

この双対性は、検定と区間推定が同一の情報に基づく2つの表現であることを示しています。実践上も、p値だけでなく信頼区間を合わせて報告することが推奨されます。

次に、t検定が正規分布の仮定に依存している点について、その頑健性を確認しましょう。

正規性の仮定と頑健性

正規性の仮定

1標本t検定は $X_i \sim N(\mu, \sigma^2)$ を仮定しています。しかし現実のデータは必ずしも正規分布には従いません。

中心極限定理による頑健性

中心極限定理により、$n$ が十分大きければ $\bar{X}$ の分布は母集団分布によらずほぼ正規分布に従います。そのため、t検定はサンプルサイズが大きければ(目安として $n \geq 30$)正規性の仮定からの逸脱に対して頑健です。

注意が必要な場合

以下の場合はt検定の信頼性が低下する可能性があります。

  • 小標本($n < 15$)かつ強い歪み: 歪んだ分布では小標本でのt検定の第一種の過誤率が公称値からずれる
  • 外れ値の存在: t検定は平均に基づくため、外れ値の影響を受けやすい
  • 重い裾の分布: コーシー分布のように分散が存在しない分布

こうした場合はウィルコクソンの符号順位検定などのノンパラメトリック検定を検討すべきです。

Pythonで1標本t検定を実装し、正規性の仮定の影響を確認しましょう。

Pythonでの実装と可視化

t検定の基本的な実装

1標本t検定を手動で実装し、SciPyの結果と一致することを確認します。

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(42)

# データ生成(真の平均 = 498、表示は500)
mu_true = 498
mu_0 = 500
sigma = 4
n = 20
sample = np.random.normal(mu_true, sigma, n)

# 手動計算
xbar = np.mean(sample)
s = np.std(sample, ddof=1)
se = s / np.sqrt(n)
t_stat = (xbar - mu_0) / se
df = n - 1

# p値(両側)
p_value_two = 2 * stats.t.cdf(t_stat, df) if t_stat < 0 else 2 * (1 - stats.t.cdf(t_stat, df))
# p値(片側: μ < μ₀)
p_value_one = stats.t.cdf(t_stat, df)

# SciPyで検証
t_scipy, p_scipy = stats.ttest_1samp(sample, mu_0)

print(f"標本平均: {xbar:.3f}")
print(f"標本標準偏差: {s:.3f}")
print(f"t統計量: {t_stat:.4f} (scipy: {t_scipy:.4f})")
print(f"p値(両側): {p_value_two:.4f} (scipy: {p_scipy:.4f})")
print(f"p値(片側): {p_value_one:.4f}")

# 信頼区間
t_crit = stats.t.ppf(0.975, df)
ci_lower = xbar - t_crit * se
ci_upper = xbar + t_crit * se
print(f"95%信頼区間: [{ci_lower:.3f}, {ci_upper:.3f}]")

fig, axes = plt.subplots(1, 3, figsize=(16, 5))

# (a) データとt検定の結果
ax = axes[0]
ax.hist(sample, bins=10, density=True, color='lightblue',
        edgecolor='gray', linewidth=0.5, alpha=0.7)
ax.axvline(xbar, color='blue', linewidth=2, label=f'$\\bar{{x}} = {xbar:.2f}$')
ax.axvline(mu_0, color='red', linewidth=2, linestyle='--',
           label=f'$\\mu_0 = {mu_0}$')
ax.axvspan(ci_lower, ci_upper, alpha=0.15, color='green',
           label=f'95% CI: [{ci_lower:.1f}, {ci_upper:.1f}]')

# 正規分布のフィット
x_grid = np.linspace(sample.min() - 2, sample.max() + 2, 100)
ax.plot(x_grid, stats.norm.pdf(x_grid, xbar, s), 'b-', linewidth=1.5, alpha=0.5)

ax.set_xlabel('Volume (ml)', fontsize=12)
ax.set_ylabel('Density', fontsize=12)
ax.set_title(f'One-sample t-test (n={n})', fontsize=13)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)

# (b) t分布とp値
ax = axes[1]
t_grid = np.linspace(-5, 5, 500)
t_pdf = stats.t.pdf(t_grid, df)
ax.plot(t_grid, t_pdf, 'b-', linewidth=2, label=f'$t_{{{df}}}$')

# p値の可視化(片側)
ax.fill_between(t_grid, t_pdf, where=(t_grid <= t_stat), alpha=0.4,
                color='red', label=f'p-value (one-sided) = {p_value_one:.4f}')
ax.axvline(t_stat, color='green', linewidth=2, linestyle='--',
           label=f'$t_{{obs}} = {t_stat:.3f}$')

# 棄却域
t_crit_one = stats.t.ppf(0.05, df)
ax.axvline(t_crit_one, color='black', linewidth=1.5, linestyle=':',
           label=f'Critical value = {t_crit_one:.3f}')

ax.set_xlabel('t statistic', fontsize=12)
ax.set_ylabel('Density', fontsize=12)
ax.set_title(f'$H_0: \\mu = {mu_0}$ vs $H_1: \\mu < {mu_0}$', fontsize=13)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)

# (c) 検定と信頼区間の双対性
ax = axes[2]
mu0_range = np.linspace(495, 502, 100)
p_values_range = []
for m0 in mu0_range:
    t_val = (xbar - m0) / se
    p_val = 2 * (1 - stats.t.cdf(abs(t_val), df))
    p_values_range.append(p_val)

ax.plot(mu0_range, p_values_range, 'b-', linewidth=2)
ax.axhline(0.05, color='red', linewidth=1.5, linestyle='--', label='$\\alpha = 0.05$')
ax.axvspan(ci_lower, ci_upper, alpha=0.15, color='green', label='95% CI')
ax.axvline(xbar, color='blue', linewidth=1.5, linestyle=':', alpha=0.5)
ax.set_xlabel('$\\mu_0$ (hypothesized mean)', fontsize=12)
ax.set_ylabel('p-value (two-sided)', fontsize=12)
ax.set_title('Duality: CI and Hypothesis Test', fontsize=13)
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('one_sample_t_test.png', dpi=150, bbox_inches='tight')
plt.show()

このグラフから1標本t検定の全体像がわかります。

  1. 左図: データのヒストグラムと $\mu_0 = 500$ の位置関係、95%信頼区間が示されています。信頼区間に $\mu_0 = 500$ が含まれるかどうかが検定結果と対応します

  2. 中央図: t分布上でのp値の可視化です。$t_{\text{obs}}$ が棄却域(臨界値の左側)に入れば $H_0$ を棄却します。赤い塗りつぶしの面積がp値です

  3. 右図: 信頼区間と仮説検定の双対性です。p値が0.05を下回る $\mu_0$ の範囲がちょうど信頼区間の外側に対応しています。このカーブの最大値は $\mu_0 = \bar{x}$ のとき $p = 1$ です

正規性の仮定の頑健性の検証

異なる分布からのデータに対するt検定の第一種の過誤率を確認します。

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(42)

# 異なる分布に対するt検定の第一種の過誤率
n_sim = 20000
alpha = 0.05
n_values = [5, 10, 20, 50, 100]

distributions = {
    'Normal': lambda n: np.random.normal(0, 1, n),
    'Exponential\n(shifted)': lambda n: np.random.exponential(1, n) - 1,
    't(3)\n(heavy tails)': lambda n: np.random.standard_t(3, n),
    'Uniform': lambda n: np.random.uniform(-np.sqrt(3), np.sqrt(3), n),
    'Lognormal\n(shifted)': lambda n: np.random.lognormal(0, 0.5, n) - np.exp(0.125),
}

fig, ax = plt.subplots(figsize=(12, 6))

x_pos = np.arange(len(n_values))
width = 0.15
offset = -width * (len(distributions) - 1) / 2

for i, (name, gen) in enumerate(distributions.items()):
    error_rates = []
    for n in n_values:
        reject_count = 0
        for _ in range(n_sim):
            sample = gen(n)
            _, p_val = stats.ttest_1samp(sample, 0)
            if p_val < alpha:
                reject_count += 1
        error_rates.append(reject_count / n_sim)

    bars = ax.bar(x_pos + offset + i * width, error_rates, width,
                  alpha=0.7, label=name)

ax.axhline(alpha, color='red', linewidth=2, linestyle='--',
           label=f'Nominal $\\alpha = {alpha}$')
ax.axhspan(alpha - 0.01, alpha + 0.01, alpha=0.1, color='red')
ax.set_xlabel('Sample size $n$', fontsize=12)
ax.set_ylabel('Type I error rate', fontsize=12)
ax.set_title('Robustness of t-test to Non-normality', fontsize=13)
ax.set_xticks(x_pos)
ax.set_xticklabels(n_values, fontsize=11)
ax.legend(fontsize=8, ncol=3, loc='upper right')
ax.grid(True, alpha=0.3, axis='y')
ax.set_ylim(0, 0.12)

plt.tight_layout()
plt.savefig('t_test_robustness.png', dpi=150, bbox_inches='tight')
plt.show()

このグラフから、t検定の正規性からの逸脱に対する頑健性が確認できます。

  1. 正規分布と一様分布からのデータに対しては、すべてのサンプルサイズで第一種の過誤率が公称値の5%に近い値を維持しています

  2. 指数分布やt(3)分布のような歪みの大きい分布や裾の重い分布では、$n = 5$ のとき過誤率が公称値からやや逸脱しますが、$n \geq 20$ では概ね5%に収まります

  3. 対数正規分布(右に強く歪んだ分布)は最も影響が大きく、小標本では過誤率が膨張します。このような場合はノンパラメトリック検定の使用が推奨されます

まとめ

本記事では、1標本t検定の理論と実装を解説しました。

  • 1標本t検定は母分散が未知のときに母平均を検定する方法で、検定統計量 $T = (\bar{X} - \mu_0)/(S/\sqrt{n})$ が自由度 $n-1$ のt分布に従う
  • 両側検定と片側検定の使い分けは研究の目的に応じて決める
  • 信頼区間と仮説検定には双対性があり、$\mu_0$ が信頼区間に入るかどうかで検定結果が決まる
  • 中心極限定理のおかげで、t検定は $n \geq 20$ 程度であれば非正規データに対しても頑健
  • 小標本かつ強い歪みがある場合はノンパラメトリック検定を検討すべき

次のステップとして、以下の記事も参考にしてください。