ノンパラメトリック検定(ウィルコクソン・マンホイットニー)を解説

t検定やz検定は、データが正規分布に従うことを前提としています。しかし実際のデータは、分布の歪みや外れ値の存在により、正規性の仮定が成り立たないことが少なくありません。そのような場合に活躍するのが ノンパラメトリック検定 です。

ノンパラメトリック検定は、データの分布形状を仮定せずに検定を行う方法です。データを実際の値ではなく 順位(rank) に変換して検定統計量を構成するため、外れ値にも頑健であるという大きな利点があります。

本記事の内容

  • ノンパラメトリック検定が必要な場面
  • ウィルコクソンの符号付き順位検定(対応あり)の理論と統計量の導出
  • マンホイットニーU検定(対応なし)の理論と統計量の導出
  • 大標本での正規近似
  • パラメトリック検定との比較
  • Pythonでのスクラッチ実装とscipy.statsを用いた実装

前提知識

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

ノンパラメトリック検定が必要な場面

パラメトリック検定の限界

t検定などのパラメトリック検定は、以下の仮定を置いています。

  1. 正規性: データが正規分布に従う
  2. 等分散性: 2群の場合、両群の分散が等しい
  3. 連続性: データが連続変数である

これらの仮定が大きく崩れている場合、パラメトリック検定の結果は信頼できなくなります。

ノンパラメトリック検定が適している場面

  • データが正規分布に従わない: 右に裾が長い分布(所得データなど)、双峰性の分布
  • 外れ値がある: 外れ値が結果に大きく影響する場合
  • 標本サイズが小さい: 分布の仮定が検証しにくい小標本
  • 順序データ: リッカート尺度(1〜5段階の評価)など、順序はあるが間隔の等しさが保証されないデータ

パラメトリック検定との対応

パラメトリック ノンパラメトリック 用途
対応のあるt検定 ウィルコクソンの符号付き順位検定 対応のある2群の比較
独立2標本t検定 マンホイットニーU検定 独立な2群の比較
一元配置分散分析 クラスカル・ウォリス検定 3群以上の比較

ウィルコクソンの符号付き順位検定

概要

ウィルコクソンの符号付き順位検定(Wilcoxon signed-rank test) は、対応のある2群のデータの差に関する検定です。対応のあるt検定のノンパラメトリック版と位置づけられます。

問題設定

対応のある $n$ 組のデータ $(X_1, Y_1), (X_2, Y_2), \dots, (X_n, Y_n)$ があります。差を $D_i = X_i – Y_i$ とおきます。

$$ H_0: D_i \text{ の分布の中央値} = 0 \quad (\text{対称な分布を仮定}) $$

$$ H_1: D_i \text{ の分布の中央値} \neq 0 $$

帰無仮説は「処理前後で差がない」という主張に対応します。

仮定

  • $D_i$ の分布は0に関して対称である
  • $D_i$ は連続分布に従う(同順位が生じない)
  • $D_i$ は互いに独立

検定統計量の構成

ステップ1: 差の絶対値を計算し、$D_i = 0$ のデータを除外します。残りのデータ数を $n’$ とします。

$$ |D_1|, |D_2|, \dots, |D_{n’}| $$

ステップ2: 差の絶対値 $|D_i|$ に小さい順に順位 $R_i$ をつけます。

ステップ3: 各順位に元の差 $D_i$ の符号を付与します。

$$ \text{符号付き順位} = \text{sign}(D_i) \cdot R_i $$

ステップ4: 正の符号付き順位の和 $W^+$ と負の符号付き順位の和 $W^-$ を計算します。

$$ W^+ = \sum_{i: D_i > 0} R_i, \quad W^- = \sum_{i: D_i < 0} R_i $$

$W^+$ と $W^-$ の関係を確認します。全順位の和は

$$ W^+ + W^- = \sum_{i=1}^{n’} R_i = 1 + 2 + \dots + n’ = \frac{n'(n’+1)}{2} $$

検定統計量として $W = W^+$ を使用します($W^-$ でも等価です)。

$W^+$ の帰無分布の導出

帰無仮説のもとでは、$D_i$ の分布は0に関して対称であるため、各 $D_i$ が正である確率と負である確率は等しく $1/2$ です。

したがって、順位 $R_i$ が正の符号付き順位に含まれるか負の符号付き順位に含まれるかは、確率 $1/2$ で独立に決まります。

$\epsilon_i \in \{0, 1\}$ を、$D_i > 0$ なら $\epsilon_i = 1$、$D_i < 0$ なら $\epsilon_i = 0$ とする指示変数とすると、

$$ W^+ = \sum_{i=1}^{n’} \epsilon_i \cdot R_i = \sum_{i=1}^{n’} \epsilon_i \cdot i $$

ここで、帰無仮説のもとで $P(\epsilon_i = 1) = P(\epsilon_i = 0) = 1/2$, かつ $\epsilon_1, \dots, \epsilon_{n’}$ は独立です。

$W^+$ の期待値と分散の導出

期待値:

$$ \begin{align} E[W^+] &= E\left[\sum_{i=1}^{n’} \epsilon_i \cdot i\right] \\ &= \sum_{i=1}^{n’} i \cdot E[\epsilon_i] \\ &= \sum_{i=1}^{n’} i \cdot \frac{1}{2} \\ &= \frac{1}{2} \sum_{i=1}^{n’} i \\ &= \frac{1}{2} \cdot \frac{n'(n’+1)}{2} \\ &= \frac{n'(n’+1)}{4} \end{align} $$

分散:

$\epsilon_i$ が独立であるため、

$$ \begin{align} \text{Var}(W^+) &= \text{Var}\left(\sum_{i=1}^{n’} \epsilon_i \cdot i\right) \\ &= \sum_{i=1}^{n’} i^2 \cdot \text{Var}(\epsilon_i) \end{align} $$

$\epsilon_i$ はベルヌーイ分布 $B(1, 1/2)$ に従うので、

$$ \text{Var}(\epsilon_i) = \frac{1}{2} \cdot \frac{1}{2} = \frac{1}{4} $$

したがって、

$$ \begin{align} \text{Var}(W^+) &= \frac{1}{4} \sum_{i=1}^{n’} i^2 \\ &= \frac{1}{4} \cdot \frac{n'(n’+1)(2n’+1)}{6} \\ &= \frac{n'(n’+1)(2n’+1)}{24} \end{align} $$

ここで $\sum_{i=1}^{n’} i^2 = \frac{n'(n’+1)(2n’+1)}{6}$ という公式を用いました。

正規近似

$n’$ が十分に大きい場合(一般に $n’ \geq 20$)、中心極限定理により $W^+$ は近似的に正規分布に従います。

$$ Z = \frac{W^+ – E[W^+]}{\sqrt{\text{Var}(W^+)}} = \frac{W^+ – \frac{n'(n’+1)}{4}}{\sqrt{\frac{n'(n’+1)(2n’+1)}{24}}} \approx N(0, 1) $$

同順位(タイ)がある場合の補正

同じ絶対値のデータがある場合、順位の平均(ミッドランク)を割り当てます。同順位がある場合の分散の補正は次のようになります。

$g$ 個の同順位グループがあり、$j$ 番目のグループの大きさを $t_j$ とすると、

$$ \text{Var}(W^+) = \frac{n'(n’+1)(2n’+1)}{24} – \frac{1}{48}\sum_{j=1}^{g} t_j(t_j-1)(t_j+1) $$

マンホイットニーU検定

概要

マンホイットニーU検定(Mann-Whitney U test) は、独立な2群のデータの分布の位置の差を検定する方法です。独立2標本t検定のノンパラメトリック版です。ウィルコクソンの順位和検定(Wilcoxon rank-sum test)とも呼ばれ、両者は数学的に等価です。

問題設定

2つの独立な標本があります。

$$ X_1, X_2, \dots, X_{n_1} \sim F, \quad Y_1, Y_2, \dots, Y_{n_2} \sim G $$

$$ H_0: F = G \quad (\text{2つの分布は同一}) $$

$$ H_1: F \neq G \quad (\text{位置パラメータが異なる}) $$

仮定

  • 2群の標本は互いに独立
  • 各群内の観測値は独立
  • データは連続分布に従う(同順位が生じない)
  • 2群の分布の形状は同じ(位置のみが異なりうる)

検定統計量の構成

ステップ1: 2群のデータ $n_1 + n_2$ 個をまとめてプールし、小さい順に順位をつけます。

ステップ2: 群1のデータに割り当てられた順位の和 $R_1$ を計算します。

$$ R_1 = \sum_{i=1}^{n_1} R(X_i) $$

ここで $R(X_i)$ は $X_i$ のプール順位です。

ステップ3: U統計量を次のように計算します。

$$ U_1 = R_1 – \frac{n_1(n_1 + 1)}{2} $$

この式の意味を理解しましょう。群1のデータだけに順位をつけた場合、最小の順位和は $1 + 2 + \dots + n_1 = n_1(n_1+1)/2$ です。$R_1$ からこれを引くことで、群1のデータが群2のデータよりも大きい方に位置するほど $U_1$ が大きくなります。

同様に、群2に対しては

$$ U_2 = R_2 – \frac{n_2(n_2 + 1)}{2} $$

$U$ 統計量の別の定義

$U_1$ は、すべてのペア $(X_i, Y_j)$ について「$X_i > Y_j$」となるペアの数とも解釈できます。

$$ U_1 = \sum_{i=1}^{n_1}\sum_{j=1}^{n_2} \mathbf{1}(X_i > Y_j) $$

これを確認しましょう。$X_i$ のプール順位 $R(X_i)$ は、$X_i$ より小さいすべてのデータの数に1を加えたものです。これは群1の中で $X_i$ より小さいデータの数と、群2の中で $X_i$ より小さいデータの数の和に1を加えたものです。

$$ R(X_i) = |\{k : X_k < X_i, k \neq i\}| + |\{j : Y_j < X_i\}| + 1 $$

群1の順位和は、

$$ \begin{align} R_1 &= \sum_{i=1}^{n_1} R(X_i) \\ &= \sum_{i=1}^{n_1} \left(|\{k : X_k < X_i, k \neq i\}| + |\{j : Y_j < X_i\}| + 1\right) \\ &= \frac{n_1(n_1-1)}{2} + n_1 + \sum_{i=1}^{n_1}|\{j : Y_j < X_i\}| \\ &= \frac{n_1(n_1+1)}{2} + U_1 \end{align} $$

3行目で、$\sum_{i=1}^{n_1}|\{k : X_k < X_i, k \neq i\}|$ は群1内の全ペアの数 $n_1(n_1-1)/2$ に等しいことを使いました。

よって $U_1 = R_1 – n_1(n_1+1)/2 = \sum_{i,j}\mathbf{1}(X_i > Y_j)$ が確認できました。

$U_1$ と $U_2$ の関係

$$ U_1 + U_2 = n_1 \cdot n_2 $$

これは $U_1 = \sum_{i,j}\mathbf{1}(X_i > Y_j)$ かつ $U_2 = \sum_{i,j}\mathbf{1}(Y_j > X_i)$ であり、連続分布では $P(X_i = Y_j) = 0$ なので、全 $n_1 n_2$ ペアについて $X_i > Y_j$ か $Y_j > X_i$ のいずれかが成立することから従います。

検定統計量として $U = \min(U_1, U_2)$ を使用します。

$U$ の帰無分布

帰無仮説 $H_0: F = G$ のもとでは、$n_1 + n_2$ 個のデータのうちどの $n_1$ 個が群1に属するかは等確率です。すなわち $\binom{n_1+n_2}{n_1}$ 通りの割り当てが等しい確率で生じます。

$U_1$ の期待値と分散の導出

期待値:

帰無仮説のもとでは、任意のペア $(X_i, Y_j)$ に対して $P(X_i > Y_j) = 1/2$ です。

$$ \begin{align} E[U_1] &= E\left[\sum_{i=1}^{n_1}\sum_{j=1}^{n_2} \mathbf{1}(X_i > Y_j)\right] \\ &= \sum_{i=1}^{n_1}\sum_{j=1}^{n_2} P(X_i > Y_j) \\ &= n_1 n_2 \cdot \frac{1}{2} \\ &= \frac{n_1 n_2}{2} \end{align} $$

分散:

分散の導出はやや複雑です。$\mathbf{1}(X_i > Y_j)$ は独立ではない(同じ $X_i$ を含むペア同士は相関がある)ため、共分散を考慮する必要があります。

$W_{ij} = \mathbf{1}(X_i > Y_j)$ とおきます。

$$ \text{Var}(U_1) = \sum_{i,j}\sum_{i’,j’} \text{Cov}(W_{ij}, W_{i’j’}) $$

ペアの関係を場合分けします。

ケース1: $(i,j) = (i’,j’)$(同じペア)

$$ \text{Cov}(W_{ij}, W_{ij}) = \text{Var}(W_{ij}) = \frac{1}{2}\left(1 – \frac{1}{2}\right) = \frac{1}{4} $$

このケースは $n_1 n_2$ 個あります。

ケース2: $i = i’$, $j \neq j’$($X_i$ を共有するペア)

$$ E[W_{ij}W_{ij’}] = P(X_i > Y_j \text{ かつ } X_i > Y_{j’}) = \frac{1}{3} $$

3つの値 $X_i, Y_j, Y_{j’}$ が帰無仮説のもとで交換可能であるため、$X_i$ が最大になる確率は $1/3$ です。

$$ \text{Cov}(W_{ij}, W_{ij’}) = \frac{1}{3} – \frac{1}{4} = \frac{1}{12} $$

このケースは $n_1 \binom{n_2}{2} \cdot 2 = n_1 n_2(n_2 – 1)$ 個あります(順序付き)。

ケース3: $i \neq i’$, $j = j’$($Y_j$ を共有するペア)

$$ E[W_{ij}W_{i’j}] = P(X_i > Y_j \text{ かつ } X_{i’} > Y_j) = \frac{1}{3} $$

同様の対称性の議論から、$Y_j$ が最小になる確率は $1/3$ です。

$$ \text{Cov}(W_{ij}, W_{i’j}) = \frac{1}{3} – \frac{1}{4} = \frac{1}{12} $$

このケースは $\binom{n_1}{2} \cdot n_2 \cdot 2 = n_1(n_1-1)n_2$ 個あります。

ケース4: $i \neq i’$, $j \neq j’$(共有なし)

4つの値が帰無仮説のもとで交換可能であるため、

$$ E[W_{ij}W_{i’j’}] = P(X_i > Y_j)P(X_{i’} > Y_{j’}) = \frac{1}{4} $$

$$ \text{Cov}(W_{ij}, W_{i’j’}) = 0 $$

すべてをまとめると、

$$ \begin{align} \text{Var}(U_1) &= n_1 n_2 \cdot \frac{1}{4} + n_1 n_2(n_2-1) \cdot \frac{1}{12} + n_1(n_1-1)n_2 \cdot \frac{1}{12} \\ &= \frac{n_1 n_2}{4} + \frac{n_1 n_2(n_2 – 1)}{12} + \frac{n_1(n_1 – 1)n_2}{12} \\ &= \frac{3 n_1 n_2 + n_1 n_2(n_2 – 1) + n_1(n_1 – 1)n_2}{12} \\ &= \frac{n_1 n_2 (3 + n_2 – 1 + n_1 – 1)}{12} \\ &= \frac{n_1 n_2 (n_1 + n_2 + 1)}{12} \end{align} $$

正規近似

$n_1, n_2$ がともに十分大きい場合(一般に各群10以上)、中心極限定理により

$$ Z = \frac{U_1 – \frac{n_1 n_2}{2}}{\sqrt{\frac{n_1 n_2(n_1 + n_2 + 1)}{12}}} \approx N(0, 1) $$

同順位がある場合の分散補正

同順位グループが $g$ 個あり、$j$ 番目のグループの大きさが $t_j$ のとき、

$$ \text{Var}(U_1) = \frac{n_1 n_2}{12}\left(n_1 + n_2 + 1 – \frac{\sum_{j=1}^{g}(t_j^3 – t_j)}{(n_1 + n_2)(n_1 + n_2 – 1)}\right) $$

パラメトリック検定との比較

漸近相対効率(ARE)

正規分布からのデータに対して、ノンパラメトリック検定の効率をパラメトリック検定と比較する指標として 漸近相対効率(Asymptotic Relative Efficiency, ARE) があります。

$$ \text{ARE} = \frac{n_{\text{nonpara}}}{n_{\text{para}}} $$

ここで $n_{\text{nonpara}}$ と $n_{\text{para}}$ は、同じ検出力を得るために必要な標本サイズです。

正規分布のデータに対するAREは次のように知られています。

比較 ARE
ウィルコクソン vs 対応t検定 $3/\pi \approx 0.955$
マンホイットニー vs 2標本t検定 $3/\pi \approx 0.955$

つまり、正規分布のもとでもノンパラメトリック検定の効率は95.5%程度であり、効率の損失はごくわずかです。一方、分布が正規分布から外れると、ノンパラメトリック検定の方が効率的になります。

選択の指針

  • データが正規分布に近い → パラメトリック検定の方が検出力が若干高い
  • データが正規分布から外れる → ノンパラメトリック検定の方が頑健で信頼性が高い
  • 迷ったら → ノンパラメトリック検定を使う(ARE $\approx 0.955$ なので損失は小さい)

具体例

数値例: ウィルコクソンの符号付き順位検定

あるダイエットプログラムの前後で、8名の体重変化を測定しました。

被験者 差 $D_i$ $|D_i|$ 順位 $R_i$ 符号付き順位
1 78 75 -3 3 3.5 -3.5
2 85 82 -3 3 3.5 -3.5
3 92 88 -4 4 5.5 -5.5
4 70 71 +1 1 1 +1
5 88 84 -4 4 5.5 -5.5
6 95 89 -6 6 7 -7
7 80 79 -1 1 1 -1
8 76 68 -8 8 8 -8

$D_i = 0$ のデータはないので、$n’ = 8$ です。同順位がありますが($|D_i| = 3$ と $|D_i| = 4$ がそれぞれ2つ)、ミッドランクを使っています。

$$ W^+ = 1 $$

$$ W^- = 3.5 + 3.5 + 5.5 + 5.5 + 7 + 1 + 8 = 34 $$

確認: $W^+ + W^- = 1 + 34 = 35 = \frac{8 \times 9}{2}$

正規近似を適用します。

$$ E[W^+] = \frac{8 \times 9}{4} = 18 $$

$$ \text{Var}(W^+) = \frac{8 \times 9 \times 17}{24} = 51 $$

$$ Z = \frac{1 – 18}{\sqrt{51}} = \frac{-17}{7.141} \approx -2.380 $$

両側検定のp値は $p = 2 \times \Phi(-2.380) \approx 0.017$ です。有意水準5%で帰無仮説を棄却し、ダイエットプログラムには有意な効果があると結論します。

Pythonでの実装

ウィルコクソンの符号付き順位検定のスクラッチ実装

import numpy as np
from scipy import stats

def wilcoxon_signed_rank_test(x, y=None, alternative='two-sided'):
    """
    ウィルコクソンの符号付き順位検定のスクラッチ実装

    Parameters
    ----------
    x : array-like
        群1のデータ(対応ありの場合)、または差のデータ
    y : array-like or None
        群2のデータ(対応ありの場合)
    alternative : str
        'two-sided', 'greater', 'less'

    Returns
    -------
    W_plus : float
        正の順位和 W+
    z_stat : float
        正規近似のZ統計量
    p_value : float
        p値
    """
    x = np.array(x, dtype=float)

    if y is not None:
        y = np.array(y, dtype=float)
        d = x - y
    else:
        d = x

    # D_i = 0 を除外
    d = d[d != 0]
    n = len(d)

    if n == 0:
        return 0, 0, 1.0

    # 絶対値と順位の計算
    abs_d = np.abs(d)

    # 順位をつける(同順位はミッドランク)
    sorted_idx = np.argsort(abs_d)
    ranks = np.zeros(n)
    i = 0
    while i < n:
        # 同じ値のグループを見つける
        j = i + 1
        while j < n and abs_d[sorted_idx[j]] == abs_d[sorted_idx[i]]:
            j += 1
        # ミッドランクを割り当て
        mid_rank = (i + 1 + j) / 2  # 1-indexed
        for k in range(i, j):
            ranks[sorted_idx[k]] = mid_rank
        i = j

    # 符号付き順位和
    W_plus = np.sum(ranks[d > 0])
    W_minus = np.sum(ranks[d < 0])

    # 正規近似
    mu = n * (n + 1) / 4
    sigma_sq = n * (n + 1) * (2 * n + 1) / 24

    # 同順位の補正
    unique_vals, counts = np.unique(abs_d, return_counts=True)
    tie_correction = np.sum(counts * (counts - 1) * (counts + 1)) / 48
    sigma_sq -= tie_correction

    sigma = np.sqrt(sigma_sq)

    # Z統計量
    z_stat = (W_plus - mu) / sigma

    # p値
    if alternative == 'two-sided':
        p_value = 2 * (1 - stats.norm.cdf(np.abs(z_stat)))
    elif alternative == 'greater':
        p_value = 1 - stats.norm.cdf(z_stat)
    elif alternative == 'less':
        p_value = stats.norm.cdf(z_stat)

    return W_plus, z_stat, p_value


# --- 具体例: ダイエットプログラムの効果 ---
before = [78, 85, 92, 70, 88, 95, 80, 76]
after  = [75, 82, 88, 71, 84, 89, 79, 68]

W_plus, z_stat, p_value = wilcoxon_signed_rank_test(before, after, alternative='two-sided')

print("=== ウィルコクソンの符号付き順位検定(スクラッチ実装) ===")
print(f"W+ = {W_plus:.1f}")
print(f"Z統計量 = {z_stat:.4f}")
print(f"p値 = {p_value:.4f}")
print(f"判定 (α=0.05): {'棄却' if p_value < 0.05 else '棄却しない'}")

# --- scipy.stats での比較 ---
stat_scipy, p_scipy = stats.wilcoxon(before, after, alternative='two-sided')
print(f"\n=== scipy.stats.wilcoxon の結果 ===")
print(f"統計量 = {stat_scipy:.4f}")
print(f"p値 = {p_scipy:.4f}")

マンホイットニーU検定のスクラッチ実装

import numpy as np
from scipy import stats

def mann_whitney_u_test(x, y, alternative='two-sided'):
    """
    マンホイットニーU検定のスクラッチ実装

    Parameters
    ----------
    x : array-like
        群1のデータ
    y : array-like
        群2のデータ
    alternative : str
        'two-sided', 'greater', 'less'

    Returns
    -------
    U1 : float
        群1のU統計量
    z_stat : float
        正規近似のZ統計量
    p_value : float
        p値
    """
    x = np.array(x, dtype=float)
    y = np.array(y, dtype=float)
    n1 = len(x)
    n2 = len(y)

    # 全データをプールして順位をつける
    combined = np.concatenate([x, y])
    n = n1 + n2

    # 順位の計算(同順位はミッドランク)
    sorted_idx = np.argsort(combined)
    ranks = np.zeros(n)
    i = 0
    while i < n:
        j = i + 1
        while j < n and combined[sorted_idx[j]] == combined[sorted_idx[i]]:
            j += 1
        mid_rank = (i + 1 + j) / 2
        for k in range(i, j):
            ranks[sorted_idx[k]] = mid_rank
        i = j

    # 群1の順位和
    R1 = np.sum(ranks[:n1])

    # U統計量
    U1 = R1 - n1 * (n1 + 1) / 2
    U2 = n1 * n2 - U1

    # 正規近似
    mu = n1 * n2 / 2
    sigma_sq = n1 * n2 * (n1 + n2 + 1) / 12

    # 同順位の補正
    unique_vals, counts = np.unique(combined, return_counts=True)
    tie_correction = np.sum(counts**3 - counts) / ((n) * (n - 1))
    sigma_sq = n1 * n2 / 12 * (n1 + n2 + 1 - tie_correction)

    sigma = np.sqrt(sigma_sq)

    # Z統計量
    z_stat = (U1 - mu) / sigma

    # p値
    if alternative == 'two-sided':
        p_value = 2 * (1 - stats.norm.cdf(np.abs(z_stat)))
    elif alternative == 'greater':
        p_value = 1 - stats.norm.cdf(z_stat)
    elif alternative == 'less':
        p_value = stats.norm.cdf(z_stat)

    return U1, z_stat, p_value


# --- 具体例: 2つの教授法の比較 ---
np.random.seed(42)

# 教授法A: 歪んだ分布(対数正規分布)
group_a = np.random.lognormal(mean=4.0, sigma=0.3, size=20)

# 教授法B: 歪んだ分布(位置をずらす)
group_b = np.random.lognormal(mean=4.2, sigma=0.3, size=22)

U1, z_stat, p_value = mann_whitney_u_test(group_a, group_b, alternative='two-sided')

print("=== マンホイットニーU検定(スクラッチ実装) ===")
print(f"群1: n₁ = {len(group_a)}, 中央値 = {np.median(group_a):.2f}")
print(f"群2: n₂ = {len(group_b)}, 中央値 = {np.median(group_b):.2f}")
print(f"U₁ = {U1:.1f}")
print(f"Z統計量 = {z_stat:.4f}")
print(f"p値 = {p_value:.4f}")
print(f"判定 (α=0.05): {'棄却' if p_value < 0.05 else '棄却しない'}")

# --- scipy.stats での比較 ---
stat_scipy, p_scipy = stats.mannwhitneyu(group_a, group_b, alternative='two-sided')
print(f"\n=== scipy.stats.mannwhitneyu の結果 ===")
print(f"U統計量 = {stat_scipy:.4f}")
print(f"p値 = {p_scipy:.4f}")

パラメトリック検定との比較シミュレーション

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

np.random.seed(42)

n_simulations = 5000
n_per_group = 20
alpha = 0.05

# 異なる分布での検出力比較
distributions = {
    'Normal': lambda loc: np.random.normal(loc, 1, n_per_group),
    'Lognormal': lambda loc: np.random.lognormal(loc, 0.5, n_per_group),
    'Exponential': lambda loc: np.random.exponential(1, n_per_group) + loc,
    't(df=3)': lambda loc: np.random.standard_t(3, n_per_group) + loc,
}

effect_sizes = np.linspace(0, 1.5, 15)

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

for idx, (dist_name, gen_func) in enumerate(distributions.items()):
    powers_t = []
    powers_mw = []

    for effect in effect_sizes:
        reject_t = 0
        reject_mw = 0

        for _ in range(n_simulations):
            group1 = gen_func(0)
            group2 = gen_func(effect)

            # t検定
            _, p_t = stats.ttest_ind(group1, group2)
            if p_t < alpha:
                reject_t += 1

            # マンホイットニーU検定
            _, p_mw = stats.mannwhitneyu(group1, group2, alternative='two-sided')
            if p_mw < alpha:
                reject_mw += 1

        powers_t.append(reject_t / n_simulations)
        powers_mw.append(reject_mw / n_simulations)

    axes[idx].plot(effect_sizes, powers_t, 'b-o', linewidth=2, markersize=5, label='t-test')
    axes[idx].plot(effect_sizes, powers_mw, 'r-s', linewidth=2, markersize=5, label='Mann-Whitney U')
    axes[idx].axhline(y=alpha, color='gray', linestyle=':', linewidth=1)
    axes[idx].set_xlabel('Effect Size (Location Shift)', fontsize=11)
    axes[idx].set_ylabel('Power / Type I Error', fontsize=11)
    axes[idx].set_title(f'{dist_name} Distribution', fontsize=13)
    axes[idx].legend(fontsize=10)
    axes[idx].grid(True, alpha=0.3)
    axes[idx].set_ylim(0, 1.05)

plt.suptitle(f'Power Comparison: t-test vs Mann-Whitney U (n = {n_per_group} per group)',
             fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

正規性からの逸脱度と検定の頑健性

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

np.random.seed(42)

n_simulations = 5000
n_per_group = 15
alpha = 0.05

# 外れ値の割合を変えて第1種の過誤率を確認
contamination_rates = np.linspace(0, 0.4, 20)

type1_t = []
type1_mw = []

for contam in contamination_rates:
    reject_t = 0
    reject_mw = 0

    for _ in range(n_simulations):
        # 汚染正規分布(H0のもと: 両群とも同じ分布)
        n_outliers = int(n_per_group * contam)
        n_normal = n_per_group - n_outliers

        # 群1
        group1 = np.concatenate([
            np.random.normal(0, 1, n_normal),
            np.random.normal(0, 10, n_outliers)  # 外れ値
        ]) if n_outliers > 0 else np.random.normal(0, 1, n_per_group)

        # 群2
        group2 = np.concatenate([
            np.random.normal(0, 1, n_normal),
            np.random.normal(0, 10, n_outliers)
        ]) if n_outliers > 0 else np.random.normal(0, 1, n_per_group)

        # t検定
        _, p_t = stats.ttest_ind(group1, group2)
        if p_t < alpha:
            reject_t += 1

        # マンホイットニーU検定
        _, p_mw = stats.mannwhitneyu(group1, group2, alternative='two-sided')
        if p_mw < alpha:
            reject_mw += 1

    type1_t.append(reject_t / n_simulations)
    type1_mw.append(reject_mw / n_simulations)

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

ax.plot(contamination_rates * 100, type1_t, 'b-o', linewidth=2, markersize=6,
        label='t-test')
ax.plot(contamination_rates * 100, type1_mw, 'r-s', linewidth=2, markersize=6,
        label='Mann-Whitney U')
ax.axhline(y=alpha, color='gray', linestyle='--', linewidth=1.5,
           label=f'Nominal $\\alpha$ = {alpha}')

ax.set_xlabel('Contamination Rate (%)', fontsize=13)
ax.set_ylabel('Type I Error Rate', fontsize=13)
ax.set_title('Robustness to Outliers: Type I Error Rate under $H_0$', fontsize=14)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_ylim(0, 0.15)

plt.tight_layout()
plt.show()

まとめ

本記事では、ノンパラメトリック検定の理論と実装について解説しました。

  • ノンパラメトリック検定はデータの分布を仮定せず、順位に基づいて検定を行う方法
  • ウィルコクソンの符号付き順位検定: 対応のある2群の比較。統計量 $W^+ = \sum_{D_i > 0} R_i$ を用い、$E[W^+] = n'(n’+1)/4$, $\text{Var}(W^+) = n'(n’+1)(2n’+1)/24$
  • マンホイットニーU検定: 独立な2群の比較。統計量 $U_1 = R_1 – n_1(n_1+1)/2$ を用い、$E[U_1] = n_1 n_2/2$, $\text{Var}(U_1) = n_1 n_2(n_1+n_2+1)/12$
  • 大標本では正規近似が適用でき、標準正規分布を参照して p 値を計算
  • 正規分布のデータに対しても ARE $\approx 0.955$ で、効率の損失は小さい

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