論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
カイ二乗検定
Chi-square Test (χ²)
カテゴリカルデータのクロス集計で「2変数が独立か」を検定。観測度数と期待度数の差を測る。
仮説検定χ²χ²検定カイ2乗chi square

🔖 キーワード索引(補強・追加分)

カイ二乗検定 関連の補強キーワード。 クリックで該当箇所へ:

適合度検定 独立性検定 Yates補正 Fisher正確検定 Cramér's V クロス集計表 期待度数 自由度 対数尤度比検定 McNemar検定

🔖 キーワード索引

このページを高速ナビゲートするための索引チップです。クリックで該当セクションへ。

索引30秒結論文脈直感数式深く理解実値計算Python実装落とし穴関連手法関連用語グループ教材

💡 30秒で分かる結論

🍰 まずはやさしく

データのズレを測る物差しのようなものです。

2つの項目に関係があるか調べます。

部活の種類と好きな科目の関係を調べます。

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

👁️ 直感 — χ²検定は「カテゴリデータの独立性検定」

カイ二乗検定はカテゴリ変数の関係を調べる主要な検定:

💡 30秒で分かる結論

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

🍰 まずはやさしく

グループ分けしたデータを分析する道具です。

予想と実際の結果が違うかを確認します。

スマホの機種と学年の関係を調べます。

この用語の意味を詳しく解説します。

論文中に 「カイ二乗検定」として登場する用語。

カイ二乗検定 とは:カテゴリカルデータのクロス集計で「2変数が独立か」を検定。観測度数と期待度数の差を測る。

📍 あなたが今見ているもの(文脈ボックス)

このページは カイ二乗検定 を解説する用語ページです。
カテゴリ:仮説検定
ジャストインタイム型データサイエンス教育の一環として、必要な時に参照し、関連概念とともに学べる構成になっています。
基準ページ:correlation.html(149KB、12セクション、SSDSE-B 実値計算)と同等以上の品質を目指しています。

🎨 直感で掴む

🍰 まずはやさしく

サイコロの出目の偏りを調べるイメージです。

直感的なズレを数値で判断するために使います。

お菓子の袋の中身が平等か調べます。

感覚的にどういう仕組みか説明します。

観測度数と期待度数のズレがどれくらい大きいかを評価する検定。「クロス表に独立性があるか」「ある分布に従うか (適合度)」を p 値で判定。 質的データ (カテゴリ) の検定に最も基本的なツール。

直感: サイコロの公平性を考える。 6 面ダイスを 60 回振って (1,2,3,4,5,6) の出目が (8,12,9,11,14,6) だったとする。 もし公平 (一様分布) なら期待度数は全て 10。 観測との差は (-2,+2,-1,+1,+4,-4)。 これを 2 乗して期待度数で割った和 $\chi^2 = \sum (O_i - E_i)^2/E_i = (4+4+1+1+16+16)/10 = 4.2$ が「ズレの総量」。 自由度 5 の χ² 分布で p 値を見ると 0.52 で、 公平とは矛盾しない。

SSDSE-B-2026 での例: A1101 (総人口) を 4 分位で「人口少/中下/中上/多」に区切り、 A1303 (65 歳以上人口) も 4 分位で「高齢人口少/中下/中上/多」に区切ると 4×4 のクロス表ができる。 期待度数は「行周辺度数 × 列周辺度数 / 総度数」で計算され、 χ² 統計量がそのまま「人口規模と高齢人口は独立か」を測る尺度になる。 実際には大きい県ほど高齢者数も多いため χ² は大きく、 p < 0.001 で独立性は棄却される。

表 1. χ² 検定の 3 用途と SSDSE 適用例
用途何を測るかSSDSE 適用例
独立性検定2 つのカテゴリ変数が独立かA1101 区分 × A1303 区分
適合度検定観測度数が仮定分布に従うか47 県を 8 地方に集計、 一様分布か
同質性検定複数群が同じ分布か都市部 7 県 vs 地方 40 県の年齢構成比較

📐 χ²統計量

🍰 まずはやさしく

ズレの大きさを計算する数式です。

正しく計算して客観的に判定するために使います。

アンケートの集計結果を数式に入れます。

計算方法とルールについて詳しく読みます。

📐 χ²統計量

$$ \chi^2 = \sum_{i, j} \frac{(O_{ij} - E_{ij})^2}{E_{ij}} $$

独立性検定の期待度数

$$ E_{ij} = \frac{\text{行}_i\text{の合計} \times \text{列}_j\text{の合計}}{\text{総合計}} $$

📊 適合度検定の例

「サイコロは公平か?」を 60 回投げて検定:

📋 χ²検定の仮定

カイ二乗検定 の中心となる数式・定義は次の通りです。

$$ \chi^2 = \sum_{i=1}^k \frac{(O_i - E_i)^2}{E_i} $$

🎮 触って理解する — 分割表で χ² を体感する

分割表(クロス集計表)の各セルの観測度数を、 スライダーまたは数値入力で自由に変えてみましょう。 期待度数χ² 統計量自由度p 値効果量 Cramér's V がその場で再計算され、 χ² 分布上の観測値と棄却域も同時に動きます。 「観測と期待の乖離が大きいほど χ² が大きくなる」ことを、 セルごとの寄与 (O−E)²/E の色分けで確かめてください。

プリセット:
① 観測度数 O(スライダー / 数値で操作)
② 期待度数 E と セル寄与 (O−E)²/E(色が濃いほど χ² への寄与大)
③ χ² 分布・観測値・棄却域(α = 0.05)
青実線=観測 χ² 灰破線=臨界値 χ²(0.05,df) 赤網=棄却域(右側裾)

📖 このインタラクションで押さえる要点

上の操作盤を動かしながら、 カイ二乗検定の核心概念を整理します。 いずれも 仮説検定p 値 の枠組みの上に成り立っています。

独立性の検定 と 適合度検定 の違い

期待度数(E)の考え方

独立性検定の期待度数は Eij = (行 i 合計 × 列 j 合計) / 総数 N。 これは「行と列が完全に独立なら、 各セルにはこれだけ入るはず」という理論値です。 操作盤で行合計・列合計を保ったままセルを動かすと E は変わらず、 O だけがずれて χ² が増えることを確認できます。 χ² は「独立を仮定した理論値からの総ズレ量」そのものです。

自由度(df)

周辺度数(行合計・列合計)を固定すると、 自由に決められるセルは (行−1)(列−1) 個だけで、 残りは引き算で決まります。 これが独立性検定の自由度。 2×2 なら df=1、 3×2 なら df=2。 df が大きいほど χ² 分布は右へ広がり、 同じ p 値を得るのに必要な χ² 値(臨界値)も大きくなります(操作盤で 2×2↔3×2 を切り替えて灰破線の位置変化を確認)。

前提:期待度数 5 以上

χ² 統計量が理論上の χ² 分布に近似するのは大標本のとき。 目安として全セルの期待度数が 5 以上(少なくとも 80% 以上のセルで E≥5)が必要です。 操作盤で小さな値を入れて総数を減らすと、 期待度数 < 5 の警告が出ます。 違反時は Fisher の正確検定へ切り替えます。

効果量 Cramér's V

V = √(χ² / (N·min(r−1, c−1))) は 0〜1 に収まる効果量で、 関連の強さを表します(Cohen の目安:0.1 小・0.3 中・0.5 大)。 p 値は「有意か(=偶然か)」しか語らず、 N が大きいと些細なズレでも有意になります。 「有意」と「実質的に強い関連」を切り分けるため、 χ² 検定では V を必ず併記します。

Fisher の正確検定との使い分け

p 値・仮説検定との関係

χ² 検定の p 値 は「帰無仮説(2変数は独立)が正しいと仮定したとき、 観測された χ² 以上のズレが偶然生じる確率」で、 χ² 分布の右側裾の面積として求まります(操作盤の③の赤網部分)。 p < 有意水準 α(通常 0.05)なら帰無仮説を棄却。 これは 仮説検定 の一般手続きそのもので、 複数のクロス表を同時に検定するときは 多重比較補正 が必要になります。 独立性が棄却されても因果ではなく関連にとどまる点、 多変量へ拡張するなら ロジスティック回帰 へ進む点も押さえておきましょう。

🔬 「カイ二乗検定」を深く理解する

🔬 「カイ二乗検定」を深く理解する

χ²分布の性質

効果量 Cramér's V

$$ V = \sqrt{\frac{\chi^2}{n \cdot \min(r-1, c-1)}} $$

0〜1の値、 大きいほど強い関連。 Cohen の基準:0.1 小、 0.3 中、 0.5 大。

応用

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

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

🧮 SSDSE-B 実値計算例(47都道府県データ)

47 都道府県を「高齢率 高/低」×「若年率 高/低」でクロス集計し、 独立性のカイ二乗検定を行う完全再現例(SSDSE-B-2026 の実在列 A1303・A1301・A1101 を使用)。

① 計算コード

① 目的:47都道府県で「高齢化率が高い県は若年率が低いのか」、 すなわち2つの人口構成区分が独立でないかを独立性のカイ二乗検定で確かめる。
② 橋渡し:連続量である高齢化率・若年率をそれぞれ中央値で2区分し、 2×2のクロス集計表(分割表)に変換してから χ² を計算する。 これで「量的データ→カテゴリの独立性検定」に持ち込める。
③ コード:SSDSE-B-2026.csv を実測値のまま読み込み、 観測度数・期待度数・χ²・自由度・p 値・効果量 Cramér's V まで一気に求める。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) 北海道 5,092,000 514,000 1,681,000 東京都 14,086,000 1,513,000 3,205,000 沖縄県 1,468,000 236,000 350,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
import pandas as pd
import numpy as np
from scipy import stats

# SSDSE-B-2026.csv は cp932・2行目(日本語名)を除いて読み込む
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]        # 先頭列=年度。2023年の47都道府県を抽出

# 実在列から人口構成比を作る(A1303=65歳以上人口, A1301=15歳未満人口, A1101=総人口)
aging = df['A1303'] / df['A1101']          # 高齢化率
youth = df['A1301'] / df['A1101']          # 若年率
df['高齢区分'] = np.where(aging >= aging.median(), '高齢率高', '高齢率低')
df['若年区分'] = np.where(youth >= youth.median(), '若年率高', '若年率低')

# クロス集計表(観測度数 O_ij)
ct = pd.crosstab(df['高齢区分'], df['若年区分'])
print(ct)

# 独立性のカイ二乗検定(correction=False で Yates 補正なし)
chi2, p, dof, expected = stats.chi2_contingency(ct, correction=False)
print(f'chi2 = {chi2:.4f}, df = {dof}, p = {p:.4f}')
print('期待度数 E_ij:')
print(pd.DataFrame(expected, index=ct.index, columns=ct.columns).round(2))

# Cramér's V(効果量)
n = ct.values.sum()
v = np.sqrt(chi2 / (n * (min(ct.shape) - 1)))
print(f"Cramér's V = {v:.3f}  (0.1=小, 0.3=中, 0.5=大)")
📤 実行例(実測) 若年区分 若年率低 若年率高 高齢区分 高齢率低 8 15 高齢率高 15 9 chi2 = 3.6108, df = 1, p = 0.0574 期待度数 E_ij: 若年区分 若年率低 若年率高 高齢区分 高齢率低 11.26 11.74 高齢率高 11.74 12.26 Cramér's V = 0.277 (0.1=小, 0.3=中, 0.5=大)

④ 実行結果の読み取り

出力は下の ② の表のとおり。 観測度数は「独立」を仮定した期待度数(各セル約11〜12)からズレ、 対角(高齢率高×若年率低、 高齢率低×若年率高)に県が偏る。 ただし n=47 と小さく χ²=3.61 / p=0.057 は 0.05 をわずかに上回るボーダーラインで、 「独立とは言い切れないが有意とも断定できない」。 このとき効果量 Cramér's V=0.28(中程度)を併記して、 p 値だけで判断しないのが定石。

② 期待出力

上記コードを SSDSE-B-2026(2023年・47都道府県)で実行すると、 観測度数のクロス集計表と検定結果が実測値で得られる。

項目 セル / 指標 解釈
観測度数高齢率低 × 若年率低8両方低い県は少なめ
観測度数高齢率低 × 若年率高15若い県(沖縄・愛知など)
観測度数高齢率高 × 若年率低15高齢化が進んだ県
観測度数高齢率高 × 若年率高9対角より少ない
期待度数各セル(独立仮定)11.26 / 11.74 / 11.74 / 12.26行合計×列合計/N
検定χ²3.611df=1, p≈0.0574
効果量Cramér's V0.277中程度の関連(負の連関)

👉 すべて SSDSE-B-2026 の実測値(合成なし)。 同じ手順で他年次・他変数にも適用できる。

🧮 実値で計算してみる(SSDSE-B-2026)

政府統計の総合窓口 e-Stat が公開する SSDSE-B-2026.csv(47都道府県×112項目・2012〜2023年のパネル)を encoding='cp932'skiprows=[1] で読み込み、 df[df['SSDSE-B-2026']==2023] で2023年の47行に絞ったうえで、 別の実在列の組合せでもう一例を確認する。

ここでは A1101(総人口)を中央値で「人口多/少」に、 高齢化率 A1303/A1101 を中央値で「高齢率高/低」に2分類してクロス表を作り、独立性のカイ二乗検定を実施。 人口の多い県ほど高齢化率が低い(都市部への若年流入)傾向がはっきり出て、 χ² ≒ 13.33、df=1、p ≒ 0.00026 で「人口規模と高齢化率は独立ではない」と結論できる。

項目値・指標
データ件数47 都道府県(SSDSE-B-2026・2023年)
使用列A1101(総人口) × 高齢化率 A1303/A1101
分類閾値各指標の中央値で 2 値化(多/少・高/低)
クロス表(実測値)人口多×高齢率低: 18, 人口多×高齢率高: 6, 人口少×高齢率低: 5, 人口少×高齢率高: 18
計算結果χ² ≒ 13.33, df=1, p ≒ 0.00026, Cramér's V ≒ 0.53(強い関連)

🧮 数式に値を入れて手で計算する

2×2 のクロス表 観測度数 O = [[20, 30], [25, 25]] を用いて、 期待度数 E と χ² = Σ(O−E)²/E を Step 1〜4 で展開する。 Python (scipy.stats.chi2_contingency) で再現し、 結果が一致することを確認する。

📐 用語の主要数式 (再掲)

$$ \chi^2 = \sum_{i,j} \frac{(O_{ij} - E_{ij})^2}{E_{ij}}, \qquad E_{ij} = \frac{(\text{行 } i \text{ 合計}) \times (\text{列 } j \text{ 合計})}{\text{総数 } N} $$

Step 1: 観測度数 O の準備 (2×2 クロス表)

列 1列 2行合計
行 1203050
行 2252550
列合計4555N = 100

Step 2: 期待度数 E を計算 (行合計 × 列合計 / N)

セル計算期待度数 E
E₁₁50 × 45 / 10022.5
E₁₂50 × 55 / 10027.5
E₂₁50 × 45 / 10022.5
E₂₂50 × 55 / 10027.5

Step 3: 各セルの (O−E)²/E を計算して合計

セルOE(O−E)²(O−E)²/E
(1,1)2022.5(−2.5)² = 6.256.25 / 22.5 ≈ 0.27778
(1,2)3027.5(+2.5)² = 6.256.25 / 27.5 ≈ 0.22727
(2,1)2522.5(+2.5)² = 6.256.25 / 22.5 ≈ 0.27778
(2,2)2527.5(−2.5)² = 6.256.25 / 27.5 ≈ 0.22727
χ²合計≈ 1.01010

Step 4: 自由度と p 値

項目計算結果
自由度 df(行数 − 1) × (列数 − 1) = (2−1)(2−1)1
χ² 統計量1.0101
p 値 (df=1, 上側)P(χ²₁ > 1.0101)≈ 0.3148
判定 (α=0.05)p > 0.05帰無仮説 (独立) を棄却できない

🐍 同じ計算を Python で再現

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import numpy as np
from scipy.stats import chi2_contingency

O = np.array([[20, 30],
              [25, 25]])

# correction=False で Yates 補正なし (手計算と同じ式)
chi2, p, dof, E = chi2_contingency(O, correction=False)

print(f"観測度数 O =\n{O}")
print(f"期待度数 E =\n{E}")
print(f"chi^2 = {chi2:.5f}")
print(f"df    = {dof}")
print(f"p     = {p:.4f}")

📤 実行結果

観測度数 O = [[20 30] [25 25]] 期待度数 E = [[22.5 27.5] [22.5 27.5]] chi^2 = 1.01010 df = 1 p = 0.3148

💬 手計算 (Step 3 χ²=1.01010, Step 4 df=1, p≈0.3148) と Python 出力が完全一致。 p=0.31 > 0.05 なので「行と列は独立である」という帰無仮説は棄却できない (このサイズ・差では有意な関連は検出されない)。

🧮 数式に値を入れて手で計算する: カイ二乗適合度

合成データでサイコロ 60 回の出目から一様分布への適合度をカイ二乗検定で評価する。

Step 1: 観測度数と期待度数

OE(O-E)²/E
18100.40
212100.40
39100.10
411100.10
510100.00
610100.00

Step 2: χ² 統計量

χ² = 0.40+0.40+0.10+0.10+0.00+0.00 = 1.00 df = 6-1 = 5 χ²(0.05,5) = 11.07 → 1.00 < 11.07 で H0 棄却せず

🐍 Python で再現

1
2
3
4
5
6
from scipy import stats
O = [8, 12, 9, 11, 10, 10]
E = [10]*6
chi2, p = stats.chisquare(O, E)
print(f"χ² = {chi2}")
print(f"p = {p:.4f}")

📤 実行結果

χ² = 1.0 p = 0.9626

💬 手計算 (Step 2) χ²=1.00 と Python 出力が完全一致。 サイコロは一様と矛盾しない。

🐍 Python での χ²検定

▼ コード解説(クロス集計表の作成(pd.crosstab))
🎯 解説: pd.crosstab で 2 変数の度数表を作成。 χ² 検定の入力。 行と列の独立性を検定する基本表。
📥 入力例: df['人口規模区分'] × df['高齢化区分'] 47 県 × 2 カテゴリ × 2 カテゴリ
📤 実行例(実測) χ² = 1.0 p = 0.9626
💬 読み方: クロス集計は「2 変数の同時分布」を可視化。 期待度数とのズレを χ² で測ると独立性が検定できる。
 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
# ── この抜粋で使うデータを用意します ──
# SSDSE-B-2026 に「性別」「購入」の列は無い。独立性検定の手順を示すための
# <架空の>アンケート集計を、ここで作る(ページの df は書き換えない)。
from scipy import stats
import numpy as np
import pandas as pd

_rng = np.random.default_rng(0)
survey = pd.DataFrame({
    '性別': _rng.choice(['男', '女'], size=200),
    '購入': _rng.choice(['あり', 'なし'], size=200, p=[0.4, 0.6]),
})

# 適合度検定
observed = [8, 12, 11, 9, 13, 7]
expected = [10] * 6
chi2, p = stats.chisquare(observed, expected)

# 独立性検定(クロス集計)
contingency = pd.crosstab(survey['性別'], survey['購入'])
chi2, p, dof, expected = stats.chi2_contingency(contingency)
print(f'χ² = {chi2:.3f}, df = {dof}, p = {p:.4f}')

# Fisher の正確検定(小サンプル)
oddsratio, p = stats.fisher_exact(contingency)

# Cramér's V(効果量)
n = contingency.sum().sum()
v = np.sqrt(chi2 / (n * (min(contingency.shape) - 1)))
print(f"Cramér's V = {v:.3f}")

🚧 落とし穴と注意点

🐍 Python 実装バリエーション(scikit-learn / scipy / Optuna)

A. scikit-learn による実装

▼ コード解説(適合度検定(観測 vs 理論分布))
🎯 解説: scipy.stats.chisquare で「観測度数が特定の分布に従うか」を検定。 一様性・正規性などの検査。
📥 入力例: 観測度数 vs 期待度数(一様分布等)
📤 実行例: χ² = 0.947, df = 1, p = 0.3305 Cramér's V = 0.069 → 性別と購入は独立(p > 0.05)。 Cramér's V = 0.069 は効果量としてもほぼゼロ ※ 同じコード内の適合度検定 observed=[8,12,11,9,13,7] は χ² 値 2.800, 自由度 5, p 値 0.7308 で、 こちらも一様性を棄却できない(print していないので上の出力には現れない)
💬 読み方: 適合度検定は分布仮定の検査。 χ² 検定の独立性検定との違いに注意。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from sklearn.feature_selection import chi2, SelectKBest
# 機械学習で「カテゴリ目的変数 vs カテゴリ説明変数」のスクリーニング
# 例:高齢区分を目的変数として、各特徴の χ² を取得(df は上の①で作成済み)
df['人口区分'] = np.where(df['A1101'] >= df['A1101'].median(), '人口多', '人口少')
X_cat = pd.get_dummies(df[['若年区分', '人口区分']], drop_first=True).astype(int)
y_cat = df['高齢区分']

chi2_scores, p_vals = chi2(X_cat, y_cat)
for col, chi2_v, p_v in zip(X_cat.columns, chi2_scores, p_vals):
    print(f'{col:20} χ²={chi2_v:6.2f}, p={p_v:.4f}')
▼ コード解説(Yates' continuity correction)
🎯 解説: 2×2 表で標本が小さいとき、 Yates 連続性補正を適用。 χ² 値を下げて第一種誤り率を抑える。
📥 入力例: correction=True を指定
📤 実行例(上のブロック③と同じ 200 件のクロス表 [[34,77],[34,55]] で比較): 補正なし: χ² 値 1.262, p 値 0.2613 補正あり: χ² 値 0.947, p 値 0.3305 → 補正で χ² が下がり、 p は大きくなる(=有意になりにくくなる)
💬 読み方: n が小さい(特に各 cell の期待度数<10)ときは補正推奨。 ただし保守的すぎる批判もあり、 Fisher 検定の方が安全な場合も。

B. scipy / statsmodels による実装

▼ コード解説(Fisher の正確検定)
🎯 解説: scipy.stats.fisher_exact で小標本の 2×2 表を厳密に検定。 χ² の代替(漸近性が崩れる場合)。
📥 入力例: 小標本(n<20 または期待度数<5)
📤 実行例(小標本の 2×2 表 [[8, 2], [3, 7]]): Fisher: オッズ比 9.333, p 値 0.0698 χ²(Yates 補正): χ² 値 3.232, p 値 0.0722 → 期待度数が小さいので両者は近い値になるが、 Fisher の方が厳密
💬 読み方: 期待度数が 5 未満の cell があるとき、 χ² 近似は不正確。 Fisher 正確検定が標準的な代替。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from scipy import stats
# Fisher の正確検定(小サンプル)
ct = pd.crosstab(df['高齢区分'], df['若年区分'])
odds, p_fisher = stats.fisher_exact(ct)
print(f'odds ratio = {odds:.3f}, p = {p_fisher:.4f}')

# 対数尤度比検定(G 検定)
g_stat, p_g, dof, exp = stats.chi2_contingency(ct, lambda_='log-likelihood')
print(f'G統計量 = {g_stat:.3f}, p = {p_g:.4f}')

# χ² 適合度検定(高齢区分の度数が一様かを検定)
freq = df.groupby('高齢区分').size().values
chi2_g, p_gof = stats.chisquare(freq, f_exp=[len(df)/2]*2)
print(f'適合度 χ² = {chi2_g:.3f}, p = {p_gof:.4f}')
▼ コード解説(ロジスティック回帰との関係)
🎯 解説: 2×2 表の χ² 検定はロジスティック回帰の Wald 検定と漸近的に等価。
📥 入力例: y = 高齢区分(0/1), x = 都市区分(0/1)
📤 実行例: ロジスティック係数 ≈ log(オッズ比) Wald χ² ≈ Pearson χ²
💬 読み方: クロス集計の χ² とロジスティック回帰は同じ情報を別表現。 多変量に拡張するならロジスティック回帰。

C. Optuna でハイパラ・選択最適化

▼ コード解説(3 変数以上の高次集計)
🎯 解説: pd.crosstab で 3 変数以上の集計表を作成。 条件付き独立性検査(Mantel-Haenszel)に使う。
📥 入力例: 都市性 × 高齢化 × 地域ブロック
📤 実行例: ブロックごとの 2×2 表 ブロック別 χ² と統合 χ²
💬 読み方: 高次集計は Simpson のパラドックスを検出。 全体では関連あり、 ブロック内では関連なし、 等のケース。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# χ² 検定で離散化の最適な閾値を探索(注意:p-hacking 回避のため探索的のみ)
import optuna

aging = df['A1303'] / df['A1101']   # 高齢化率
youth = df['A1301'] / df['A1101']   # 若年率

def objective(trial):
    q = trial.suggest_int('quantiles', 2, 5)
    a = pd.qcut(aging, q=q, labels=False, duplicates='drop')
    y = pd.qcut(youth, q=q, labels=False, duplicates='drop')
    ct = pd.crosstab(a, y)
    if (ct.values < 5).mean() > 0.2:
        return 0  # 期待度数が小さすぎるビン分割は無効
    chi2, p, dof, _ = stats.chi2_contingency(ct)
    v = np.sqrt(chi2 / (ct.values.sum() * (min(ct.shape) - 1)))
    return v  # Cramér's V を最大化

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=20)
print('Best quantiles:', study.best_params, 'V:', study.best_value)
# 注意:これは探索のみ。 仮説検定として報告するなら事前登録が必要
▼ コード解説(Mantel-Haenszel 検定)
🎯 解説: 層別 2×2 表の共通オッズ比を計算。 交絡因子で層別したときに使う。
📥 入力例: ブロック別 2×2 表
📤 実行例(実測) Best quantiles: {'quantiles': 2} V: 0.14945652173913043
💬 読み方: MH 検定は「複数の 2×2 を同時に検定」する古典的手法。 ロジスティック回帰の前身。

D. ライブラリ早見表

ライブラリ / 関数 用途
scipy.stats.chi2_contingency独立性検定(汎用)
scipy.stats.chisquare適合度検定
scipy.stats.fisher_exact2×2 の Fisher 正確検定
statsmodels.stats.contingency_tables.Table詳細な分析(残差・効果量等)
statsmodels.stats.contingency_tables.mcnemar対応のある 2×2 検定

🐍 Python 実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency

# cp932・2行目(日本語名)を除いて読み込み、先頭列=年度で2023年に絞る
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]           # 47都道府県

aging = df['A1303'] / df['A1101']             # 高齢化率(65歳以上人口/総人口)
pop_high   = (df['A1101'] >= df['A1101'].median()).astype(int)  # 人口 多=1/少=0
aging_high = (aging >= aging.median()).astype(int)              # 高齢率 高=1/低=0

table = pd.crosstab(pop_high, aging_high)
chi2, p, dof, _ = chi2_contingency(table, correction=False)
print(f'χ²={chi2:.2f}, df={dof}, p={p:.4g}')   # χ²=13.33, df=1, p=0.00026
📤 実行例(実測) χ²=13.33, df=1, p=0.0002608

上記コードは pandas / numpy / scipy の標準的なライブラリを用い、SSDSE-B-2026.csv を実在列(A1101・A1303)のまま直接読み込んで計算します(合成データ不使用)。

⚠️ 落とし穴(拡張版・各 100 文字以上)

① 期待度数が小さい(< 5)セルが多いと近似が破綻
カイ二乗分布は連続分布だが、 検定統計量は離散的な度数から計算する。 期待度数が小さいセルでは近似が悪化し、 p 値が信頼できない。 セルの 20% 以上が期待度数 5 未満ならFisher の正確検定に切り替える。 SSDSE のような n=47 の小サンプルでは特に注意が必要で、 全カテゴリで期待度数を必ず確認する。
② 連続値の離散化の恣意性
「高所得/低所得」の切り方を四分位・三分位・任意境界のどれにするかで p 値が変わる。 探索的に複数の切り方を試して「有意になるもの」を選ぶのは p-hacking。 事前に分析計画を決め、 切り方を固定する。 連続変数のままなら相関分析やロジスティック回帰で扱うべき。
③ 独立性検定 ≠ 因果関係
χ² で「独立でない」と分かっても因果ではなく単なる関連。 「所得と持ち家が独立でない」は事実だが、 「高所得が持ち家を引き下げる」とは限らない(都市部効果が交絡)。 第3変数の影響を考えるなら層別分析・ロジスティック回帰へ。
④ 多重比較を無視
複数のクロス集計表を次々に検定すると、 偽陽性が積み上がる(α=0.05 で 20 回検定すれば期待 1 つは「有意」)。 Bonferroni 補正・Holm 法・FDR で調整。 探索的なら p 値より効果量(Cramér's V)を重視。
⑤ Yates 補正の使いどころを誤解
2×2 表で n が小さい場合、 連続性補正(Yates)を使うと χ² が小さくなり保守的。 ただし現代では Yates 補正は過保守との批判があり、 むしろ Fisher 正確検定が推奨される。 R のデフォルトは Yates 補正あり、 Python の scipy はなし、 と挙動が異なる点に注意。
⑥ 効果量を報告しない
p 値だけ報告は古典的。 サンプルサイズが大きいと「実用的に無視できる関連」でも有意になる。 必ず Cramér's V や odds ratio など効果量を併記。 0.1=小、 0.3=中、 0.5=大が目安(Cohen)。 信頼区間も付ければなお良い。
⑦ McNemar 検定との混同
「対応のある」2×2 表(例:同じ被験者の前後比較)には独立性検定でなく McNemar 検定を使う。 χ² 独立性を誤用すると検定の前提(独立観測)が崩れ、 結論が信頼できない。 デザインに応じた検定選択が必須。
⑧ 期待度数の式を勘違い
期待度数 E = (行合計 × 列合計) / 全体. これは「独立を仮定した場合の理論度数」。 観測度数との乖離が χ² の正体。 行・列合計を入れ替えると違う値になるので、 セルの位置と添字を確認する習慣をつける。

⚠️ 落とし穴

➕ 解説深化(追記) — 直感・落とし穴・発展手法

既存の各セクションを補強する追記です。 まず一段深い直感、 次に見落としやすい落とし穴、 最後に χ² の周辺にある発展手法(効果量・残差分析・Fisher/G 検定・尤度比)を整理し、 SSDSE-B-2026 の別の実測クロス集計(地方 × 高齢区分)で確かめます。

👁️ 直感の追記 — 「ズレ²を期待度数で正規化して合算する」

χ² は本質的に「観測度数 O と期待度数 E のズレを二乗し、 E で割って合計する」だけの量です。 二乗するのはプラスとマイナスのズレが打ち消し合わないようにするため、 E で割るのは「度数が大きいセルの絶対ズレは大きくて当然」なので相対化するためです。 たとえば E=100 のセルで O が 110(差 10)なのと、 E=10 のセルで O が 20(差 10)なのでは、 後者の方が「予想外」です。 (O−E)²/E はこの直感どおり、 前者に 1.0、 後者に 10.0 を与えます。 χ² 検定は「クロス集計表に並べた 2 つのカテゴリ変数の関連(=独立からのズレ)を、 このズレ量の総和で 1 つの数字に要約し、 それが偶然の範囲かを問う」検定だと捉えると、 適合度・独立性・等質性の 3 用途が同じ骨格の変奏だと見えてきます。

⚠️ 落とし穴の深掘り(追記)

⑨ 大標本では「実質ゼロ」の差でも有意になる
χ² 統計量は総数 N にほぼ比例して膨らみます。 同じ比率の偏りでも N を 10 倍にすれば χ² も約 10 倍になり、 p 値はいくらでも小さくできます。 つまり十分大きな標本では「実務的に無視できる差」でも必ず p < 0.05 になります。 「有意」=「関連が強い・重要」ではありません。 必ず効果量 Cramér's V(N に依存しない 0〜1 の指標)を併記し、 「統計的有意」と「実質的な関連の強さ」を切り分けて報告します。 逆に SSDSE(n=47)のような小標本では、 目に見える偏りがあっても有意にならないことが多い点にも注意します。
⑩ 順序情報を捨てている
χ² 独立性検定はカテゴリを名義尺度として扱い、 「低→中→高」のような順序を無視します。 カテゴリの並び順を入れ替えても χ² 値は不変です。 そのため「用量が増えるほど反応率が上がる」といった単調傾向は χ² では検出力が落ちます。 順序カテゴリには Cochran–Armitage 傾向検定や順序ロジット、 あるいは順位相関(Spearman)など、 順序を活かす手法の方が適切です。 連続量を無理に区切った 2×2 で情報を捨てるくらいなら、 元の連続量で相関ロジスティック回帰を使う判断も重要です。

🚀 発展手法(追記) — 効果量・残差分析・Fisher/G 検定

🧮 SSDSE-B-2026 追加実測例 — 地方(東/中/西日本) × 高齢区分

47 都道府県を3 地方(都道府県コードで 東日本=北海道〜新潟/中日本=富山〜愛知/西日本=三重〜沖縄)に、 高齢化率 A1303/A1101中央値で「高齢率 高/低」に区分して 3×2 のクロス集計を作り、 独立性の χ² 検定・G 検定・調整済み残差を実測値で求めた例(合成なし)。 期待度数に 5 未満が現れるため、 前掲の落とし穴①がそのまま顔を出す実データ例でもある。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,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
import pandas as pd
import numpy as np
from scipy import stats

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

# 都道府県コード(R01..R47)の数値で3地方に区分
code = d['Code'].str[1:3].astype(int)
region = np.where(code <= 15, '東日本', np.where(code <= 23, '中日本', '西日本'))
aging = d['A1303'] / d['A1101']
d['地方'] = region
d['高齢区分'] = np.where(aging >= aging.median(), '高齢率高', '高齢率低')

ct = pd.crosstab(d['地方'], d['高齢区分']).reindex(['東日本', '中日本', '西日本'])
chi2, p, dof, E = stats.chi2_contingency(ct, correction=False)
G, pG, _, _ = stats.chi2_contingency(ct, lambda_='log-likelihood')
N = ct.values.sum()
V = np.sqrt(chi2 / (N * (min(ct.shape) - 1)))
# 調整済み標準化残差
O = ct.values.astype(float)
rs, cs = O.sum(1), O.sum(0)
adj = (O - E) / np.sqrt(E * np.outer(1 - rs / N, 1 - cs / N))
print(ct)
print(f'chi2={chi2:.3f} df={dof} p={p:.4f} V={V:.3f} minE={E.min():.2f}')
print(f'G={G:.3f} pG={pG:.4f}')
print('調整済み残差:', np.round(adj, 2))

📤 実行結果(SSDSE-B-2026・2023 年・47 都道府県の実測値)

高齢区分 高齢率低 高齢率高 地方 東日本 8 7 中日本 5 3 西日本 10 14 chi2=1.213 df=2 p=0.5454 V=0.161 minE=3.91 G=1.221 pG=0.5432 調整済み残差: [[ 0.41 -0.41] [ 0.84 -0.84] [-1.02 1.02]]
表. 追加実測例の読み取り
指標解釈
χ² / df / p1.213 / 2 / 0.545p > 0.05。 独立の帰無仮説は棄却できない
G 検定 / p1.221 / 0.543χ² とほぼ一致(大標本での漸近一致を実データで確認)
Cramér's V0.161弱い関連(Cohen 目安で 0.1〜0.3 の下寄り)
最小期待度数3.91(2/6 セルが 5 未満=33%)落とし穴①に該当。 χ² 近似は割引いて解釈し、 Fisher の正確検定で追認すべき場面
調整済み残差全セル |d| < 1.96(最大 1.02)有意に過剰/過少なセルは無し。 見た目の偏りは偶然の範囲

👉 すべて SSDSE-B-2026 の実測値。 「地方ブロックと高齢化の高低は、 n=47 の解像度では独立と区別できない」という結論。 期待度数 5 未満が 33% を占めるため、 この 3×2 表は本来 Fisher の正確検定やカテゴリ併合(例:東日本+中日本 vs 西日本)で追認するのが安全。

🔗 この追記に関連する用語ページ

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

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

📍 体系階層のパス

🌐 統計・データサイエンス › 推測統計 › 検定 › カイ二乗検定(ノンパラメトリック検定の一つ)

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

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

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

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

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

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

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

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

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

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

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

🔗 隣接手法への橋渡し

「カイ二乗検定」はクロス集計の独立性を検証する手続きで、 期待度数の計算 (上流) と効果量による実質的解釈 (下流) を欠くと p 値の儀式に陥る。

上流のクロス集計で期待度数を整え、 並列の Fisher 検定で小標本に対応し、 下流のクラメール V で関連の強さまで示せば、 SSDSE の地域 × カテゴリの独立性検証が完結する。

🌳 手法選択フロー

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

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

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

選んだ後の検証ステップ

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