← 授業トップへ戻る


本日の目標

  1. カテゴリデータの分析が必要な場面を理解する
  2. クロス集計表(分割表)を作成できる
  3. カイ二乗検定(独立性の検定)をRで実行し,結果を解釈できる
  4. 検定が使える条件を確認し,使えないときの代わりを知る
  5. 発展:期待度数・カイ二乗統計量・自由度の式を,自分で導ける

準備:パッケージの読み込み

この回のコードは,すべて tidyverse を読み込んだ状態で動きます。 RStudioを起動したら,まずこれを実行してください。

library(tidyverse)

毎回必要です。読み込まないまま filter() や ggplot() を使うと, 「関数が見つかりません」というエラーになります。


1. カテゴリデータとは

今日の位置づけ

ここまでの流れを整理しておきます。

回 何と何の関係を見るか 使う手法
第8回 量 と 量(身長と体重のような) 相関分析
第9回(今日) 分類 と 分類(性別と購買の有無のような) カイ二乗検定
第10・11回 グループ と 量(男女で平均点が違うか) t検定

今日のデータは,これまでと性質が違います。平均が計算できません。 「男性と女性の平均」には意味がありませんし,「生存した人の平均」も意味をなしません。 数えること,つまり度数(件数)でしか扱えないのです。

行動科学研究法を履修している人へ

カイ二乗検定は,研究法では1月に扱われる予定の内容です。 つまり今日が初めてになります。考え方のところから丁寧に進めます。 一方,帰無仮説やp値の枠組みは1学期に学んだとおりで,そこは変わりません。


カテゴリデータ(名義・順序尺度):数値ではなく,グループや分類を表すデータ

カテゴリデータの例 連続データとの違い
性別(男・女) 平均が計算できない
学部(文・理・医…) 「文学部の平均」は意味をなさない
回答(はい・いいえ) 頻度(件数)で分析する

2. クロス集計表(分割表)

2つのカテゴリ変数の組み合わせごとの件数を表にしたものです。

例:タイタニック号の乗客データ

# R組み込みの Titanic データ
# データフレーム形式に変換
titanic <- as.data.frame(Titanic)
head(titanic)
# 性別と生存の2×2分割表を作る
titanic_summary <- titanic |>
  group_by(Sex, Survived) |>
  summarise(Freq = sum(Freq), .groups = "drop")

titanic_summary
# クロス集計表の形式で表示
xtabs(Freq ~ Sex + Survived, data = titanic_summary)
##         Survived
## Sex        No  Yes
##   Male   1364  367
##   Female  126  344

3. カイ二乗検定(独立性の検定)

なぜ検定するのか:表を見るだけでは決められない

クロス集計表ができると,つい割合を見比べて「差がある」と言いたくなります。 ところが,割合だけ見ても結論は出せません。

次の2つの表を見てください。

small <- matrix(c(16, 4, 10, 10), nrow = 2, byrow = TRUE,
                dimnames = list(c("A群", "B群"), c("はい", "いいえ")))
small
##     はい いいえ
## A群   16      4
## B群   10     10
# 行ごとの割合
round(prop.table(small, margin = 1) * 100, 1)
##     はい いいえ
## A群   80     20
## B群   50     50

A群は80%,B群は50%。かなり違って見えます。では,この表をそのまま5倍にしてみます。

large <- small * 5
large
##     はい いいえ
## A群   80     20
## B群   50     50
# 割合はまったく同じ
round(prop.table(large, margin = 1) * 100, 1)
##     はい いいえ
## A群   80     20
## B群   50     50

割合は1%も変わっていません。 ところが検定の結果は変わります。

c(小さい表 = chisq.test(small)$p.value,
  大きい表 = chisq.test(large)$p.value)
##     小さい表     大きい表 
## 9.742169e-02 1.713801e-05

小さい表では \(p > .05\),大きい表では \(p < .001\) です。

同じ割合なのに結論が逆になるのは,「その差が偶然でも起こりうるか」はデータの量によって変わるからです。20人で8割対5割なら偶然の範囲ですが,100人で同じ差が出たら偶然とは考えにくい。この判断を目分量ではなく手続きで行うのが検定です。

逆に言えば,\(p\) が小さいことは「差が大きい」ことを意味しません。 差の大きさは5節の効果量で別に測ります。

問い

「性別と生存率は独立(無関係)か,それとも関連があるか?」

仮説の設定

  • 帰無仮説 H₀:性別と生存は独立(関連がない)
  • 対立仮説 H₁:性別と生存は独立でない(関連がある)

カイ二乗統計量

期待度数(H₀が正しいとしたときの理論上の頻度)と観測度数の差から計算します。

\[\chi^2 = \sum \frac{(観測度数 - 期待度数)^2}{期待度数}\]

導出①:期待度数が「行合計 × 列合計 ÷ 総数」になる理由

ここが,この検定が独立性の検定と呼ばれるゆえんです。

2つの変数が独立であるとは,同時に起こる確率が,それぞれの確率の積で書けることでした。

\[P(A \cap B) = P(A) \times P(B)\]

いま \(A\) を「男性である」,\(B\) を「死亡した」とします。表から読める割合は次の2つです。

\[P(\text{男性}) = \frac{\text{男性の行合計}}{N}, \qquad P(\text{死亡}) = \frac{\text{死亡の列合計}}{N}\]

もし性別と生存が独立なら,両方に当てはまる人の割合は,この2つの積になるはずです。

\[P(\text{男性} \cap \text{死亡}) = \frac{\text{行合計}}{N} \times \frac{\text{列合計}}{N}\]

期待される人数は,この割合に総数 \(N\) を掛けたものです。

\[E = N \times \frac{\text{行合計}}{N} \times \frac{\text{列合計}}{N} = \frac{\text{行合計} \times \text{列合計}}{N}\]

\(N\) が1つ約分されて消え,見慣れた形になりました。

数値で確かめる:タイタニックの表では \(N = 2201\),男性の行合計 \(1731\),死亡の列合計 \(1490\) なので

\[E_{\text{男性・死亡}} = \frac{1731 \times 1490}{2201} = 1171.83\]

chisq.test(tab)$expected が返す値と一致します。4つのセルの期待度数は次のとおりです。

死亡 生存 行合計
男性 1171.83 559.17 1731
女性 318.17 151.83 470
列合計 1490 711 2201

観測値(男性の死亡は \(1364\) 人)はこれより大きく,\(E\) から離れています。期待度数は「独立だったらこうなるはずの表」であり,カイ二乗検定はそこからのズレを測っているのです。

使った数学:独立の定義(確率の積)と,割合に総数を掛けて人数に戻す操作。

導出②:なぜ「ズレの二乗を期待度数で割る」のか

ズレ \(O - E\) をそのまま全セル足すと,必ず 0 になります。

数値で確かめる:タイタニックの4セルのズレは

死亡 生存
男性 +192.17 −192.17
女性 −192.17 +192.17

で,合計は \(0\) です。行合計と列合計が観測値と一致するように \(E\) を作ったので,プラスとマイナスが必ず打ち消し合います。これは第3回で見た「偏差をそのまま足すと 0」と同じ事情です。

そこで二乗して符号を消します。ここまでは分散のときと同じ考え方です。

残るのは「なぜ \(E\) で割るのか」です。同じ大きさのズレでも,期待される規模が違えば意味が違うからです。

期待度数 \(E\) ズレ \(O-E\) \((O-E)^2/E\)
10 5 2.5
1000 5 0.025

10人しか来ないはずのところに5人多いのは大事件ですが,1000人来るところで5人多いのは誤差のうちです。\(E\) で割ることで,ズレを期待される規模に対する相対的な大きさに直しています。

数値で確かめる:4セルとも \(|O-E| = 192.17\) ですが,\(E\) が小さいセルほど寄与が大きくなり,合計は \(\chi^2 = 456.87\) になります。

本文の出力 X-squared = 454.5 と少しずれるのはなぜか。 R は 2×2 の表に対して既定でイェーツの連続性補正を掛けます(出力の1行目に with Yates' continuity correction と書かれています)。補正なしの値が \(456.87\),補正後が \(454.50\) です。chisq.test(tab, correct = FALSE) とすると補正なしの値が得られます。結論は変わりません。

使った数学:偏差の和が 0 になること,二乗で符号を消すこと,相対化のための割り算。


4. Rでカイ二乗検定を実行する

chisq.test()

# クロス集計表の作成
tab <- xtabs(Freq ~ Sex + Survived, data = titanic_summary)
tab
##         Survived
## Sex        No  Yes
##   Male   1364  367
##   Female  126  344
# カイ二乗検定
chisq.test(tab)
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  tab
## X-squared = 454.5, df = 1, p-value < 2.2e-16

結果の読み方

Pearson's Chi-squared test with Yates' continuity correction

X-squared = 454.5, df = 1, p-value < 2.2e-16
項目 意味
X-squared カイ二乗統計量 χ²
df 自由度(= (行数-1)×(列数-1))
p-value p値

p < .05 ならば「性別と生存は独立でない」(関連がある)と結論できます。

出力の「Yates’ continuity correction」とは。

2×2 の表のとき,R は既定でイェーツの連続性補正を適用します。 カイ二乗検定は「とびとびの度数」を「なめらかな分布」で近似しているので,そのズレを少し埋め合わせる調整です。 \(\chi^2\) がわずかに小さくなり,\(p\) 値は大きめ,つまり慎重な側に出ます。

今回は補正あり \(454.5\),補正なし \(456.9\) で,結論は変わりません。 外すときは chisq.test(tab, correct = FALSE) と書きます。2×2 以外の表では,そもそも適用されません。

前提条件:期待度数が小さいときは使えない

カイ二乗検定は近似を使っています。その近似は,各セルに十分な人数が期待されるときにしか成り立ちません。

目安は「期待度数が5未満のセルが全体の20%を超えないこと」です。満たさない場合,Rは警告を出します。

sparse <- matrix(c(8, 2, 3, 7), nrow = 2, byrow = TRUE,
                 dimnames = list(c("A群", "B群"), c("はい", "いいえ")))
chisq.test(sparse)
## Warning in chisq.test(sparse): カイ自乗近似は不正確かもしれません
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  sparse
## X-squared = 3.2323, df = 1, p-value = 0.0722

期待度数を確認してみます。

chisq.test(sparse)$expected
##     はい いいえ
## A群  5.5    4.5
## B群  5.5    4.5

5を下回るセルがあります。このようなときは,近似を使わず正確に確率を計算するフィッシャーの正確確率検定を使います。

fisher.test(sparse)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  sparse
## p-value = 0.06978
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
##    0.8821175 127.0558418
## sample estimates:
## odds ratio 
##   8.153063

実務上の注意:警告が出たまま結果を報告しないこと。 期待度数を確認し,小さければ fisher.test() に切り替えます。 サンプルサイズが小さい研究ではよく起こります。

どのセルが効いているのか:残差を見る

2×2の表なら「関連がある」で十分ですが,3×2以上の表では「どこが効いているのか」が分かりません。 そこで調整済み残差を見ます。

# 等級(Class)と生存の関係:4×2 の表
tab_class <- xtabs(Freq ~ Class + Survived, data = titanic)
tab_class
##       Survived
## Class   No Yes
##   1st  122 203
##   2nd  167 118
##   3rd  528 178
##   Crew 673 212
chisq.test(tab_class)
## 
##  Pearson's Chi-squared test
## 
## data:  tab_class
## X-squared = 190.4, df = 3, p-value < 2.2e-16
# 調整済み残差:絶対値が 2 を超えるセルが「効いている」
round(chisq.test(tab_class)$stdres, 2)
##       Survived
## Class      No    Yes
##   1st  -12.59  12.59
##   2nd   -3.52   3.52
##   3rd    4.89  -4.89
##   Crew   6.87  -6.87

残差が正なら期待より多い,負なら期待より少ないことを表します。絶対値がおおむね2を超えるセルに注目します。

この表では1等が生存側に大きく正,3等と乗員が生存側に負で,「等級が高いほど生存しやすかった」という中身まで読み取れます。検定は「関連の有無」しか教えてくれません。中身は残差を見て初めて分かります。


5. 効果量:クラメールの V

p値は「統計的に有意かどうか」を示しますが, 「効果の大きさ」は別途,効果量で表します。

\[V = \sqrt{\frac{\chi^2}{n \cdot (k-1)}}\]

(n = 総度数,k = 行数・列数の小さい方)

# 効果量の計算(手動)
# 効果量は「関連の強さ」なので,連続性補正を外した χ² を使います
chi2 <- unname(chisq.test(tab, correct = FALSE)$statistic)   # unname() で「X-squared」という名前を外す
n    <- sum(tab)
k    <- min(nrow(tab), ncol(tab))
V    <- sqrt(chi2 / (n * (k - 1)))
round(V, 3)
## [1] 0.456

なぜ correct = FALSE なのか。 イェーツ補正は,\(p\) 値を慎重な側に寄せるための調整でした。 一方 \(V\) が表したいのは「関連がどれくらい強いか」であって,偶然かどうかの判定ではありません。 目的が違うので,効果量は補正を外した \(\chi^2\) から計算するのが一般的です。

今回は補正あり \(V = 0.454\),補正なし \(V = 0.456\) で解釈は変わりませんが, セルの度数が小さい表では差が開きます。

クラメールのVの目安

V の値 効果の大きさ
0.1〜0.3 小
0.3〜0.5 中
0.5〜 大
導出③:自由度が \((\text{行数}-1)(\text{列数}-1)\) になる理由と,\(V\) が \(n\) に振り回されない理由

まず自由度から。 カイ二乗検定では,行合計と列合計は観測された表のものに固定されています。すると,自由に決められるセルはごくわずかしかありません。

2×2 の表で確かめます。左上(男性・死亡)を \(1364\) と決めた瞬間,残りの3つは引き算で自動的に決まります。

  • 右上 = 男性の行合計 − 左上 = \(1731 - 1364 = 367\)
  • 左下 = 死亡の列合計 − 左上 = \(1490 - 1364 = 126\)
  • 右下 = 残り = \(344\)

つまり自由に動けるのは1つだけです。これが \(df = (2-1)(2-1) = 1\) の正体です。

一般には,各行で最後の1つが行合計から決まり,各列で最後の1つが列合計から決まるので,自由に決められるのは \((\text{行数}-1) \times (\text{列数}-1)\) 個になります。

次に \(V\) です。 \(\chi^2\) には困った性質があります。同じ関連の強さでも,データを増やすだけで大きくなるのです。全セルを \(m\) 倍すると,\((O-E)^2\) は \(m^2\) 倍,\(E\) は \(m\) 倍なので,比は \(m\) 倍になります。

\[\chi^2 \ \longrightarrow \ m\,\chi^2\]

一方 \(V\) の定義は

\[V = \sqrt{\frac{\chi^2}{n(k-1)}}\]

で,分子の \(\chi^2\) も分母の \(n\) も同じ \(m\) 倍になるため,約分されて \(V\) は変わりません。

数値で確かめる(補正なしの \(\chi^2\) で計算):

データの量 \(n\) \(\chi^2\) \(V\) \(p\)
そのまま 2,201 456.87 0.4556 \(2 \times 10^{-101}\)
2倍 4,402 913.75 0.4556 \(1 \times 10^{-200}\)
5倍 11,005 2284.37 0.4556 ほぼ 0
10倍 22,010 4568.74 0.4556 ほぼ 0

\(\chi^2\) と \(p\) はいくらでも動かせるのに,\(V\) は \(0.4556\) から動きません。

「有意である」と「関連が強い」は,別のことを言っています。 \(p\) は \(n\) を増やせば小さくできます。\(V\) は関連の強さそのものなので,\(n\) では動きません。だから本文7節のように \(\chi^2\)・\(p\)・\(V\) を並べて報告します。

なお,上の表も本文5節も,どちらも補正なしの \(\chi^2\) から \(V\) を計算しています(\(V = 0.4556\))。補正項は \(n\) に依存するため,補正を入れたまま倍率を変えると \(V\) がわずかに動いてしまい,「\(V\) は \(n\) に振り回されない」という性質が見えにくくなるからです。参考までに,補正ありで計算すると \(V = 0.454\) です。

使った数学:数え上げ(自由度),および比を取ると共通の倍率が消えること。


6. 可視化:積み上げ棒グラフ

titanic_pct <- titanic_summary |>
  group_by(Sex) |>
  mutate(pct = Freq / sum(Freq) * 100)

ggplot(titanic_pct, aes(x = Sex, y = pct, fill = Survived)) +
  geom_col() +
  scale_fill_manual(values = c("No" = "#D95F02", "Yes" = "#1B9E77"),
                    labels = c("No" = "死亡", "Yes" = "生存")) +
  labs(title = "タイタニック:性別と生存率",
       x = "性別", y = "割合(%)", fill = "生存") +
  theme_bw()


7. 結果の報告

論文・レポートでの記述例:

性別と生存の独立性を検定するためにカイ二乗検定を実施した。 その結果,性別と生存の間には有意な関連が認められた, χ²(1, N = 2201) = 454.5, p < .001, V = .46。

\(\chi^2\) と \(p\) は chisq.test() の既定(イェーツ補正あり)の値, \(V\) は補正を外して計算した値です。検定と効果量で目的が違うためで,食い違いではありません。


演習

演習1:クロス集計表の作成

mtcars データを使って,シリンダー数(cyl)とトランスミッション(am)の クロス集計表を作成してください。

table(mtcars$cyl, mtcars$am)

演習2:カイ二乗検定

演習1のクロス集計表を使って,カイ二乗検定を実施し,結果を解釈してください。

  • 仮説を日本語で書く
  • chisq.test() を実行する
  • 期待度数を確認する
  • p値と自由度を確認し,結論を書く
tab <- table(mtcars$cyl, mtcars$am)
chisq.test(tab)
chisq.test(tab)$expected

⚠ この表では警告が出ます

実行すると 「カイ自乗近似は不正確かもしれません」という警告が出ます。 期待度数を見ると,6セルのうち3セル(50%)が5未満です。 4節の目安「5未満のセルが全体の20%を超えないこと」を満たしていません。

警告が出たまま報告してはいけません。 フィッシャーの正確確率検定に切り替えてください。

fisher.test(tab)

この例では,カイ二乗検定が \(p\) = .013,フィッシャーが \(p\) = .009 で, どちらも有意という結論は変わりません。 結論が変わらないことを確かめたうえで,フィッシャーの結果を報告します。

演習3:可視化

mtcars の cyl(シリンダー数)と am(0=オートマ, 1=マニュアル)の 関係を積み上げ棒グラフで表現してください。

mtcars |>
  mutate(
    am  = factor(am,  labels = c("オートマ", "マニュアル")),
    cyl = factor(cyl, labels = c("4気筒", "6気筒", "8気筒"))
  ) |>
  group_by(cyl, am) |>
  summarise(n = n(), .groups = "drop") |>
  group_by(cyl) |>
  mutate(pct = n / sum(n) * 100) |>
  ggplot(aes(x = cyl, y = pct, fill = am)) +
  geom_col() +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "シリンダー数とトランスミッションの関係",
       x = "シリンダー数", y = "割合(%)", fill = "トランスミッション") +
  theme_bw()

本日のまとめ

  • カテゴリデータの分析にはクロス集計表とカイ二乗検定を使う
  • table() / xtabs() でクロス集計表を作成
  • chisq.test() で独立性を検定
  • 期待度数が5未満のセルがあれば fisher.test() に切り替える
  • 3×2以上の表では調整済み残差を見て,どのセルが効いているかを読む
  • 効果量(クラメールの V)も一緒に報告する
  • 積み上げ棒グラフでカテゴリ間の比率を可視化する

宿題・予習

  • 演習2の結果をレポート形式でまとめる(1〜2文)
  • 次回は t検定(2群の平均値の比較) を学びます
  • 教科書のt検定に関する章を読んでおく

← 授業トップへ戻る