t検定の理論と実装まとめ — 1標本・2標本・対応ありの体系的理解

「この薬は本当に血圧を下げるのか?」「新しい製造ラインの品質は従来と変わらないか?」「同じ学生が講習を受ける前と後でスコアは上がったのか?」 — これらはすべて平均の差を検定する問題であり、最も頻繁に使われる統計手法が t検定 です。

t検定には大きく分けて4つの変種があります。1標本t検定、独立2標本t検定(等分散を仮定する Student’s t検定)、独立2標本t検定(等分散を仮定しないウェルチのt検定)、そして対応のあるt検定です。それぞれの個別記事はすでに公開していますが、いざ実務で「どのt検定を使えばよいのか」と問われたとき、4つの関係や使い分けの基準を俯瞰した知識がなければ判断に迷います。

この記事を読むと、以下のことが体系的に理解できます。

  • 臨床試験: 治療群とプラセボ群、あるいは治療前後の比較で、どのt検定を使うかの判断ができる
  • 品質管理: 規格値との比較、2ライン間の比較など、製造現場で生じる「平均の差」の検定を正しく設計できる
  • A/Bテスト: ウェブサービスやマーケティングにおける2群比較で、等分散性やサンプルサイズの非対称性を考慮した適切な選択ができる

本記事の内容

  • t検定の全体像 — 4つの変種の分類と数学的関係
  • 共通の理論基盤 — t分布の導出と検定統計量の一般構造
  • 各t検定の公式・仮定・自由度の整理
  • 検定の選び方フローチャート
  • Pythonでの統一実装(スクラッチ + scipy.stats との比較)
  • t検定の仮定・頑健性・よくある間違い

前提知識

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

本記事はこれら個別記事のまとめ・統合記事です。各検定の詳細な証明は上記記事に譲り、本記事では4つの変種を横断的に比較し、使い分けの判断基準を明確にすることに重点を置きます。

t検定の全体像 — 4つの変種の分類と関係

なぜ複数の変種があるのか

t検定を初めて学ぶとき、「1標本」「2標本」「等分散」「ウェルチ」「対応あり」と名前が乱立して混乱することがあります。しかし、これらを生み出している分岐条件はたった2つです。

  1. 比較の構造: 1つの集団の平均を基準値と比べるのか(1標本)、2つの集団の平均を比べるのか(2標本)
  2. データの対応: 2標本のデータは独立なのか、同一対象の前後測定のように対応があるのか

さらに独立2標本の中で「2群の母分散が等しいかどうか」という仮定の有無によって Student’s t と Welch’s t に分かれます。これを表にまとめると次のようになります。

変種 比較 データの対応 分散の仮定 自由度
1標本t検定 $\mu$ vs $\mu_0$ $n – 1$
独立2標本 Student’s t $\mu_1$ vs $\mu_2$ 独立 $\sigma_1^2 = \sigma_2^2$ $n_1 + n_2 – 2$
独立2標本 Welch’s t $\mu_1$ vs $\mu_2$ 独立 $\sigma_1^2 \neq \sigma_2^2$ 可 サターウェイト近似 $\nu$
対応のあるt検定 $\mu_D$ vs $0$ 対応あり $n – 1$

4つは実は1つの原理でつながっている

表の最後の列「自由度」を見てみましょう。対応のあるt検定は差 $D_i = X_i – Y_i$ を取って1標本t検定を適用するものなので、自由度が同じ $n – 1$ になっています。Student’s t検定は2群の情報をプールするため自由度が $n_1 + n_2 – 2$、ウェルチのt検定はプールせずに近似自由度を使います。

つまり、4つのt検定はすべて次の共通構造を持っています。

$$ T = \frac{\text{推定された差} – \text{帰無仮説のもとでの差}}{\text{差の標準誤差}} $$

分子は「観測された効果」から「帰無仮説で期待される値」を引いたもの、分母は「その差がどれくらいばらつくか」の推定量です。この $T$ がt分布に従うことを利用してp値を計算するのが、すべてのt検定に共通する原理です。

この共通構造を理解するために、まずt分布がどこから生まれるのかを確認しましょう。

共通の理論基盤 — t分布の導出

t分布が生まれる場面

Z検定では母分散 $\sigma^2$ が既知であることを前提としています。しかし現実には母分散がわかっていることは稀です。母分散を標本分散 $S^2$ で置き換えたとき、検定統計量の分布が正規分布からずれる — このずれを正確に記述するのがt分布です。

イメージとしては、正規分布は「真の散らばり」で標準化した世界、t分布は「散らばりの推定値」で標準化した世界です。推定値には不確実性があるため、t分布は正規分布より裾が重くなります。

t分布の定義

$Z \sim N(0,1)$ と $V \sim \chi^2_\nu$ が独立のとき、

$$ T = \frac{Z}{\sqrt{V/\nu}} $$

は自由度 $\nu$ のt分布に従います。これを $T \sim t_\nu$ と書きます。

1標本の場合のt統計量

$X_1, X_2, \ldots, X_n$ が $N(\mu, \sigma^2)$ からの独立な標本のとき、正規分布の性質から次の2つが成り立ちます。

$$ \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$ は互いに独立です(正規分布に特有の重要な性質)。

帰無仮説 $H_0: \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)}} $$

の形に書けます。ここで右辺を具体的に展開してみましょう。

分子は $Z = \frac{\bar{X} – \mu_0}{\sigma/\sqrt{n}}$ であり、分母は $\sqrt{V/(n-1)} = \sqrt{\frac{S^2}{\sigma^2}} = \frac{S}{\sigma}$ です。したがって、

$$ T = \frac{(\bar{X} – \mu_0)/(\sigma/\sqrt{n})}{S/\sigma} = \frac{\bar{X} – \mu_0}{S/\sqrt{n}} $$

となり、未知パラメータ $\sigma$ が見事に消えます。これがt統計量の最も重要な性質であり、母分散が未知でも検定を実行できる理由です。

この導出では $Z$ と $V$ の独立性と、$V$ がカイ二乗分布に従うことが本質的でした。同じロジックが2標本のケースにも拡張されます。2群の場合に分母をどう構成するかが、Student’s t とウェルチの t の違いを生みます。

では、4つの変種を一つずつ見ていきましょう。

1標本t検定

使う場面

1つの集団から得た標本平均を、既知の基準値 $\mu_0$ と比較したいときに使います。例えば、「500ml表記の飲料の実際の内容量が500mlからずれていないか」「ある工場の製品の厚さが設計値と一致しているか」といった場面です。

仮説と検定統計量

$$ H_0: \mu = \mu_0 \quad \text{vs} \quad H_1: \mu \neq \mu_0 \;(\text{両側}) $$

検定統計量は前節で導出した通り、

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

です。$S = \sqrt{\frac{1}{n-1}\sum_{i=1}^n (X_i – \bar{X})^2}$ は不偏標準偏差です。

押さえるべきポイント

  • 自由度は $n – 1$(平均を推定するために1つの自由度を消費)
  • 母分散 $\sigma^2$ が既知のときは $S$ の代わりに $\sigma$ を使ってZ検定になるが、実用上ほぼt検定を使う
  • 片側検定の場合は $H_1: \mu > \mu_0$ または $H_1: \mu < \mu_0$ として棄却域を片側に設定する

1標本t検定の詳しい導出・信頼区間との双対性・正規性の頑健性については 1標本t検定の理論と実装 で解説しています。

1つの集団と基準値の比較を見ました。次に、2つの独立な集団の平均を比較するケースに進みましょう。

独立2標本t検定

問題設定

2つの独立な正規母集団からの標本が得られたとします。

$$ X_1, \ldots, X_{n_1} \sim N(\mu_1, \sigma_1^2), \quad Y_1, \ldots, Y_{n_2} \sim N(\mu_2, \sigma_2^2) $$

仮説は、

$$ H_0: \mu_1 = \mu_2 \quad \text{vs} \quad H_1: \mu_1 \neq \mu_2 $$

です。「独立」とは、2群のデータが互いに無関係に収集されたことを意味します。例えば、薬を投与した群とプラセボ群が別々の被験者で構成されている場合です。

ここで、$\sigma_1^2$ と $\sigma_2^2$ の関係によって検定の方法が分かれます。

等分散を仮定する場合 — Student’s t検定

$\sigma_1^2 = \sigma_2^2 = \sigma^2$ が成り立つと仮定するとき、2群の標本分散を合わせて $\sigma^2$ をより精度良く推定できます。これがプールされた分散(pooled variance)です。

2つのグループの重み付け平均を取るイメージです。標本サイズが大きいグループの情報をより多く取り入れることで、分散推定の精度が向上します。具体的には、

$$ \begin{equation} S_p^2 = \frac{(n_1 – 1)S_1^2 + (n_2 – 1)S_2^2}{n_1 + n_2 – 2} \end{equation} $$

と定義します。分母の $n_1 + n_2 – 2$ は、2つの標本平均を推定するために合計2つの自由度が消費されることを反映しています。

検定統計量は、

$$ \begin{equation} T = \frac{\bar{X} – \bar{Y}}{S_p\sqrt{\frac{1}{n_1} + \frac{1}{n_2}}} \sim t_{n_1 + n_2 – 2} \end{equation} $$

です。分母は $\bar{X} – \bar{Y}$ の標準誤差であり、$S_p$ で共通の母標準偏差を推定し、$\sqrt{1/n_1 + 1/n_2}$ で2つの標本平均の差の散らばりを計算しています。

自由度は $n_1 + n_2 – 2$ であり、2群のデータ数を合計して2を引いた値です。等分散の仮定が正しいとき、この検定は一様最強力検定(UMP test)であり、検出力が最も高くなります。

等分散を仮定しない場合 — ウェルチのt検定

現実のデータでは $\sigma_1^2 = \sigma_2^2$ が厳密に成り立つことは稀です。等分散の仮定が崩れたとき Student’s t検定を適用すると、第一種の過誤率(偽陽性率)が公称の $\alpha$ から大きくずれる危険があります。

ウェルチのt検定は、各群の標本分散をそのまま使い、等分散を仮定しません。

$$ \begin{equation} T = \frac{\bar{X} – \bar{Y}}{\sqrt{\frac{S_1^2}{n_1} + \frac{S_2^2}{n_2}}} \end{equation} $$

この統計量は厳密にはt分布に従いませんが、サターウェイト(Satterthwaite)の近似により、近似的な自由度 $\nu$ を用いることで $T \approx t_\nu$ と見なせます。

$$ \begin{equation} \nu = \frac{\left(\frac{S_1^2}{n_1} + \frac{S_2^2}{n_2}\right)^2}{\frac{\left(S_1^2/n_1\right)^2}{n_1 – 1} + \frac{\left(S_2^2/n_2\right)^2}{n_2 – 1}} \end{equation} $$

この自由度は一般に整数にはならず、$\min(n_1, n_2) – 1$ 以上 $n_1 + n_2 – 2$ 以下の実数値を取ります。$S_1^2 = S_2^2$ のとき $\nu = n_1 + n_2 – 2$ となり、Student’s t検定の自由度に一致します。

Student’s t vs ウェルチの t — どちらを使うべきか

結論を先に述べると、迷ったらウェルチのt検定を使うのが安全 です。その理由は次の通りです。

  • 等分散が成り立つとき: ウェルチの自由度は Student’s t の自由度にほぼ一致するため、検出力の損失はごくわずか
  • 等分散が成り立たないとき: Student’s t の第一種の過誤率は $\alpha$ から大きく逸脱する(特に $n_1 \neq n_2$ のとき深刻)が、ウェルチは頑健

つまりウェルチのt検定は、等分散でも非等分散でも適切に機能する「安全な選択」です。多くの統計ソフトでウェルチのt検定がデフォルトとなっている理由がここにあります。

等分散性の事前検定としてF検定やルヴェーン検定を行い、その結果に応じて Student’s t かウェルチの t を選ぶ、というアプローチもあります。しかし「検定を選ぶために検定する」という二段階手続きは全体の検定の性質を複雑にするため、近年は最初からウェルチのt検定を使うことが推奨される傾向にあります。

独立な2群の比較を見てきました。しかし、データが同一対象の前後測定のように対応関係を持つ場合は、別のアプローチが必要です。

対応のあるt検定

使う場面

同じ被験者や同じ対象に対して、2つの条件(処理前と処理後、薬Aと薬Bなど)で測定されたデータがある場合、それらの測定値には対応関係があります。

例えば、ダイエットプログラムの効果を検証するために、同じ10人の体重を「プログラム開始前」と「3ヶ月後」に測定したとします。このとき、各個人の「体重の変化量」に注目すれば、個人間の体格差(身長の違い、基礎代謝の違いなど)の影響を除去できます。

差を取って1標本問題に帰着

$n$ 組の対応データ $(X_i, Y_i)$ に対し、差 $D_i = X_i – Y_i$ を計算します。すると「2標本の比較」が「差 $D_i$ の母平均 $\mu_D$ が0かどうか」という1標本問題に帰着します。

$$ H_0: \mu_D = 0 \quad \text{vs} \quad H_1: \mu_D \neq 0 $$

検定統計量は、

$$ \begin{equation} T = \frac{\bar{D}}{S_D/\sqrt{n}} \sim t_{n-1} \end{equation} $$

です。ここで $\bar{D} = \frac{1}{n}\sum_{i=1}^n D_i$ は差の標本平均、$S_D = \sqrt{\frac{1}{n-1}\sum_{i=1}^n(D_i – \bar{D})^2}$ は差の標本標準偏差です。

なぜ対応のある検定は検出力が高いのか

対応のある検定が独立2標本の検定より検出力が高くなる理由を、分散の観点から理解しましょう。

独立2標本として扱った場合、$\bar{X} – \bar{Y}$ の分散は、

$$ \text{Var}[\bar{X} – \bar{Y}]_{\text{独立}} = \frac{\sigma_X^2}{n} + \frac{\sigma_Y^2}{n} $$

です。一方、対応のある場合は差 $D_i = X_i – Y_i$ の分散を使うため、

$$ \text{Var}[\bar{D}] = \frac{\sigma_D^2}{n} = \frac{\sigma_X^2 + \sigma_Y^2 – 2\rho\sigma_X\sigma_Y}{n} $$

となります。ここで $\rho$ は $X_i$ と $Y_i$ の相関係数です。

重要なのは右辺の $-2\rho\sigma_X\sigma_Y$ の項です。同じ個体の前後測定は通常正の相関($\rho > 0$)を持つため、この項が分散を小さくします。分散が小さいほどt統計量の絶対値が大きくなりやすく、検出力が向上します。

直感的に言えば、対応のある検定は「個人差というノイズ」を差し引くことで、「処理効果というシグナル」を浮かび上がらせているのです。

対応のあるt検定を含めた4つの変種を一通り見てきました。では、手元のデータに対してどのt検定を適用すべきかの判断プロセスを整理しましょう。

検定の選び方フローチャート

実際の分析場面では、以下のステップで適切なt検定を選択します。テキストによるフローチャートで示します。

[START] 手元のデータで平均の比較をしたい
│
├─ Q1: 比較対象はいくつ?
│   ├─ 1つの標本 vs 既知の基準値 → 【1標本t検定】
│   │
│   └─ 2つの標本を比較
│       │
│       ├─ Q2: データに対応関係があるか?
│       │   │  (同一被験者の前後、同一製品のA法B法、マッチドペアなど)
│       │   │
│       │   ├─ YES → 【対応のあるt検定】
│       │   │        (差を取って1標本t検定に帰着)
│       │   │
│       │   └─ NO(独立な2群)
│       │       │
│       │       └─ Q3: 等分散を仮定できるか?
│       │           │
│       │           ├─ YES(理論的根拠あり、F検定で確認済み)
│       │           │   → 【Student's t検定(等分散)】
│       │           │
│       │           └─ NO / 不明
│       │               → 【ウェルチのt検定(推奨デフォルト)】
│       │
│       └─ 3つ以上の標本 → t検定ではなくANOVAを検討
│
[補足] いずれの場合も正規性の仮定を確認する
       ├─ n ≥ 30 → 中心極限定理により頑健(通常問題なし)
       ├─ n < 30 かつ正規性に問題なし → t検定OK
       └─ n < 30 かつ強い歪み/外れ値あり
           → ノンパラメトリック検定を検討
             (ウィルコクソン符号順位検定、マンホイットニーU検定など)

判断のポイント

Q2(対応の有無)が最も重要な分岐 です。対応があるのに独立2標本の検定を使ってしまうと、検出力が大幅に低下します。逆に、独立データに対応のある検定を使うことはできません(対応の取り方が定義できないため)。

Q3(等分散の判断)については、前述の通りウェルチのt検定をデフォルトとすることを推奨 します。等分散を仮定する Student’s t検定を使うのは、物理的・理論的に等分散が保証される特殊なケースに限るのが安全です。

3群以上の比較にt検定を繰り返し適用すると、多重比較の問題(第一種の過誤率の膨張)が生じます。この場合は分散分析(ANOVA)を使いましょう。

選び方を整理したところで、いよいよPythonで4つの変種をすべてスクラッチ実装し、統一的に扱えるようにしましょう。

Pythonでの統一実装

4つのt検定をスクラッチで実装する

まず、4つの変種を1つの関数群として実装します。すべてのt検定が「差 / 差の標準誤差」という共通構造を持つことを意識しながら書いていきます。

import numpy as np
from scipy import stats

def one_sample_t_test(x, mu0=0, alternative='two-sided'):
    """1標本t検定(スクラッチ実装)"""
    n = len(x)
    xbar = np.mean(x)
    s = np.std(x, ddof=1)
    se = s / np.sqrt(n)
    t_stat = (xbar - mu0) / se
    df = n - 1

    if alternative == 'two-sided':
        p_value = 2 * (1 - stats.t.cdf(abs(t_stat), df))
    elif alternative == 'greater':
        p_value = 1 - stats.t.cdf(t_stat, df)
    elif alternative == 'less':
        p_value = stats.t.cdf(t_stat, df)

    return {'t_stat': t_stat, 'p_value': p_value, 'df': df,
            'mean': xbar, 'se': se, 'test': '1-sample t-test'}

def two_sample_t_test_equal(x, y, alternative='two-sided'):
    """独立2標本t検定 — 等分散仮定(Student's t)"""
    n1, n2 = len(x), len(y)
    xbar, ybar = np.mean(x), np.mean(y)
    s1_sq, s2_sq = np.var(x, ddof=1), np.var(y, ddof=1)

    # プールされた分散
    sp_sq = ((n1 - 1) * s1_sq + (n2 - 1) * s2_sq) / (n1 + n2 - 2)
    se = np.sqrt(sp_sq * (1/n1 + 1/n2))
    t_stat = (xbar - ybar) / se
    df = n1 + n2 - 2

    if alternative == 'two-sided':
        p_value = 2 * (1 - stats.t.cdf(abs(t_stat), df))
    elif alternative == 'greater':
        p_value = 1 - stats.t.cdf(t_stat, df)
    elif alternative == 'less':
        p_value = stats.t.cdf(t_stat, df)

    return {'t_stat': t_stat, 'p_value': p_value, 'df': df,
            'mean_diff': xbar - ybar, 'se': se,
            'test': "Student's t-test (equal var)"}

def two_sample_t_test_welch(x, y, alternative='two-sided'):
    """独立2標本t検定 — ウェルチ(等分散仮定なし)"""
    n1, n2 = len(x), len(y)
    xbar, ybar = np.mean(x), np.mean(y)
    s1_sq, s2_sq = np.var(x, ddof=1), np.var(y, ddof=1)

    se = np.sqrt(s1_sq/n1 + s2_sq/n2)
    t_stat = (xbar - ybar) / se

    # サターウェイトの近似自由度
    num = (s1_sq/n1 + s2_sq/n2)**2
    den = (s1_sq/n1)**2 / (n1 - 1) + (s2_sq/n2)**2 / (n2 - 1)
    df = num / den

    if alternative == 'two-sided':
        p_value = 2 * (1 - stats.t.cdf(abs(t_stat), df))
    elif alternative == 'greater':
        p_value = 1 - stats.t.cdf(t_stat, df)
    elif alternative == 'less':
        p_value = stats.t.cdf(t_stat, df)

    return {'t_stat': t_stat, 'p_value': p_value, 'df': df,
            'mean_diff': xbar - ybar, 'se': se,
            'test': "Welch's t-test (unequal var)"}

def paired_t_test(x, y, alternative='two-sided'):
    """対応のあるt検定(差の1標本t検定に帰着)"""
    if len(x) != len(y):
        raise ValueError("対応ありの場合、2群のサイズは同じ必要があります")
    d = x - y
    return one_sample_t_test(d, mu0=0, alternative=alternative) | {
        'test': 'Paired t-test', 'mean_diff': np.mean(d)}

上のコードでは、4つの関数がすべて同じパターン(差の計算 → 標準誤差の計算 → t統計量 → p値)に従っていることが見て取れます。特に paired_t_test は差を取った後に one_sample_t_test を呼んでいるだけであり、対応のあるt検定が1標本t検定の特殊ケースであることがコード上でも明確です。

次に、テストデータを生成して4つの検定をすべて実行してみましょう。

テストデータでの実行

import numpy as np
from scipy import stats

np.random.seed(42)

# --- 1標本t検定のテスト ---
sample = np.random.normal(498, 4, 20)  # 真の平均498, 基準値500
result_1s = one_sample_t_test(sample, mu0=500)

# --- 独立2標本(等分散)のテスト ---
group_a = np.random.normal(105, 10, 30)
group_b = np.random.normal(100, 10, 30)
result_eq = two_sample_t_test_equal(group_a, group_b)

# --- 独立2標本(ウェルチ)のテスト ---
group_c = np.random.normal(105, 15, 30)
group_d = np.random.normal(100, 5, 30)
result_welch = two_sample_t_test_welch(group_c, group_d)

# --- 対応のあるt検定のテスト ---
baseline = np.random.normal(70, 12, 25)
pre = baseline + np.random.normal(0, 3, 25)
post = baseline + 4 + np.random.normal(0, 3, 25)  # 処理効果 = 4
result_paired = paired_t_test(post, pre)

# 結果表示
for name, res in [('1-sample', result_1s),
                  ('Student (equal var)', result_eq),
                  ('Welch (unequal var)', result_welch),
                  ('Paired', result_paired)]:
    print(f"[{name}]")
    print(f"  検定名: {res['test']}")
    print(f"  t統計量 = {res['t_stat']:.4f}")
    print(f"  自由度  = {res['df']:.2f}")
    print(f"  p値     = {res['p_value']:.6f}")
    print()

実行すると、4つの検定それぞれについてt統計量・自由度・p値が出力されます。1標本t検定では自由度が19($n – 1 = 20 – 1$)、Student’s t検定では58($30 + 30 – 2$)、ウェルチのt検定では整数でない近似自由度、対応のあるt検定では24($25 – 1$)が得られることを確認できます。また、対応のあるt検定では個人差が除去されるため、独立検定より小さなp値が得られやすい傾向が見て取れます。

スクラッチ実装が正しいことを確認するため、次にscipy.statsの結果と照合しましょう。

scipy.statsとの比較

4つの検定すべてを照合

scipy.statsには ttest_1sampttest_indttest_rel が用意されています。スクラッチ実装の結果と一致することを確認しましょう。

import numpy as np
from scipy import stats

np.random.seed(42)

# 同じデータを再生成
sample = np.random.normal(498, 4, 20)
group_a = np.random.normal(105, 10, 30)
group_b = np.random.normal(100, 10, 30)
group_c = np.random.normal(105, 15, 30)
group_d = np.random.normal(100, 5, 30)
baseline = np.random.normal(70, 12, 25)
pre = baseline + np.random.normal(0, 3, 25)
post = baseline + 4 + np.random.normal(0, 3, 25)

# スクラッチ実装
res_1s = one_sample_t_test(sample, mu0=500)
res_eq = two_sample_t_test_equal(group_a, group_b)
res_w = two_sample_t_test_welch(group_c, group_d)
res_p = paired_t_test(post, pre)

# scipy.stats
sp_1s = stats.ttest_1samp(sample, 500)
sp_eq = stats.ttest_ind(group_a, group_b, equal_var=True)
sp_w = stats.ttest_ind(group_c, group_d, equal_var=False)
sp_p = stats.ttest_rel(post, pre)

# 比較表
print(f"{'検定':<25} {'t (scratch)':>12} {'t (scipy)':>12} {'p (scratch)':>12} {'p (scipy)':>12}")
print("-" * 75)
comparisons = [
    ('1-sample', res_1s, sp_1s),
    ('Student (equal var)', res_eq, sp_eq),
    ('Welch (unequal var)', res_w, sp_w),
    ('Paired', res_p, sp_p),
]
for name, scratch, scipy_res in comparisons:
    print(f"{name:<25} {scratch['t_stat']:>12.6f} {scipy_res.statistic:>12.6f} "
          f"{scratch['p_value']:>12.8f} {scipy_res.pvalue:>12.8f}")

出力では、4つすべてのケースでスクラッチ実装と scipy.stats のt統計量・p値が小数点以下6桁以上一致することが確認できます。これは、スクラッチ実装が正しく動作していることの裏付けです。scipy.stats の ttest_indequal_var パラメータで Student’s t(True)とウェルチ(False、デフォルト)を切り替えられます。

可視化による4つの検定の俯瞰

4つの変種を同時に可視化して、データの構造と検定結果の関係を直感的に把握します。

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

np.random.seed(42)

# データ生成
sample = np.random.normal(498, 4, 20)
group_a = np.random.normal(105, 10, 30)
group_b = np.random.normal(100, 10, 30)
group_c = np.random.normal(105, 15, 30)
group_d = np.random.normal(100, 5, 30)
baseline = np.random.normal(70, 12, 25)
pre = baseline + np.random.normal(0, 3, 25)
post = baseline + 4 + np.random.normal(0, 3, 25)

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# (a) 1標本t検定
ax = axes[0, 0]
t_1s, p_1s = stats.ttest_1samp(sample, 500)
ax.hist(sample, bins=10, density=True, color='steelblue',
        edgecolor='white', alpha=0.8)
ax.axvline(np.mean(sample), color='navy', linewidth=2,
           label=f'$\\bar{{x}} = {np.mean(sample):.1f}$')
ax.axvline(500, color='red', linewidth=2, linestyle='--',
           label=f'$\\mu_0 = 500$')
ax.set_title(f'(a) One-sample t-test\nt = {t_1s:.3f}, p = {p_1s:.4f}',
             fontsize=12)
ax.set_xlabel('Volume (ml)')
ax.set_ylabel('Density')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)

# (b) Student's t検定(等分散)
ax = axes[0, 1]
t_eq, p_eq = stats.ttest_ind(group_a, group_b, equal_var=True)
bp = ax.boxplot([group_a, group_b], labels=['Group A', 'Group B'],
                patch_artist=True, widths=0.5)
bp['boxes'][0].set_facecolor('steelblue')
bp['boxes'][1].set_facecolor('coral')
for box in bp['boxes']:
    box.set_alpha(0.7)
ax.set_title(f"(b) Student's t-test (equal var)\nt = {t_eq:.3f}, p = {p_eq:.4f}\n"
             f"$s_1^2 = {np.var(group_a, ddof=1):.1f}$, "
             f"$s_2^2 = {np.var(group_b, ddof=1):.1f}$",
             fontsize=12)
ax.set_ylabel('Value')
ax.grid(True, alpha=0.3, axis='y')

# (c) ウェルチのt検定(非等分散)
ax = axes[1, 0]
t_w, p_w = stats.ttest_ind(group_c, group_d, equal_var=False)
bp = ax.boxplot([group_c, group_d], labels=['Group C', 'Group D'],
                patch_artist=True, widths=0.5)
bp['boxes'][0].set_facecolor('steelblue')
bp['boxes'][1].set_facecolor('coral')
for box in bp['boxes']:
    box.set_alpha(0.7)
ax.set_title(f"(c) Welch's t-test (unequal var)\nt = {t_w:.3f}, p = {p_w:.4f}\n"
             f"$s_1^2 = {np.var(group_c, ddof=1):.1f}$, "
             f"$s_2^2 = {np.var(group_d, ddof=1):.1f}$",
             fontsize=12)
ax.set_ylabel('Value')
ax.grid(True, alpha=0.3, axis='y')

# (d) 対応のあるt検定
ax = axes[1, 1]
t_p, p_p = stats.ttest_rel(post, pre)
n = len(pre)
for i in range(n):
    color = 'green' if post[i] > pre[i] else 'red'
    ax.plot([0, 1], [pre[i], post[i]], color=color, alpha=0.3, linewidth=1)
ax.scatter(np.zeros(n), pre, color='steelblue', s=40, zorder=5, label='Pre')
ax.scatter(np.ones(n), post, color='coral', s=40, zorder=5, label='Post')
ax.set_xticks([0, 1])
ax.set_xticklabels(['Pre', 'Post'], fontsize=11)
ax.set_title(f'(d) Paired t-test\nt = {t_p:.3f}, p = {p_p:.4f}\n'
             f'mean diff = {np.mean(post - pre):.2f}',
             fontsize=12)
ax.set_ylabel('Score')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3, axis='y')

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

この4パネルの図から、各t検定の特徴が視覚的に把握できます。

  1. (a) 1標本t検定: 標本平均 $\bar{x}$ と基準値 $\mu_0 = 500$ のずれを検定しています。ヒストグラムが $\mu_0$ の左側に偏っていることから、平均が500より小さいことが示唆されます

  2. (b) Student’s t検定: 2群の箱ひげ図がほぼ同じ幅(同程度の分散)を持っており、等分散の仮定が妥当な状況です。中央値の差がp値に反映されています

  3. (c) ウェルチのt検定: Group Cの箱ひげ図が Group Dより明らかに幅が広く、分散の不等性が目に見えます。このような場合は Student’s t検定ではなくウェルチのt検定が適切です

  4. (d) 対応のあるt検定: 各被験者の前後を線で結んでおり、ほとんどの線が右上がり(緑色)で処理効果が一貫していることがわかります。個人差が大きくても差の方向は揃っているため、小さなp値が得られています

第一種の過誤率のシミュレーション

最後に、等分散の仮定が崩れたとき Student’s t検定とウェルチのt検定で第一種の過誤率がどう変わるかをモンテカルロシミュレーションで確認します。

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

np.random.seed(42)

# 分散比を変えてシミュレーション
variance_ratios = [1, 2, 4, 8, 16]
sample_size_pairs = [(15, 15), (15, 30), (30, 15)]
n_sim = 10000
alpha = 0.05

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

for ax_idx, (n1, n2) in enumerate(sample_size_pairs):
    ax = axes[ax_idx]
    error_student = []
    error_welch = []

    for ratio in variance_ratios:
        s1, s2 = 1.0, np.sqrt(ratio)
        rej_s, rej_w = 0, 0
        for _ in range(n_sim):
            x = np.random.normal(0, s1, n1)
            y = np.random.normal(0, s2, n2)
            _, p_s = stats.ttest_ind(x, y, equal_var=True)
            _, p_w = stats.ttest_ind(x, y, equal_var=False)
            rej_s += (p_s < alpha)
            rej_w += (p_w < alpha)
        error_student.append(rej_s / n_sim)
        error_welch.append(rej_w / n_sim)

    x_pos = np.arange(len(variance_ratios))
    width = 0.35
    ax.bar(x_pos - width/2, error_student, width, color='steelblue',
           alpha=0.8, label="Student's t")
    ax.bar(x_pos + width/2, error_welch, width, color='coral',
           alpha=0.8, label="Welch's t")
    ax.axhline(0.05, color='black', linewidth=1.5, linestyle='--',
               label='Nominal $\\alpha = 0.05$')
    ax.set_xlabel('Variance ratio $\\sigma_2^2/\\sigma_1^2$', fontsize=11)
    ax.set_ylabel('Type I error rate', fontsize=11)
    ax.set_title(f'$n_1 = {n1}$, $n_2 = {n2}$', fontsize=13)
    ax.set_xticks(x_pos)
    ax.set_xticklabels(variance_ratios)
    ax.legend(fontsize=9)
    ax.grid(True, alpha=0.3, axis='y')
    ax.set_ylim(0, 0.15)

plt.suptitle('Type I Error Rate: Student vs Welch', fontsize=14, y=1.02)
plt.tight_layout()
plt.savefig('student_vs_welch_error.png', dpi=150, bbox_inches='tight')
plt.show()

この3パネルの図は非常に教訓的です。

  1. $n_1 = n_2 = 15$(左): サンプルサイズが等しいとき、分散比が大きくなっても Student’s t の過誤率はそれほど膨張しません。等サンプルサイズには「自己補正」の性質があるためです。ウェルチのt検定は常に0.05を維持しています

  2. $n_1 = 15, n_2 = 30$(中央): サンプルサイズが異なるとき、Student’s t の過誤率の膨張が顕著です。サンプルサイズが小さい方の分散が大きい($\sigma_1^2 < \sigma_2^2$ かつ $n_1 < n_2$)場合、過誤率は公称の0.05を大幅に上回ります

  3. $n_1 = 30, n_2 = 15$(右): こちらのパターンでは過誤率が逆に0.05を下回る方向に歪みます。Student’s t検定は保守的になり、検出力が低下します

いずれの場合もウェルチのt検定は0.05付近に安定しており、不等分散に対する頑健性が実証されています。このシミュレーション結果が「迷ったらウェルチ」という推奨の根拠です。

t検定の注意点と頑健性

仮定のまとめ

すべてのt検定に共通する仮定と、検定固有の仮定を整理します。

仮定 対象 違反時のリスク
正規性 全t検定 小標本かつ強い歪みで過誤率が逸脱
独立性 独立2標本 系統的偏りを招き、検定が無意味に
等分散性 Student’s t のみ 第一種の過誤率が膨張($n_1 \neq n_2$ で深刻)

正規性に対する頑健性

t検定の正規性の仮定はどの程度厳密に守る必要があるのでしょうか。結論としては、中心極限定理のおかげで、サンプルサイズが十分大きければ(目安 $n \geq 30$)正規性の仮定からの逸脱に頑健 です。

ただし、以下のケースでは注意が必要です。

  • 小標本($n < 15$)かつ強い歪み: 指数分布や対数正規分布のように右裾が長い分布では、小標本でのt検定の信頼性が低下します
  • 外れ値の存在: t検定は平均と分散に基づくため、外れ値の影響を強く受けます。1つの外れ値がt統計量を大きく変える可能性があります
  • 分散が存在しない分布: コーシー分布のように理論的に分散が発散する場合、t検定の前提が根本的に崩れます

正規性に不安がある場合は ノンパラメトリック検定の概要 で紹介しているウィルコクソンの符号順位検定(1標本・対応あり)やマン・ホイットニーのU検定(独立2標本)を検討してください。

独立性に対する注意

独立性の仮定はしばしば見過ごされますが、実は最も深刻な問題を引き起こし得ます。例えば、同じクラスの生徒同士は互いに影響し合うため、独立とは言えません。時系列データの連続した観測値も独立ではありません。

独立性が成り立たないときのt検定の結果は、過誤率が予測不能な方向にずれるため、正規性の違反より修正が困難です。データ収集の段階で独立性を確保する実験計画が重要です。

よくある間違い

t検定を使う上で実務で頻繁に見られる間違いを整理します。

1. 対応のあるデータに独立2標本検定を使う

同じ被験者の前後データを「2つの独立グループ」として扱うと、個人差の情報が捨てられ、検出力が大幅に低下します。ダイエット前後の体重を比較するのに、前のグループ20人と後のグループ20人を独立に扱うのは典型的なミスです。

2. 多重比較を無視する

3群以上の比較でt検定を繰り返す(A vs B、B vs C、A vs C)と、全体の第一種の過誤率が公称の $\alpha$ をはるかに超えます。$k$ 群の比較で $\binom{k}{2}$ 回のt検定を行うと、少なくとも1つの偽陽性が出る確率は $1 – (1-\alpha)^{\binom{k}{2}}$ に膨張します。3群でも $1 – 0.95^3 \approx 0.14$ であり、公称5%の3倍近い偽陽性率になります。3群以上の場合はANOVA + 多重比較法を使いましょう。

3. 「有意でない = 差がない」と解釈する

p値が $\alpha$ を超えて帰無仮説を棄却できなかったとき、「2群に差がない」と結論するのは論理的誤りです。帰無仮説を棄却できなかったことは、「差がないことが証明された」のではなく、「差があるとは言えなかった」に過ぎません。差の存在を積極的に示すには、同等性検定(TOST: Two One-Sided Tests)などの別の枠組みが必要です。

4. サンプルサイズを考慮しない

非常に大きなサンプルサイズでは、実質的に意味のない小さな差でも統計的に有意になります。p値だけでなく、効果量(Cohen’s d)や信頼区間の幅を合わせて報告することが重要です。逆に、サンプルサイズが小さいときは、本当に差があっても検出できない(第二種の過誤)可能性が高くなります。事前に検出力分析を行い、必要なサンプルサイズを設計に組み込むべきです。

5. p値のハッキング(p-hacking)

データを見てから検定の種類(両側/片側)を選ぶ、都合の良い外れ値を除去する、有意差が出るまでデータを追加する — これらはいずれもp値の意味を破壊する行為です。検定の計画は必ずデータ収集の前に確定しておく必要があります。

効果量 Cohen’s d

t検定で有意差が出たとき、その差がどの程度「大きい」のかを示す指標が効果量 Cohen’s d です。

1標本の場合:

$$ d = \frac{\bar{x} – \mu_0}{s} $$

独立2標本の場合:

$$ d = \frac{\bar{x} – \bar{y}}{s_p} $$

対応ありの場合:

$$ d = \frac{\bar{d}}{s_d} $$

Cohen の目安では $|d| = 0.2$ が小さな効果、$|d| = 0.5$ が中程度の効果、$|d| = 0.8$ が大きな効果とされています。ただしこの基準は分野によって異なり、あくまで参考です。p値と合わせて効果量を報告することで、統計的有意性と実質的有意性の両面から結果を評価できます。

t検定と他の検定手法の関係

t検定は多くの検定手法と関連しています。この関係を理解しておくと、適用範囲を超えた場面で適切な代替手法を選べます。

  • Z検定: 母分散既知のときのt検定の特殊ケース。$n \to \infty$ でt検定はZ検定に収束
  • ANOVA(分散分析): 独立2標本t検定を3群以上に一般化したもの。2群ANOVAのF統計量は $t^2$ に等しい
  • ウィルコクソン検定: t検定のノンパラメトリック版。正規性が仮定できないとき使用
  • マン・ホイットニーU検定: 独立2標本t検定のノンパラメトリック版
  • ブートストラップ検定: 分布の仮定なしに任意の統計量の検定を行う再標本法

t検定の全体像、理論、実装、注意点をすべて網羅しました。最後にまとめます。

まとめ

本記事では、t検定の4つの変種を統一的な視点から整理しました。

  • 共通構造: すべてのt検定は $T = \frac{\text{推定された差} – \text{帰無仮説のもとでの差}}{\text{差の標準誤差}}$ という共通の構造を持ち、母分散を標本分散で推定することでt分布に従う
  • 1標本t検定: 1つの集団の平均を基準値と比較する。自由度は $n – 1$
  • Student’s t検定: 独立2群の平均を等分散の仮定のもとで比較する。プールされた分散を使い、自由度は $n_1 + n_2 – 2$
  • ウェルチのt検定: 独立2群の平均を等分散の仮定なしで比較する。サターウェイトの近似自由度を使う。デフォルトの選択として推奨
  • 対応のあるt検定: 差を取って1標本t検定に帰着させる。個人差を除去するため検出力が高い。自由度は $n – 1$
  • 選び方: まず対応の有無を判断し、独立2標本なら原則ウェルチを使う。正規性は $n \geq 30$ で頑健だが、小標本かつ歪みが大きいときはノンパラメトリック検定を検討
  • 報告: p値だけでなく効果量(Cohen’s d)と信頼区間を合わせて報告することで、結果の解釈が豊かになる

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