論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
Kruskal-Wallis検定
Kruskal-Wallis Test (H)
3群以上の中央値に差があるかを順位ベースで検定。ANOVAのノンパラ版。正規性を仮定しない。
仮説検定HKW検定クラスカルウォリス

🔖 キーワード索引 — 拡張版

Kruskal-Wallis 検定に関する用語を、 仮定・検定統計量・事後検定 別に索引化します。

カテゴリキーワード(日本語)キーワード(英語)
基本概念ノンパラメトリック検定、 順位和、 H統計量、 自由度(k−1)non-parametric, rank sum, H statistic, df
理論基盤順位(ランク)、 タイの補正、 カイ二乗近似、 漸近分布rank, tie correction, chi-square approximation
仮説帰無仮説(全群同じ分布)、 対立仮説、 中央値の差H0, H1, median difference, location shift
事後検定Dunn検定、 ペアワイズMann-Whitney、 Bonferroni補正、 Conover-ImanDunn's test, pairwise Mann-Whitney, Bonferroni, Conover
関連検定Mann-Whitney U、 ANOVA、 Friedman検定、 Welch ANOVAMann-Whitney U, ANOVA, Friedman, Welch ANOVA
効果量イプシロン二乗、 イータ二乗、 ベイズファクターepsilon², eta², Bayes factor

💡 30秒で分かる結論

🍰 まずはやさしく

3つ以上のグループを比べる道具です。

中央値(真ん中の値)に差があるか調べます。

部活ごとのスマホ利用時間を比べる時に使います。

この検定の結論を短くまとめます。

👁️ 直感 — Kruskal-Wallisは「3群以上のノンパラ ANOVA」

Kruskal-Wallis 検定は、 3つ以上の独立群に差があるかをノンパラメトリックに判定。 ANOVA のロバスト版。

使い時

📍 あなたが今見ているもの

🍰 まずはやさしく

順位を使って差を判定する方法です。

データの分布がバラバラな時に使います。

地域ごとの人口の差を調べる時に役立ちます。

この検定がどう使われるかを説明します。

論文中に 「Kruskal-Wallis検定」として登場する用語。

Kruskal-Wallis検定 とは:3群以上の中央値に差があるかを順位ベースで検定。ANOVAのノンパラ版。正規性を仮定しない。

本ページでは Kruskal-Wallis 検定を扱う。 ANOVA (一元配置分散分析) のノンパラメトリック版で、 3 群以上の代表値 (中央値) に差があるかを順位ベースで検定する。 SSDSE-B-2026 で地域ブロック別 (北海道・東北・関東・中部・近畿・中国・四国・九州沖縄) の合計特殊出生率や人口指標に差があるかを検定する例で具体化する。

Kruskal-Wallis 検定の流れは「全データを混ぜて順位付け → 群ごとの順位和を計算 → H 統計量を計算 → カイ二乗分布で p 値判定」。 有意なら事後検定 (Dunn 法など) で「どの群とどの群が違うか」を特定する。 正規性を仮定しないので、 ANOVA より頑健だが、 検出力はやや劣る。

🎨 直感で掴む — 3 群以上を「順位の重心」で比較する

🍰 まずはやさしく

順位の重心(平均的な位置)を比べるイメージです。

外れ値に振り回されずに差を見つけます。

テストの点数を順位にしてグループで比べます。

直感的に仕組みを理解するための解説です。

Kruskal-Wallis を一言で表すと「データを全部 1 列に並べて順位を付け、 群ごとに順位の重心 (平均順位) を計算し、 重心が群間でバラついていれば有意」。 物理で例えるなら、 3 つの異なる重さの球を秤に載せて、 「重心が偏っているか?」を見るのと同じ発想です。

「ANOVA は平均の比較、 KW は順位の重心の比較」と覚えると、 両者の使い分けが明確になります。

🔧 タイ補正 (tie correction) — H 統計量を歪める「同順位の罠」

SSDSE-B-2026 で 47 都道府県の人口 (連続値) を Kruskal-Wallis にかける場合、 同じ値はほぼ発生しません。 ところが、 死亡率や合計特殊出生率など 小数点 1 桁で打ち切られた値では「1.30」「1.30」のような同順位 (tie) が頻発します。 タイがあると平均順位の分散が小さくなり、 H 統計量を 過小評価するため、 タイ補正 (correction for ties) を必ず行います。

📐 タイ補正の公式

$$H_\text{corrected} = \frac{H}{1 - \frac{\sum_{j=1}^{g}(t_j^3 - t_j)}{N^3 - N}}$$

🔬 数式を言葉で読み解く

🧮 実値で計算: SSDSE 合計特殊出生率の例

このコードでやること: SSDSE-B-2026 の合計特殊出生率 (TFR) を 8 地方ブロックに分け、 タイ補正の効果を可視化する。 TFR は小数点 2 桁打ち切りでタイが発生しやすい。

📥 入力例 (SSDSE-B-2026 抜粋):

Code Prefecture A1101(総人口) A4103(TFR) R01000 北海道 5092000 1.06 R13000 東京都 14086000 0.99 R27000 大阪府 8763000 1.19 R47000 沖縄県 1468000 1.60
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# SSDSE-B-2026 で TFR の Kruskal-Wallis (タイ補正の効果検証)
import pandas as pd
from scipy import stats
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
# 最新年 (2023) の 47 都道府県を抽出
latest = df[df['SSDSE-B-2026'] == 2023]

# 8 地方ブロック割当 (都道府県コード R01000..R47000)
def region(code):
    n = int(code[1:3])
    if n == 1: return '北海道'
    if 2 <= n <= 7: return '東北'
    if 8 <= n <= 14: return '関東'
    if 15 <= n <= 23: return '中部'
    if 24 <= n <= 30: return '近畿'
    if 31 <= n <= 35: return '中国'
    if 36 <= n <= 39: return '四国'
    return '九州沖縄'

latest = latest.assign(地方=latest['Code'].apply(region))
# A4103 = 合計特殊出生率 (TFR)
groups = [g['A4103'].dropna().values for _, g in latest.groupby('地方')]

H, p = stats.kruskal(*groups)
print(f'H 統計量 = {H:.3f}')
print(f'p 値    = {p:.4f}')

# タイ率を計算
all_vals = np.concatenate(groups)
from collections import Counter
counts = Counter(all_vals)
ties = [c for c in counts.values() if c > 1]
print(f'タイの個数 = {sum(ties) - len(ties)}/{len(all_vals)} = {(sum(ties)-len(ties))/len(all_vals)*100:.1f}%')

📤 実行例:

H 統計量 = 33.476 p 値 = 0.0000 タイの個数 = 16/47 = 34.0%

💬 タイが 34% もあるため、 scipy.stats.kruskal は内部でタイ補正済みの H (33.48) を返している。 補正なしで手計算すると H は 33.44 とわずかに小さくなる。 本ケースは p が極めて小さく (p=0.000022) 判定は変わらないが、 境界ケースではタイ補正の有無で有意判定が逆転しうる。 タイ率 30% 超は要警戒。

🔍 Kruskal-Wallis 有意後の事後検定 — Dunn 法を手計算で理解

「H 検定で p<0.05 → どの群とどの群に差があるのか?」を特定する標準手段が Dunn 検定です。 ペアごとに平均順位差を SE で割った Z 値を計算し、 多重比較補正 (Bonferroni / Holm) を適用します。

📐 Dunn 統計量

$$Z_{ij} = \frac{\bar R_i - \bar R_j}{\sqrt{\frac{N(N+1)}{12}\left(\frac{1}{n_i}+\frac{1}{n_j}\right)}}$$

🔬 数式を言葉で読み解く

🧮 SSDSE 47 県人口 4 地方ブロック比較の Dunn 検定

このコードでやること: 関東 (7 県)・近畿 (7 県)・九州沖縄 (8 県)・東北 (6 県) の人口について Kruskal-Wallis → Dunn 事後で「どのペアに差があるか」を特定。

📥 入力例:

地方 県数 平均人口(千人) 関東 7 6218 近畿 7 3141 九州沖縄 8 1754 東北 6 1386
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# SSDSE-B-2026 4 地方ブロック人口の Kruskal-Wallis + Dunn 事後検定
import pandas as pd
from scipy import stats
import scikit_posthocs as sp

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)

def region4(code):
    n = int(code[1:3])
    if 2 <= n <= 7: return '東北'
    if 8 <= n <= 14: return '関東'
    if 24 <= n <= 30: return '近畿'
    if 40 <= n <= 47: return '九州沖縄'
    return 'その他'

latest['地方'] = latest['Code'].apply(region4)
sub = latest[latest['地方'] != 'その他']
# A1101 = 総人口
H, p = stats.kruskal(*[g['A1101'].values for _, g in sub.groupby('地方')])
print(f'KW: H={H:.2f}, p={p:.4f}')

# Dunn 事後検定 (Bonferroni 補正)
dunn = sp.posthoc_dunn(sub, val_col='A1101', group_col='地方', p_adjust='bonferroni')
print(dunn.round(3))

📤 実行例:

KW: H=11.64, p=0.0087 九州沖縄 東北 近畿 関東 九州沖縄 1.000 1.000 1.00 0.018 東北 1.000 1.000 1.00 0.021 近畿 1.000 1.000 1.00 0.510 関東 0.018 0.021 0.51 1.000

💬 「関東 vs 九州沖縄」(p=0.018) と「関東 vs 東北」(p=0.021) のみが p<0.05。 つまり関東が他地方に比べ突出して人口が多く、 近畿・九州・東北の 3 ブロックの間には統計的な差が見えない。 これは 関東一極集中の事後検定的証拠になる。

⚖️ ANOVA vs Kruskal-Wallis — 検出力 (power) シミュレーション

「正規分布なら ANOVA / 非正規なら Kruskal-Wallis」と機械的に覚えるのは危険です。 実際には: (1) 分布が正規に近いなら ANOVA の方が検出力が高い (約 5%)(2) 裾の重い分布や外れ値があれば KW の方が検出力が高い (時に 30% 以上)(3) サンプル数が小さいときの両者の差は小さい、 という事実があります。

🧮 SSDSE 人口データのブートストラップ検出力

このコードでやること: SSDSE 47 県人口を群間効果サイズ別に bootstrap で再抽出し、 ANOVA と KW の検出力 (p<0.05 になる割合) を比較する。

📥 入力例: SSDSE 47 県人口を母集団とし、 3 群 (各 n=10) を効果サイズ δ で平行移動して抽出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# SSDSE 47 県人口を母集団とした検出力シミュレーション
import pandas as pd, numpy as np
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023]          # 最新年 (2023)
pop = latest['A1101'].values / 1000  # 47 県の実分布 (千人、右に裾長い)

rng = np.random.default_rng(42)
n_per_group = 10
n_iter = 2000
sd_pop = pop.std()

for delta in [0, 0.3, 0.5, 0.8]:
    n_anova_sig = 0
    n_kw_sig = 0
    for _ in range(n_iter):
        g1 = rng.choice(pop, n_per_group, replace=True)
        g2 = rng.choice(pop, n_per_group, replace=True) + delta * sd_pop
        g3 = rng.choice(pop, n_per_group, replace=True) + 2 * delta * sd_pop
        _, p_anova = stats.f_oneway(g1, g2, g3)
        _, p_kw    = stats.kruskal(g1, g2, g3)
        if p_anova < 0.05: n_anova_sig += 1
        if p_kw    < 0.05: n_kw_sig    += 1
    print(f'δ={delta}: ANOVA power={n_anova_sig/n_iter:.3f}, KW power={n_kw_sig/n_iter:.3f}')

📤 実行例 (n_iter=2000):

δ=0: ANOVA power=0.040, KW power=0.048 δ=0.3: ANOVA power=0.220, KW power=0.670 δ=0.5: ANOVA power=0.517, KW power=0.860 δ=0.8: ANOVA power=0.850, KW power=0.961

💬 SSDSE 人口は東京・神奈川など右裾が極端に長い分布。 結果として KW が ANOVA を大きく上回る検出力 (δ=0.3 で +45.0pp、 δ=0.5 で +34.3pp、 δ=0.8 で +11.1pp) を示した。 δ=0 ではどちらも α=0.05 近傍を保つ (第一種の過誤を保持)。 「非正規データには KW」が経験的に正当化される瞬間。

📊 検出力グラフの読み方

効果サイズ δANOVAKruskal-Wallis差 (KW − ANOVA)
0.0 (帰無)0.0400.048+0.008
0.3 (小)0.2200.670+0.450
0.5 (中)0.5170.860+0.343
0.8 (大)0.8500.961+0.111

📏 効果量 (η²) とサンプルサイズ設計

「p<0.05 で有意」だけでは情報不足です。 効果量 (effect size) を併記しないと、 「サンプルが大きすぎてどんな小さな差でも有意」になる罠を見抜けません。 Kruskal-Wallis では イータ二乗 (eta-squared, η²)イプシロン二乗 (ε²) が標準指標です。

📐 効果量の定義

$$\eta^2_H = \frac{H - k + 1}{N - k}, \quad \varepsilon^2_H = \frac{H}{(N^2 - 1)/(N+1)}$$

🔬 数式を言葉で読み解く

🧮 SSDSE 4 地方人口の効果量計算

このコードでやること: 先の Dunn 検定で使った 4 地方ブロック人口比較について効果量 η²・ε² を算出する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Kruskal-Wallis の効果量 η² と ε² を計算
import pandas as pd
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)

def region4(code):
    n = int(code[1:3])
    if 2 <= n <= 7: return '東北'
    if 8 <= n <= 14: return '関東'
    if 24 <= n <= 30: return '近畿'
    if 40 <= n <= 47: return '九州沖縄'
    return 'その他'

latest['地方'] = latest['Code'].apply(region4)
sub = latest[latest['地方'] != 'その他']
groups = [g['A1101'].values for _, g in sub.groupby('地方')]   # A1101 = 総人口
H, p = stats.kruskal(*groups)

N = sum(len(g) for g in groups)
k = len(groups)

eta2 = (H - k + 1) / (N - k)
eps2 = H / ((N**2 - 1) / (N + 1))
print(f'H={H:.2f}, p={p:.4f}')
print(f'η² = {eta2:.3f}  (Cohen 目安: 0.01 小 / 0.06 中 / 0.14 大)')
print(f'ε² = {eps2:.3f}')

📤 実行例:

H=11.64, p=0.0087 η² = 0.360 (Cohen 目安: 0.01 小 / 0.06 中 / 0.14 大) ε² = 0.431

💬 η²=0.360 は「順位分散の約 36% が地方の違いで説明される」を意味し、 Cohen 目安では 非常に大きな効果。 サンプルサイズ N=28 と小さいにもかかわらず p<0.01 で有意になったのは、 効果が大きいから (検出力不足ではない)。

📊 必要サンプルサイズの目安

η² 目安3 群比較 (power=0.8)4 群比較5 群比較
0.01 (小)95610991212
0.06 (中)158181200
0.14 (大)667684

📜 歴史 — Kruskal & Wallis (1952)

📚 適用上のチェックリスト (10 項目)

  1. 3 群以上を比較しているか? 2 群なら Mann-Whitney U を使う
  2. 独立な観察か? 反復測定なら Friedman 検定
  3. 連続変数または順序尺度か? 名義尺度ならカイ二乗検定
  4. 各群のサンプル数が 5 以上か? それ未満は exact 版 (statsmodels) を検討
  5. 分布の形は群間で似ているか? 大きく異なるなら「中央値の差」ではなく「分布の差」と解釈
  6. タイの率は 30% 未満か? 超えるなら Brunner-Munzel など別検定
  7. 事後検定の計画 (Dunn / Conover-Iman) は事前に決めたか?
  8. 多重比較補正 (Bonferroni / Holm / FDR) を適用したか?
  9. 効果量 (η² / ε²) を報告したか? p 値だけは不十分
  10. 検出力 (1−β) は事前計算したか? 有意でなかった場合は「効果なし」ではなく「検出できなかった」

🚫 Kruskal-Wallis のよくある誤用 8 連発

1. 「KW で有意 → 中央値の差」と短絡する。 KW は「分布の確率的優越」を検定しており、 中央値の差そのものを検定しているのではありません。 群間で分布の形 (歪み・分散) が大きく異なる場合、 「中央値は同じだが KW で有意」もあり得ます。 群比較で本当に中央値の差を見たいなら Brown-Mood の中央値検定を併用しましょう。

2. 「サンプルサイズが少ない群」を理由にデータを捨てる。 KW は群サイズが不均衡でも適用可能です。 4-5 サンプルしかない群でも、 順位ベースなので外れ値の影響を受けにくく、 むしろ ANOVA より頑健。 「n が小さいから比較を諦める」前に KW を検討しましょう。

3. 事前に決めずに事後検定を選ぶ (cherry-picking)。 KW で有意になった後、 「どのペアが有意になりやすいか」を見て事後検定を選ぶと、 第一種の過誤が膨張します。 解析計画書 (Statistical Analysis Plan) に Dunn / Conover-Iman / Tukey-Nemenyi のどれを使うかを事前に明記しましょう。

4. 多重比較補正を忘れる。 5 群比較ならペア数は 10 件。 α=0.05 で各ペアを補正なく検定すると、 全体の第一種の過誤率は 1−(0.95)^10 ≈ 40% に膨張。 Bonferroni (保守的) / Holm (中庸) / Benjamini-Hochberg FDR (発見重視) を使い分けましょう。

5. 順序尺度の Likert に強引に KW を使う。 5 段階 Likert は順位として扱えますが、 値が 5 種類しかないのでタイ率が極めて高くなります。 タイ補正を必ず確認し、 場合によっては順序ロジスティック回帰の方が情報量が多くなります。

6. 群間で観察の独立性が崩れている。 同じ個体を 3 時点で測定した「対応あり 3 群比較」に KW を使うのは誤り (Friedman 検定が正解)。 兄弟ペア・隣接県のようにクラスター構造がある場合は混合モデルやクラスター頑健 SE が必要。

7. p 値だけ報告して効果量を書かない。 学会発表で「KW で p=0.03」とだけ書くと、 「実用的に意味のある差なのか不明」とコメントされます。 η² / ε² を必ず併記し、 「小・中・大」の解釈を添えましょう。

8. KW で「有意でない」を「群間に差がない」と結論する。 検出力不足 (β エラー) の可能性があります。 事前に statsmodels.stats.power で必要 N を計算し、 不足していたなら「現在のサンプルでは検出できなかった」と正確に記述します。

🎨 概念図で押さえる

Kruskal-Wallis 検定は「3 群以上の中央位置に差があるか」をノンパラに検定する。 ここでは「順位への変換」「H 統計量の構造」「ANOVA との対応関係」を概念図で押さえる。

生データから順位への変換概念図生データ (左) → 全体順位 (右) への変換関東関西九州原データ (人口など)順位化関東: 順位 7,8,9関西: 順位 4,5,6九州: 順位 1,2,3順位 (外れ値に頑健)'>
図 A. 生データを「全体での順位」に置き換えるのが KW の核心。 外れ値は順位 1 〜 N の範囲に圧縮されるため、 平均値検定 (ANOVA) より頑健になる。
Kruskal-Wallis H 統計量の構造概念図H 統計量: 群間順位平均のばらつきH = 12 / (N(N+1)) × Σ (R_i² / n_i) − 3(N+1)群 i の順位合計R_i = Σ rank群間ばらつきΣ R_i² / n_iH 統計量χ² 近似帰無仮説下では H ~ χ²(k−1)。 k=3 群なら自由度 2 の χ² で p 値を出す。H が大きい → 群間で順位平均が大きく違う → p 値が小さくなる'>
図 B. H 統計量は「各群の順位平均が全体平均からどれだけ離れているか」の和。 帰無仮説では χ²(k−1) で近似される。 群間順位差が大きいほど H は大きくなる。
ANOVA / Welch / KW / Mood 選択フローチャート3 群以上の比較: 検定の選び方各群は正規?Shapiro / 視覚確認YesNo分散等しい?Kruskal-WallisOne-way ANOVAWelch ANOVA'>
図 C. 3 群以上の比較の選び方。 正規性が崩れる、 外れ値が多い、 順序尺度しかない場合は Kruskal-Wallis を選ぶ。 正規 + 等分散なら ANOVA、 不等分散なら Welch ANOVA。

🎮 触って理解する

下の図は 3 群それぞれ 5 個ずつのデータ点(丸)を横軸(値 0〜100)上に並べたものです。 丸を ドラッグ(スマホは指でなぞる)すると、全データを通しで順位付けし直し、 各群の 平均順位H 統計量近似 p 値(χ² 自由度 k−1)が リアルタイムで再計算されます。順位ベースなので、値がいくら極端でも順位は 1〜N の範囲に収まる ——この「外れ値への頑健さ」を、平均ベースの ANOVA の F 値と並べて体感してください。

丸の上の数字はその点の「全体順位」。同じ整数値の点は タイ(同順位)となり、平均順位+タイ補正が自動適用されます。

🧭 直感 — 何を見ているか

⚠️ よくある落とし穴

🚀 発展

関連ページ: ANOVA(分散分析)ノンパラメトリック手法カイ二乗検定p 値外れ値順序尺度効果量

📐 Kruskal-Wallis H統計量

🍰 まずはやさしく

計算式を使って差を数値にする方法です。

統計量(計算結果の数字)を出して判定します。

買い物した金額を順位にして計算に使いましょう。

具体的な計算の手順とルールを学びます。

全データに通し順位 R_i を付けて:

$$ H = \frac{12}{N(N+1)} \sum_{g=1}^{k} \frac{R_g^2}{n_g} - 3(N+1) $$

H は近似的に df = k-1 の χ² 分布に従う。

🎯 有意な場合の事後検定

Kruskal-Wallis で「群間に差あり」と分かった後の、 「どの群間に差があるか」の検定:

🔬 「Kruskal-Wallis」を深く理解する

Kruskal-Wallis の歴史

William Kruskal(米統計学者)と W. Allen Wallis(経済学者)が1952年に提案。 ANOVAの一般化として位置づけられる。

応用例

限界

📝 練習問題 — 理解度チェック

  1. この用語の基本定義を、 自分の言葉で説明できますか?
  2. この手法が使われる典型的なシナリオを3つ挙げられますか?
  3. この手法の前提条件・仮定を確認できますか?
  4. 結果を解釈する際の注意点は何ですか?
  5. 類似手法との違いを説明できますか?
  6. Python(または他言語)で実装できますか?
  7. SSDSE データで応用例を作成できますか?

🧮 SSDSE-B での Kruskal-Wallis — 実値計算例

SSDSE-B-2026(2023 年、 47 都道府県)から、 「3つの地域ブロック(東日本=北海道〜神奈川/中部=新潟〜三重/西日本=滋賀〜沖縄)の合計特殊出生率(A4103)の差」を Kruskal-Wallis で検定します。

① 各群の中央値(実測値)

地域n中央値(TFR)平均順位
東日本141.159.1
中部101.3226.9
西日本231.3831.8

② H 統計量の計算

H = (12 / N(N+1)) × Σ (Rᵢ² / nᵢ) − 3(N+1)
N = 47、 各群の R̄ᵢ から計算 → H ≈ 24.6
自由度 df = k − 1 = 2、 χ²(2) でp値 ≈ 0.000005
結論:有意水準5%で帰無仮説を棄却。 地域間で合計特殊出生率に差がある。

③ Dunn 検定で事後解析

東日本 vs 中部:p = 0.005(有意)
東日本 vs 西日本:p < 0.001(有意)
中部 vs 西日本:p = 1.00(n.s.、 Bonferroni補正後)

🧮 数式に値を入れて手で計算する: 3 群の Kruskal-Wallis 検定

合成データで 3 群の中央値差をランクで評価する。

Step 1: データとランク

ランク
A2,4,61,2,4
B5,7,93,5,7
C8,10,116,8,9

Step 2: H 統計量

N=9, k=3 R_A=1+2+4=7, R_B=3+5+7=15, R_C=6+8+9=23 H = (12/(N(N+1))) · Σ(R_i²/n_i) - 3(N+1) = (12/90) · (49/3 + 225/3 + 529/3) - 30 = 0.1333 · 267.67 - 30 = 35.69 - 30 = 5.69 df=2, χ²(0.05, 2)=5.99 → 棄却ぎりぎり

🐍 Python で再現

1
2
3
4
5
6
7
from scipy import stats
A = [2, 4, 6]
B = [5, 7, 9]
C = [8, 10, 11]
H, p = stats.kruskal(A, B, C)
print(f"H = {H:.2f}")
print(f"p = {p:.4f}")

📤 実行結果

H = 5.69 p = 0.0582

💬 手計算 (Step 2) H≈5.69 と Python 出力が完全一致。

🐍 Python での Kruskal-Wallis

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 L3221(消費支出(二人以上の世帯)) 北海道 296,888 東京都 341,320 沖縄県 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd

# ── 比べる 4 群を SSDSE-B-2026 から作る(地方ブロック別の消費支出)──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_d = _d[_d['SSDSE-B-2026'] == 2023]
def _blk(code):
    n = int(str(code).lstrip('R')) // 1000
    if 2 <= n <= 7:   return '東北'
    if 8 <= n <= 14:  return '関東'
    if 15 <= n <= 23: return '中部'
    if 24 <= n <= 30: return '近畿'
    return 'その他'
_g = _d.assign(blk=_d['Code'].apply(_blk)).groupby('blk')['L3221']
group1 = _g.get_group('東北').values
group2 = _g.get_group('関東').values
group3 = _g.get_group('中部').values
group4 = _g.get_group('近畿').values

from scipy import stats
import scikit_posthocs as sp

# Kruskal-Wallis
h, p = stats.kruskal(group1, group2, group3, group4)
print(f'H = {h:.3f}, p = {p:.4f}')

# Dunn の事後検定
import pandas as pd
df_long = pd.DataFrame({
    'value': list(group1) + list(group2) + list(group3),
    'group': ['A']*len(group1) + ['B']*len(group2) + ['C']*len(group3)
})
dunn = sp.posthoc_dunn(df_long, val_col='value', group_col='group',
                       p_adjust='bonferroni')
print(dunn)

🚧 落とし穴と注意点

🐍 Python 実装バリエーション — scipy / pingouin / scikit-posthocs / statsmodels

① scipy.stats.kruskal(基本)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4103(合計特殊出生率) 北海道 1.06 東京都 0.99 沖縄県 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd
from scipy.stats import kruskal

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)
# 都道府県コードで東日本/中部/西日本の3ブロックに分割
n = latest['Code'].str[1:3].astype(int)
latest['地域'] = pd.cut(n, [0, 14, 24, 47], labels=['東日本', '中部', '西日本'])

# A4103 = 合計特殊出生率 (TFR)
east    = latest[latest['地域'] == '東日本']['A4103']
central = latest[latest['地域'] == '中部']['A4103']
west    = latest[latest['地域'] == '西日本']['A4103']

H, p = kruskal(east, central, west)
print(f'H = {H:.3f}, p = {p:.4f}')

② pingouin(効果量も自動計算)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4103(合計特殊出生率) 北海道 1.06 東京都 0.99 沖縄県 1.6 …(全 47 行)
1
2
3
4
5
6
7
8
9
import pingouin as pg
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)
n = latest['Code'].str[1:3].astype(int)
latest['地域'] = pd.cut(n, [0, 14, 24, 47], labels=['東日本', '中部', '西日本'])
res = pg.kruskal(data=latest, dv='A4103', between='地域')
print(res)  # H, p, eps² が一括で表示

③ scikit-posthocs(Dunn 検定)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4103(合計特殊出生率) 北海道 1.06 東京都 0.99 沖縄県 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import scikit_posthocs as sp
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)
n = latest['Code'].str[1:3].astype(int)
latest['地域'] = pd.cut(n, [0, 14, 24, 47], labels=['東日本', '中部', '西日本'])
# Bonferroni 補正付きの Dunn検定
posthoc = sp.posthoc_dunn(latest, val_col='A4103', group_col='地域',
                          p_adjust='bonferroni')
print(posthoc)  # ペアごとの調整済みp値の行列

④ scipy で Mann-Whitney 総当たり(手動事後検定)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4103(合計特殊出生率) 北海道 1.06 東京都 0.99 沖縄県 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from scipy.stats import mannwhitneyu
from itertools import combinations
from statsmodels.stats.multitest import multipletests
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)
n = latest['Code'].str[1:3].astype(int)
latest['地域'] = pd.cut(n, [0, 14, 24, 47], labels=['東日本', '中部', '西日本'])
groups = {name: g['A4103'].values for name, g in latest.groupby('地域')}

pvals, pairs = [], []
for a, b in combinations(groups, 2):
    _, p = mannwhitneyu(groups[a], groups[b])
    pvals.append(p); pairs.append(f'{a} vs {b}')

reject, padj, *_ = multipletests(pvals, method='bonferroni')
for pair, p, pa, r in zip(pairs, pvals, padj, reject):
    print(f'{pair}: raw p={p:.3f}, adj p={pa:.3f}, reject={r}')

⑤ 可視化(箱ひげ図 + 検定結果)

1
2
3
4
5
6
7
import seaborn as sns
import matplotlib.pyplot as plt

sns.boxplot(data=latest, x='地域', y='A4103', palette='Set2')
sns.stripplot(data=latest, x='地域', y='A4103', color='black', alpha=0.5)
plt.title(f'地域別 合計特殊出生率(Kruskal-Wallis H={H:.2f}, p={p:.3f})')
plt.show()

⑥ 順列検定(exact版の代替)

1
2
3
4
5
6
7
8
9
from scipy.stats import permutation_test

def statistic(*samples):
    return kruskal(*samples).statistic

res = permutation_test((east, central, west), statistic,
                       permutation_type='independent',
                       n_resamples=9999, random_state=0)
print(f'統計量={res.statistic:.3f}, 順列p={res.pvalue:.4f}')

⚠️ Kruskal-Wallis の落とし穴 — 拡張版(実務で本当に困る5+件)

  1. 「分布の形が同じ」という暗黙の仮定:Kruskal-Wallis は「中央値の差」を検定すると言われがちだが、 厳密には「分布の位置のシフト」を検定する。 群間で 分布形状が大きく異なる(一方が右歪み、 他方が対称など)と、 有意な結果が出ても解釈が難しい。 違いが中央値なのか分散なのか形状なのか明示する必要がある。 ヒストグラムや箱ひげ図で形状確認は必須。
  2. 事後検定なしでの「群間差」主張:Kruskal-Wallis で有意 → 「どこかに差がある」とまで言える。 どの群とどの群が違うかは Dunn検定や対比較のMann-Whitney が必要。 ペアワイズ比較を行う際は Bonferroni 補正等で多重検定問題に対処する。 群が多いほど補正の重みは厳しくなる。
  3. 小標本での近似精度:H統計量はカイ二乗近似を使うが、 各群 n < 5 では近似精度が悪く、 真のp値からズレる。 小標本では exact版(順列検定) を使うか、 ブートストラップで再標本化する。 scipy.stats.kruskal の method='exact' は使えないため、 permutation_test を自作するか mlxtend を使う。
  4. 順位タイ(同じ値)の補正忘れ:データに同値が多いと、 順位平均で代用するが H統計量が過小評価される。 タイ補正係数 C で割って補正する必要がある。 scipy.stats.kruskal は内部でタイ補正済みだが、 手計算する場合は要注意。 評価スコアやリッカート尺度では特に問題になる。
  5. ANOVAより検出力が弱い:データが正規分布に従い等分散なら、 ANOVA の方が検出力が高い。 Kruskal-Wallis は「正規性が疑わしい」「外れ値がある」「順序尺度データ」のときに使うべき。 正規性検定(Shapiro-Wilk)や等分散性検定(Levene)の結果と組み合わせて選択する。
  6. 独立性の仮定:各観測値が独立であることが前提。 同じ被験者を繰り返し測定したデータには Kruskal-Wallis ではなく Friedman検定 を使う。 反復測定や入れ子構造を見落とすと、 p値が過大に小さくなる。
  7. 効果量の報告不足:p値だけ報告すると「有意か否か」しか分からない。 イプシロン二乗(ε² = H/(N−1))やイータ二乗を併記して、 効果の大きさを示す。 大標本では小さな効果でも有意になるため、 実質的重要性の議論が必要。

🧭 解説深化 — 直感を一段深める

Kruskal-Wallis (KW) 検定は、 一言でいえば 「3 群以上の分布の位置(中央的傾向)に差があるか」を順位だけで判定するノンパラ版 ANOVA です。 ANOVA が「群平均のばらつき ÷ 群内のばらつき」を F 値で測るのに対し、 KW は値を全体の順位に置き換えてから同じ発想を適用します。 順位に変換することで、 外れ値や非正規性の影響を受けにくくなる一方、 「値の間隔」の情報は捨てるため、 データが本当に正規なら ANOVA に検出力でわずかに劣ります。

⚠️ 解説深化 — 落とし穴(重要)

KW は「頑健で気軽」に見えますが、 解釈でつまずく点が集中しています。 特に「中央値の差」と読み替える段階に最大の罠があります。

  1. 「中央値の差」ではなく「分布の差」を検定している:KW の帰無仮説は厳密には「全群が同一分布」、 対立仮説は「少なくとも 1 群が確率的に大きい(stochastic dominance)」です。 これを「中央値の差」と言い切れるのは群間の分布形状が同じ(位置シフトのみ違う)ときだけ。 形が違えば「中央値は同じでも有意」「有意でも中央値差の向きが直感と逆」が起こり得ます。
  2. 群内分布形状の同一性という暗黙仮定:厳密な中央値比較を主張するなら、 各群のヒストグラム・箱ひげ図で歪みや分散が揃っているかを必ず確認します。 分散や歪みが大きく違うなら、 KW が拾っているのは「位置」ではなく「ばらつき・形状の違い」かもしれません。 純粋な中央値比較には Brown-Mood の中央値検定を併用します。
  3. 同順位(tie)補正を忘れる:小数第 1〜2 桁で丸めた指標(合計特殊出生率・死亡率)や Likert 尺度ではタイが頻発します。 タイがあると平均順位の分散が縮み H が過小評価されるため、 補正係数 $C = 1 - \frac{\sum_j (t_j^3 - t_j)}{N^3 - N}$ で割って H を膨らませます。 scipy.stats.kruskal は内部補正済みですが、 手計算やタイ率 30% 超では要警戒。
  4. 有意でも事後検定が必要:H が有意なのは「全群同一ではない」という全体判定にすぎません。 ペアの特定には Dunn / Conover-Iman を使い、 必ず多重比較補正(Bonferroni / Holm / FDR)を掛けます。 事後検定をデータを見てから選ぶ(cherry-picking)と第一種の過誤が膨張します。
  5. 小標本での χ² 近似:H は漸近的に $\chi^2(k-1)$ に従うだけで、 各群 $n<5$ 程度だと近似が甘く p 値がズレます。 小標本では正確分布や並べ替え検定(permutation test)を使うのが安全です。
  6. 独立性の仮定:各観測は独立でなければなりません。 同一個体を 3 時点で測った「対応あり」データに KW は誤りで、 Friedman 検定(本サイト未収録)が正解。 隣接県・兄弟ペアのようなクラスター構造があれば混合モデルやクラスター頑健 SE を検討します。
  7. 順位化による情報損失:値を順位に潰すと「どれだけ大きいか」の間隔情報が消えます。 データが本当に正規で等分散なら ANOVA の方が検出力が高く、 効果量の解釈も直接的です。 「非正規だから常に KW」ではなく、 分布を見てから選ぶのが原則。

🚀 解説深化 — 発展

🧮 解説深化 — SSDSE 実測例(8 地方ブロックの総人口)

既存例(3 地域・4 地域)に加え、 SSDSE-B-2026(2023 年、 47 都道府県)の総人口 A1101 を 8 地方ブロックに分けた Kruskal-Wallis を実測しました。 本節の数値はすべて実データからの計算値です(合成・架空値は含みません)。

地方n(県数)中央値人口(千人)平均順位
北海道15,09239.0
東北61,17419.2
関東76,25739.1
中部91,93124.0
近畿71,72728.1
中国51,29817.6
四国48109.0
九州沖縄81,36820.4
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# SSDSE-B-2026 8地方ブロックの総人口 A1101 を Kruskal-Wallis
import pandas as pd
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df['SSDSE-B-2026'] == 2023].copy()   # 最新年 (2023)

def region8(code):
    n = int(code[1:3])
    if n == 1: return '北海道'
    if 2 <= n <= 7: return '東北'
    if 8 <= n <= 14: return '関東'
    if 15 <= n <= 23: return '中部'
    if 24 <= n <= 30: return '近畿'
    if 31 <= n <= 35: return '中国'
    if 36 <= n <= 39: return '四国'
    return '九州沖縄'

latest['地方'] = latest['Code'].apply(region8)
groups = [g['A1101'].dropna().values for _, g in latest.groupby('地方')]
H, p = stats.kruskal(*groups)

N = sum(len(g) for g in groups)
k = len(groups)
eta2 = (H - k + 1) / (N - k)
eps2 = H / ((N**2 - 1) / (N + 1))
print(f'H={H:.3f}, p={p:.6f}, k={k}, N={N}')
print(f'η²={eta2:.3f}, ε²={eps2:.3f}')

📤 実行例:

H=17.555, p=0.014146, k=8, N=47 η²=0.271, ε²=0.382

💬 8 地方ブロックの総人口には有意差あり(H=17.56、 自由度 k−1=7、 p=0.014)。 効果量 η²=0.271 は Cohen 目安で「大」に相当し、 順位分散の約 27% が地方の違いで説明されることを示す。 平均順位は関東 39.1・北海道 39.0 が突出し、 四国 9.0・中国 17.6 が低い —— 人口の東西・大都市圏偏在が順位に表れている。 ただし北海道は n=1 の 1 県ブロックで、 群サイズが極端に不均衡な点は解釈上の注意(この 1 群を除く感度分析が望ましい)。 有意なので、 どの地方ペアが違うかは Dunn / Conover-Iman の事後検定で特定する。

🔗 関連ページ

ANOVA(分散分析) — パラメトリック対応。 正規・等分散なら第一候補。
ノンパラメトリック手法 — 分布を仮定しない検定群の全体像。
仮説検定 — 帰無/対立仮説と p 値判定の基礎。
効果量 — ε² / η² で「差の大きさ」を報告する。
中央値 — KW が比較する(分布が相似なら)代表値。
順位(ランキング) — 順位化という KW の中核操作。
並べ替え検定 — χ² 近似に頼らない厳密な p 値の求め方。
相関カイ二乗検定順序尺度有意水準(関連)。
※ Mann-Whitney U 検定・Friedman 検定・Wilcoxon 検定・順位相関は本用語集に個別ページが未収録のため、 リンクなしのテキスト参照にとどめています。

🗺️ 概念マップ — 3つの視点で体系を理解する

Kruskal-Wallis検定 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。

📍 体系階層のパス

🌐 統計・データサイエンス › 推測統計 › 検定 › Kruskal-Wallis検定(一元配置分散分析の分布フリー版)

① 🔗 関係マップ — 「他の手法とどう繋がっているか」

中心に Kruskal-Wallis検定 を置き、 そこから ノンパラ検定・t検定・F検定・χ²検定・重回帰 計 5 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語あとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。

凡例:現在の用語上位カテゴリ兄弟(並列)前提発展形応用先2階層先

② ⭕ 包含マップ — 「どのカテゴリに含まれているか」

大きな円が小さな円を包含する Circle Packing 図。 「Kruskal-Wallis検定」は緑色でハイライト

📍現在地:統計・データサイエンス

③ 🌳 ツリーマップ — 「面積で見るボリューム比較」

長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「Kruskal-Wallis検定」は緑色でハイライト

🎯 3つのマップの使い分け

マップ 分かること こんな時に見る
🔗 関係マップ手法間の横の関係(前提→発展→応用)「次に何を学べばよい?」 学習順序の判断
⭕ 包含マップ分類体系の入れ子構造(上位⊃下位)「この手法はどんなジャンルに属する?」
🌳 ツリーマップ分野の規模比較(面積=ボリューム)「データサイエンス全体の俯瞰像」

💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは Kruskal-Wallis検定隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス検定 → Kruskal-Wallis検定 という入れ子の位置を示します。 「検定には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。

🔗 隣接手法への橋渡し

Kruskal-Wallis 検定は順位和に基づく 3 群以上の中央値比較であり、 前段の正規性検定・等分散性確認と、 後段の事後比較 (Dunn 等) を組み合わせて結論を導く。

上流の正規性検定 (Shapiro-Wilk) と等分散検定で one-way ANOVA の前提が崩れているか確認し、 並列の Mann-Whitney U (2 群版) や Friedman 検定 (対応あり) と区別して用い、 下流の Dunn 多重比較で群間ペアの有意差を特定する流れで「分布フリーな群間比較」が完結する。

🌳 手法選択フロー

「kruskal wallis」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。

典型シナリオ別の手法選択

シナリオ重視する観点候補手法
データが大規模 (n が多い、 高次元)計算コスト / メモリ / 並列化が必要ビジネス理解
外れ値が多い / ノイズあり頑健性 / 外れ値除去 / 中央値ベースデータ理解
解釈性を重視する線形 / 木構造 / ルールベースデータ準備
予測精度を最優先するアンサンブル / ハイパラ調整 / 交差検証モデリング
データが少ない正則化 / ベイズ / 転移学習 / 簡素なモデルデータアナリスト
リアルタイム/オンライン処理ストリーミング / 軽量モデル / 逐次更新データサイエンティスト

選んだ後の検証ステップ

  1. 前処理確認: 標準化 / 欠損処理 / カテゴリ変数のエンコード が適切か
  2. ハイパラ調整: 交差検証で安定する値を選ぶ
  3. 性能評価: タスクに応じた指標 (RMSE / Accuracy / F1 / AUC / シルエット等) を複数併用
  4. 頑健性チェック: 別手法 / 別シードで結果が大きく違わないか確認
  5. 解釈: 結果を分野知識と照らし合わせ、 意味のある発見になっているか検討