マン・ホイットニーU検定の理論と導出をわかりやすく解説

2つの異なる条件で実験を行ったとき、その結果に「差がある」と言えるかどうかを調べたい場面は非常に多く登場します。たとえば、2つの異なる製造プロセスで作られた製品の品質を比較したり、2つの教育方法による学習効果を比較したりする場面です。

このような二標本の比較に対して、$t$ 検定は最もよく使われる手法ですが、データが正規分布に従うことを前提としています。データが歪んでいたり、外れ値を含んでいたり、順序尺度(「非常に良い」「良い」「普通」…)であったりする場合、正規性の仮定は成り立ちません。

このような場面で使える強力な手法がマン・ホイットニーU検定(Mann-Whitney U test)です。この検定はデータの順位のみを使うため、分布の形状に対して頑健(ロバスト)であり、外れ値の影響を受けにくいという優れた性質を持っています。

マン・ホイットニーU検定を理解すると、以下のような応用が可能です。

  • 医学研究: 副作用の重症度スコア(順序尺度)の群間比較など、データの正規性を仮定できない臨床研究で標準的に使われます
  • 品質管理: 製造プロセスの改善効果を、非正規な品質データで評価できます
  • 社会科学: リッカート尺度(1-5段階評価)のアンケートデータの群間比較に適しています
  • 生態学: 種の個体数や多様性指標など、歪んだ分布を持つ生態データの分析に広く利用されています

本記事の内容

  • U統計量の直感的な意味と数学的定義
  • ウィルコクソン順位和検定との等価性の証明
  • 帰無分布の導出と正規近似
  • 同順位(タイ)の処理
  • Pythonによる実装と$t$検定との比較

前提知識

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

U統計量の直感的な意味

「勝ち負け」で差を測る

マン・ホイットニーU検定の直感を、スポーツの対戦に例えて理解しましょう。

群1に $n_1$ 人、群2に $n_2$ 人いるとします。群1の各メンバーと群2の各メンバーを1対1で「対戦」させます。対戦の勝敗は、観測値の大小で決めます。つまり、群1のメンバー $i$ の値 $X_i$ と群2のメンバー $j$ の値 $Y_j$ を比べて、$X_i > Y_j$ なら「群1の勝ち」と数えます。

全部で $n_1 \times n_2$ 回の対戦があります。U統計量は、この中で「群1が勝った回数」を数えたものです。

$$ \begin{equation} U = \sum_{i=1}^{n_1}\sum_{j=1}^{n_2} \mathbf{1}(X_i > Y_j) \end{equation} $$

ここで $\mathbf{1}(\cdot)$ は指示関数(条件が真なら1、偽なら0)です。

もし2つの群に差がなければ、群1が勝つ確率と群2が勝つ確率は等しく、$U$ は全対戦数 $n_1 n_2$ の半分 $n_1 n_2 / 2$ 付近の値を取るはずです。逆に、$U$ がこの期待値から大きくずれていれば、2つの群には差があると判断できます。

確率的優越性の尺度

U統計量を全対戦数で割った量は、確率的優越性(stochastic superiority)の推定量です。

$$ \begin{equation} \hat{P} = \frac{U}{n_1 n_2} \approx P(X > Y) \end{equation} $$

つまり、U統計量は「群1からランダムに選んだ値が、群2からランダムに選んだ値より大きい確率」を推定しています。帰無仮説のもとでは $P(X > Y) = 0.5$ であり、$\hat{P}$ が $0.5$ から離れるほど2群に差があると判断します。

この解釈は非常に直感的で、効果の大きさの指標としても有用です。$\hat{P} = 0.7$ は「群1の値が群2の値より大きい確率が $70\%$」という明快な意味を持ちます。

U統計量の定義を理解したところで、次にこれがウィルコクソンの順位和検定と数学的に等価であることを示しましょう。

ウィルコクソン順位和検定との等価性

ウィルコクソンの順位和統計量

フランク・ウィルコクソン(Frank Wilcoxon)は1945年に、順位に基づく二標本検定を提案しました。マン(H. B. Mann)とホイットニー(D. R. Whitney)は1947年にU統計量に基づく検定を独立に提案しました。両者は本質的に同じ検定であり、検定統計量の間に簡単な線形関係があります。

ウィルコクソンの方法では、2つの群のデータを合わせて $n = n_1 + n_2$ 個のデータに順位を付けます。最小値に順位1、次に小さい値に順位2、…、最大値に順位 $n$ を与えます。

群1のデータの順位を $R_1, R_2, \dots, R_{n_1}$ とすると、ウィルコクソン順位和統計量 $W$ は次のように定義されます。

$$ \begin{equation} W = \sum_{i=1}^{n_1} R_i \end{equation} $$

群1の値が全体的に大きければ、群1のデータは大きな順位を持ち、$W$ は大きくなります。逆に群1の値が小さければ $W$ は小さくなります。

等価性の証明

U統計量とウィルコクソン順位和統計量の間には、次の関係が成り立ちます。

$$ \begin{equation} U = W – \frac{n_1(n_1 + 1)}{2} \end{equation} $$

この等式を証明しましょう。群1の $i$ 番目のデータ $X_i$ の全体での順位 $R_i$ は、$X_i$ より小さい値の個数に1を加えたものです。

$$ R_i = \text{(群1で } X_i \text{ より小さい値の個数)} + \text{(群2で } X_i \text{ より小さい値の個数)} + 1 $$

ここで、同順位がないとすると、「群2で $X_i$ より小さい値の個数」は $\sum_{j=1}^{n_2}\mathbf{1}(X_i > Y_j)$ に等しいです。また、群1内で $X_i$ より小さい値の個数の総和は、群1内の順位の和から $n_1$ を引いたものです。

群1内だけで順位を付けたとき、群1の内部順位の和は $\frac{n_1(n_1+1)}{2}$ です($1 + 2 + \cdots + n_1$ の和)。

全体の順位和 $W$ を書き下すと、

$$ W = \sum_{i=1}^{n_1} R_i = \sum_{i=1}^{n_1}\left[\text{(群1内で} X_i \text{より小さい数)} + \sum_{j=1}^{n_2}\mathbf{1}(X_i > Y_j) + 1\right] $$

右辺の第1項と第3項を合わせると、群1の内部順位の和 $\frac{n_1(n_1+1)}{2}$ になります。第2項はU統計量そのものです。

したがって、

$$ W = \frac{n_1(n_1+1)}{2} + U $$

を得ます。両辺から $\frac{n_1(n_1+1)}{2}$ を引けば、$U = W – \frac{n_1(n_1+1)}{2}$ が示されます。

この関係により、U検定とウィルコクソン順位和検定は全く同じ検定です。一方を棄却するなら他方も必ず棄却され、p値も完全に一致します。

このU統計量がどのような分布に従うかを知るために、帰無分布を導出しましょう。

帰無分布の導出

帰無仮説のもとでの設定

帰無仮説は次のように設定します。

$$ H_0: F_X = F_Y \quad \text{(2群の分布が同一)} $$

帰無仮説のもとでは、$n = n_1 + n_2$ 個のデータはすべて同一の分布から生成されるため、順位 $1, 2, \dots, n$ のうちどの $n_1$ 個が群1に割り当てられるかは等確率です。すべての割り当ては $\binom{n}{n_1}$ 通りあり、それぞれの確率は $1/\binom{n}{n_1}$ です。

U統計量の期待値

帰無仮説のもとで、U統計量の期待値を求めます。$W$ の期待値から出発するのが簡便です。

群1のデータの順位 $R_i$ は、$1, 2, \dots, n$ の中から $n_1$ 個を非復元抽出したものと考えられます。各 $R_i$ の期待値は全体の順位の平均です。

$$ E[R_i] = \frac{1 + 2 + \cdots + n}{n} = \frac{n + 1}{2} $$

したがって、

$$ E[W] = n_1 \cdot \frac{n + 1}{2} = \frac{n_1(n_1 + n_2 + 1)}{2} $$

$U = W – \frac{n_1(n_1+1)}{2}$ の関係を使って、

$$ \begin{equation} E[U] = E[W] – \frac{n_1(n_1+1)}{2} = \frac{n_1(n_1 + n_2 + 1)}{2} – \frac{n_1(n_1+1)}{2} = \frac{n_1 n_2}{2} \end{equation} $$

期待値が $\frac{n_1 n_2}{2}$ であるという結果は、先ほどの直感(帰無仮説のもとでは群1が「勝つ」確率が $1/2$)と完全に整合しています。

U統計量の分散

分散の計算は少し技巧的ですが、$W$ の分散から求めるのが効率的です。非復元抽出の理論から、$W$ の分散は次のように計算されます。

$W = \sum_{i=1}^{n_1} R_i$ であり、各 $R_i$ の分散は

$$ \text{Var}(R_i) = \frac{n^2 – 1}{12} $$

また、異なる $R_i$ と $R_j$($i \neq j$)の共分散は、非復元抽出の相関から

$$ \text{Cov}(R_i, R_j) = -\frac{n + 1}{12} $$

となります。したがって、$W$ の分散は次のようになります。

$$ \text{Var}(W) = n_1 \cdot \frac{n^2 – 1}{12} + n_1(n_1 – 1) \cdot \left(-\frac{n + 1}{12}\right) $$

$n = n_1 + n_2$ を使って整理します。

$$ \text{Var}(W) = \frac{n_1(n+1)}{12}\left[(n-1) – (n_1-1)\right] = \frac{n_1(n+1)}{12} \cdot n_2 = \frac{n_1 n_2(n+1)}{12} $$

$U = W – \text{定数}$ なので、$U$ の分散も同じです。

$$ \begin{equation} \text{Var}(U) = \frac{n_1 n_2 (n_1 + n_2 + 1)}{12} \end{equation} $$

正規近似

$n_1$ と $n_2$ がともに十分大きい場合、中心極限定理により $U$ は漸近的に正規分布に従います。標準化した統計量は

$$ \begin{equation} Z = \frac{U – E[U]}{\sqrt{\text{Var}(U)}} = \frac{U – \frac{n_1 n_2}{2}}{\sqrt{\frac{n_1 n_2(n_1 + n_2 + 1)}{12}}} \end{equation} $$

帰無仮説のもとで $Z \xrightarrow{d} N(0, 1)$ となります。

一般的な目安として、$n_1$ と $n_2$ がともに8以上であれば正規近似は十分な精度を持つとされています。より小さい標本では、正確な帰無分布(すべての置換を列挙)を使うことが推奨されます。

連続補正

離散分布を連続分布で近似する際の精度を改善するために、連続補正(continuity correction)が用いられることがあります。

$$ Z_c = \frac{|U – \frac{n_1 n_2}{2}| – 0.5}{\sqrt{\frac{n_1 n_2(n_1 + n_2 + 1)}{12}}} $$

分子から $0.5$ を引くことで、離散分布の確率 $P(U \geq u)$ を連続分布で近似する際の誤差を軽減します。ただし、$n_1, n_2$ がそれぞれ20以上であれば、連続補正の有無はほとんど影響しません。

同順位がある場合の処理について、次のセクションで見ていきましょう。

同順位(タイ)の処理

同順位が存在する場合の問題

実際のデータでは、同じ値を持つ観測値(同順位、タイ)が存在することがあります。特に、離散的な変数(順序尺度データなど)では同順位が頻繁に発生します。

標準的な対処法は、中間順位(midrank)を使うことです。同じ値を持つデータには、それらが占める順位の平均を割り当てます。

例えば、5つのデータ $\{3, 5, 5, 7, 9\}$ の場合、値5のデータは順位2と順位3を占めるため、中間順位 $(2 + 3) / 2 = 2.5$ を割り当てます。順位は $1, 2.5, 2.5, 4, 5$ となります。

同順位補正を含む分散

同順位がある場合、U統計量の分散に補正が必要です。同順位グループが $g$ 個あり、$k$ 番目のグループに $t_k$ 個のデータが含まれるとします。

補正付きの分散は次のようになります。

$$ \begin{equation} \text{Var}(U) = \frac{n_1 n_2}{12}\left[(n + 1) – \frac{\sum_{k=1}^{g}(t_k^3 – t_k)}{n(n-1)}\right] \end{equation} $$

同順位がない場合(すべての $t_k = 1$)は $\sum(t_k^3 – t_k) = 0$ となり、補正なしの分散に一致します。同順位が多いほど分散は小さくなり、これを無視すると検定が保守的になります(p値が実際より大きくなる)。

理論的な導出を一通り終えたので、次にPythonで実装し、数値的に検証しましょう。

Pythonによる実装

U検定のスクラッチ実装

まず、マン・ホイットニーU検定をゼロから実装します。

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

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

    Parameters
    ----------
    x, y : array-like  2群のデータ
    alternative : str  'two-sided', 'greater', 'less'
    continuity : bool  連続補正を使うか

    Returns
    -------
    U : float  U統計量(群1に基づく)
    z : float  標準化統計量
    p_value : float  p値
    """
    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)
    n1, n2 = len(x), len(y)

    # すべてのペアの比較
    U = 0.0
    for xi in x:
        for yj in y:
            if xi > yj:
                U += 1.0
            elif xi == yj:
                U += 0.5  # 同値の場合は0.5
    # --- 順位を使った方法(等価) ---
    combined = np.concatenate([x, y])
    ranks = stats.rankdata(combined, method="average")
    W = np.sum(ranks[:n1])
    U_from_W = W - n1 * (n1 + 1) / 2

    # --- 帰無分布の期待値と分散 ---
    mu_U = n1 * n2 / 2

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

    # 標準化
    if continuity:
        z = (np.abs(U - mu_U) - 0.5) / np.sqrt(var_U)
    else:
        z = (U - mu_U) / np.sqrt(var_U)

    # p値
    if alternative == "two-sided":
        if continuity:
            p_value = 2 * (1 - stats.norm.cdf(z))
        else:
            p_value = 2 * (1 - stats.norm.cdf(np.abs(z)))
    elif alternative == "greater":
        if continuity:
            z_dir = (U - mu_U - 0.5) / np.sqrt(var_U)
        else:
            z_dir = (U - mu_U) / np.sqrt(var_U)
        p_value = 1 - stats.norm.cdf(z_dir)
    else:
        if continuity:
            z_dir = (U - mu_U + 0.5) / np.sqrt(var_U)
        else:
            z_dir = (U - mu_U) / np.sqrt(var_U)
        p_value = stats.norm.cdf(z_dir)

    return U, z if not continuity else z * np.sign(U - mu_U), p_value

# --- 例: 2つの製造プロセスの品質スコア ---
np.random.seed(42)
process_A = np.array([85, 90, 88, 92, 87, 95, 83, 91, 89, 86])
process_B = np.array([78, 82, 80, 85, 79, 84, 77, 81, 76, 83])

U_val, z_val, p_val = mann_whitney_u_test(process_A, process_B,
                                           alternative="two-sided",
                                           continuity=False)

# SciPyとの比較
U_scipy, p_scipy = stats.mannwhitneyu(process_A, process_B,
                                       alternative="two-sided")

print("=" * 55)
print("マン・ホイットニーU検定の結果")
print("=" * 55)
print(f"プロセスA: 平均={np.mean(process_A):.1f}, "
      f"中央値={np.median(process_A):.1f}")
print(f"プロセスB: 平均={np.mean(process_B):.1f}, "
      f"中央値={np.median(process_B):.1f}")
print(f"\n自作実装:  U = {U_val:.1f}, p = {p_val:.6f}")
print(f"SciPy:     U = {U_scipy:.1f}, p = {p_scipy:.6f}")
print(f"\n確率的優越性 P(A>B) = {U_val / (len(process_A) * len(process_B)):.3f}")

上の結果から、自作実装とSciPyの結果が一致していることが確認できます。確率的優越性 $P(A > B)$ の推定値は、プロセスAからランダムに選んだ品質スコアがプロセスBのものより大きい確率を直接表現しており、効果の大きさの直感的な指標になっています。

帰無分布の可視化

小さなサンプルサイズで、U統計量の正確な帰無分布と正規近似を比較します。

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

def exact_u_distribution(n1, n2):
    """正確なU統計量の帰無分布を列挙法で計算"""
    n = n1 + n2
    ranks = np.arange(1, n + 1)
    u_values = []

    for idx in combinations(range(n), n1):
        W = sum(ranks[i] for i in idx)
        U = W - n1 * (n1 + 1) / 2
        u_values.append(U)

    return np.array(u_values)

# n1=5, n2=5 の場合
n1, n2 = 5, 5
u_dist = exact_u_distribution(n1, n2)

# 理論的な期待値と分散
mu_U = n1 * n2 / 2
var_U = n1 * n2 * (n1 + n2 + 1) / 12

fig, axes = plt.subplots(1, 2, figsize=(13, 5))

# ヒストグラム
ax = axes[0]
u_unique, u_counts = np.unique(u_dist, return_counts=True)
u_probs = u_counts / len(u_dist)
ax.bar(u_unique, u_probs, width=0.8, alpha=0.7, color="steelblue",
       edgecolor="white", label="Exact distribution")

# 正規近似
u_cont = np.linspace(0, n1*n2, 200)
norm_pdf = stats.norm.pdf(u_cont, mu_U, np.sqrt(var_U))
ax.plot(u_cont, norm_pdf, "r-", linewidth=2, label="Normal approximation")

ax.set_xlabel("U statistic", fontsize=12)
ax.set_ylabel("Probability", fontsize=12)
ax.set_title(f"U distribution (n1={n1}, n2={n2})\n"
             rf"$\mu_U={mu_U:.1f}$, $\sigma_U={np.sqrt(var_U):.2f}$",
             fontsize=12)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)

# Q-Qプロット
ax = axes[1]
theoretical_quantiles = stats.norm.ppf(
    (np.arange(1, len(u_dist) + 1) - 0.5) / len(u_dist)
)
sorted_u = np.sort(u_dist)
standardized = (sorted_u - mu_U) / np.sqrt(var_U)
ax.scatter(theoretical_quantiles, standardized, s=1, alpha=0.3, color="steelblue")
lim = max(abs(theoretical_quantiles.min()), abs(theoretical_quantiles.max()))
ax.plot([-lim, lim], [-lim, lim], "r-", linewidth=1.5, label="y = x")
ax.set_xlabel("Theoretical quantiles (standard normal)", fontsize=11)
ax.set_ylabel("Standardized U", fontsize=11)
ax.set_title("Q-Q plot: U vs Normal", fontsize=12)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_aspect("equal")

plt.tight_layout()
plt.savefig("mann_whitney_null_dist.png", dpi=150, bbox_inches="tight")
plt.show()

print(f"並べ替えの総数: C({n1+n2},{n1}) = {len(u_dist)}")
print(f"Uの範囲: {u_dist.min():.0f} ~ {u_dist.max():.0f}")

このグラフから、以下の点が読み取れます。

  1. 正確な帰無分布(青い棒グラフ)は離散的で対称な形状をしている。U統計量は $0$ から $n_1 n_2 = 25$ までの整数値を取り、$12.5$ を中心に対称です。$\binom{10}{5} = 252$ 通りの等確率な置換から生成されるため、分布は左右対称になります

  2. 正規近似(赤線)は $n_1 = n_2 = 5$ という小さな標本でも適度に良い近似を与えている。分布の中心部では正規近似がよく一致しています。ただし、裾の部分では離散性の影響で多少のずれがあります

  3. Q-Qプロット(右図)は直線に近い。中心部はほぼ完全に直線上にあり、両端でわずかなずれが見られます。$n_1, n_2$ を増やせばこのずれはさらに小さくなります

$t$検定との検出力比較

正規分布と非正規分布の両方で、マン・ホイットニーU検定と$t$検定の検出力を比較します。

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

np.random.seed(42)

n1, n2 = 20, 20
n_sim = 3000
alpha = 0.05
shifts = np.linspace(0, 2.0, 12)

distributions = {
    "Normal": lambda shift, n: np.random.randn(n) + shift,
    "Exponential": lambda shift, n: np.random.exponential(1, n) + shift,
    "Cauchy": lambda shift, n: np.random.standard_cauchy(n) + shift,
}

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

for ax, (dist_name, gen_func) in zip(axes, distributions.items()):
    power_mw = []
    power_t = []

    for shift in shifts:
        reject_mw = 0
        reject_t = 0

        for _ in range(n_sim):
            x = gen_func(shift, n1)
            y = gen_func(0, n2)

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

            # Welch t検定
            _, p_t = stats.ttest_ind(x, y, equal_var=False)
            if p_t < alpha:
                reject_t += 1

        power_mw.append(reject_mw / n_sim)
        power_t.append(reject_t / n_sim)

    ax.plot(shifts, power_mw, "b-o", markersize=5, linewidth=1.5,
            label="Mann-Whitney U")
    ax.plot(shifts, power_t, "r-s", markersize=5, linewidth=1.5,
            label="Welch t-test")
    ax.axhline(alpha, color="gray", linestyle="--", linewidth=0.8)
    ax.set_xlabel("Location shift", fontsize=11)
    ax.set_ylabel("Power", fontsize=11)
    ax.set_title(dist_name, fontsize=13)
    ax.legend(fontsize=9)
    ax.grid(True, alpha=0.3)
    ax.set_ylim(0, 1.05)

plt.tight_layout()
plt.savefig("mann_whitney_power.png", dpi=150, bbox_inches="tight")
plt.show()

検出力の比較から、以下の重要な結果が読み取れます。

  1. 正規分布(左図): $t$検定がわずかに高い検出力を示しています。これは予想通りの結果です。正規分布のもとでは$t$検定が最も強力な検定であり、マン・ホイットニーU検定は順位への変換で情報を一部失うため、わずかに劣ります。しかし、漸近相対効率(ARE)は $3/\pi \approx 0.955$ であり、検出力の損失は $5\%$ 未満です

  2. 指数分布(中央図): 歪んだ分布では、マン・ホイットニーU検定と$t$検定の検出力はほぼ同等か、マン・ホイットニーU検定がやや優位です。指数分布は右に裾が長い分布であり、外れ値が$t$検定の性能に影響を与える可能性がありますが、$n = 20$ ではその影響は限定的です

  3. コーシー分布(右図): 裾の非常に重い分布では、マン・ホイットニーU検定が$t$検定を大幅に上回ります。コーシー分布は期待値が存在しないほど裾が重い分布であり、標本平均が不安定になるため$t$検定の検出力が著しく低下します。一方、順位に基づくマン・ホイットニーU検定は外れ値の影響を受けにくく、安定した検出力を維持しています

確率的優越性の可視化

最後に、U統計量の「確率的優越性」の解釈を可視化します。

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

np.random.seed(42)

# 2群のデータ
n1, n2 = 50, 50
x = np.random.normal(loc=1.5, scale=1.0, size=n1)
y = np.random.normal(loc=0.0, scale=1.0, size=n2)

# U統計量と確率的優越性
U_val, p_val = stats.mannwhitneyu(x, y, alternative="two-sided")
prob_superiority = U_val / (n1 * n2)

fig, axes = plt.subplots(1, 2, figsize=(13, 5.5))

# データの分布
ax = axes[0]
bins = np.linspace(min(x.min(), y.min()) - 0.5,
                   max(x.max(), y.max()) + 0.5, 30)
ax.hist(x, bins=bins, density=True, alpha=0.5, color="blue",
        edgecolor="white", label=f"Group 1 (median={np.median(x):.2f})")
ax.hist(y, bins=bins, density=True, alpha=0.5, color="orange",
        edgecolor="white", label=f"Group 2 (median={np.median(y):.2f})")
ax.set_xlabel("Value", fontsize=12)
ax.set_ylabel("Density", fontsize=12)
ax.set_title("Data distributions", fontsize=13)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)

# ペアの比較の可視化(サンプリング)
ax = axes[1]
n_pairs = 500
idx_x = np.random.randint(0, n1, n_pairs)
idx_y = np.random.randint(0, n2, n_pairs)
x_sample = x[idx_x]
y_sample = y[idx_y]

colors = np.where(x_sample > y_sample, "blue",
                  np.where(x_sample < y_sample, "orange", "gray"))
ax.scatter(y_sample, x_sample, c=colors, alpha=0.3, s=15, edgecolors="none")
lim = max(abs(x.max()), abs(y.max())) + 1
ax.plot([-4, 6], [-4, 6], "k--", linewidth=1, alpha=0.5)
ax.set_xlabel("Group 2 value", fontsize=12)
ax.set_ylabel("Group 1 value", fontsize=12)
ax.set_title(f"Pairwise comparison\n"
             f"P(X>Y) = {prob_superiority:.3f}, p = {p_val:.4e}",
             fontsize=12)
ax.grid(True, alpha=0.3)

# 凡例
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor="blue", alpha=0.5, label="X > Y (Group 1 wins)"),
                   Patch(facecolor="orange", alpha=0.5, label="X < Y (Group 2 wins)")]
ax.legend(handles=legend_elements, fontsize=9)

plt.tight_layout()
plt.savefig("mann_whitney_superiority.png", dpi=150, bbox_inches="tight")
plt.show()

この可視化から、以下のことが確認できます。

  1. 左図のヒストグラムは2群の分布の重なりを示している。群1(青)は群2(オレンジ)に比べて右側にシフトしていますが、完全には分離していません。この重なりの程度が確率的優越性 $P(X > Y)$ の値に反映されます

  2. 右図の散布図は全ペア比較の結果を視覚化している。対角線より上(青い点)は群1の値が大きいペア、対角線より下(オレンジの点)は群2の値が大きいペアです。青い点が多ければ $P(X > Y) > 0.5$ であり、群1が「確率的に優越」していることを意味します

  3. 確率的優越性は効果量の直感的な指標である。$P(X > Y) = 0.5$ は差なし、$P(X > Y) = 1.0$ は完全な分離を意味します。この指標はp値とは異なり、効果の「大きさ」を直接表現しています

マン・ホイットニーU検定の注意点

検定が測っているもの

マン・ホイットニーU検定は、しばしば「中央値の差の検定」と説明されますが、これは正確ではありません。正確には、U検定は確率的優越性 $P(X > Y) = 0.5$ を検定しています。

$P(X > Y) = 0.5$ と「中央値が等しい」は、一般には異なる条件です。2つの分布が同じ形状で位置のみ異なる(location shift model)場合に限り、両者は同値になります。分散やスケールが異なる場合、中央値が等しくても $P(X > Y) \neq 0.5$ となることがあります。

一方向の差と両方向の差

対立仮説として「群1が群2より大きい」(片側検定)または「群1と群2は異なる」(両側検定)を設定できます。片側検定は効果の方向が事前に分かっている場合に適しています。

大標本での効率

正規分布のもとでの漸近相対効率(ARE)は $\frac{3}{\pi} \approx 0.955$ です。これは、$t$検定と同じ検出力を得るためにU検定が必要とする標本サイズが、$t$検定の $\frac{\pi}{3} \approx 1.047$ 倍であることを意味します。つまり、正規分布の場合でもU検定の効率損失は $5\%$ 未満であり、実用上ほとんど問題になりません。

一方、ロジスティック分布やラプラス分布など裾の重い分布では、U検定のAREが1を超えることがあり、$t$検定よりも効率的になります。

まとめ

本記事では、マン・ホイットニーU検定の理論を直感的な理解から導出まで解説しました。

  • U統計量は「全ペア比較」の勝率を測定します。$n_1 \times n_2$ 個のペアの中で群1が勝った割合が確率的優越性 $P(X > Y)$ の推定量です
  • ウィルコクソン順位和検定との等価性: $U = W – \frac{n_1(n_1+1)}{2}$ の関係により、2つの検定は同一です
  • 帰無分布: $E[U] = \frac{n_1 n_2}{2}$、$\text{Var}(U) = \frac{n_1 n_2(n+1)}{12}$ であり、$n_1, n_2 \geq 8$ で正規近似が実用的です
  • $t$検定との比較: 正規分布のもとでもARE $= 0.955$ と効率損失は小さく、裾の重い分布ではU検定の方が高い検出力を示します
  • 注意点: U検定は「中央値の差」ではなく「確率的優越性」の検定です。分布の形状が異なる場合にはこの区別が重要になります

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