カイ二乗検定(chi-square test)は、カテゴリカルデータの分析に使われる最も基本的な検定手法です。観測されたデータが期待される分布に従うか(適合度検定)、2つの変数が独立か(独立性の検定)を判断します。
アンケートの分析、品質管理、遺伝学のメンデルの法則の検証など、カテゴリカルデータが関わるあらゆる場面で活用されます。
本記事の内容
- カイ二乗分布の定義と性質
- 適合度検定(goodness-of-fit test)
- 独立性の検定(test of independence)
- Pythonでの実装
前提知識
この記事を読む前に、以下の概念を理解しておくと理解が深まります。
- 正規分布の基本
- 仮説検定の基本的な枠組み
カイ二乗分布
定義
$Z_1, Z_2, \dots, Z_k$ が独立に標準正規分布 $N(0, 1)$ に従うとき、
$$ \chi^2 = Z_1^2 + Z_2^2 + \cdots + Z_k^2 $$
は自由度 $k$ のカイ二乗分布 $\chi^2(k)$ に従います。
確率密度関数
$$ f(x) = \frac{1}{2^{k/2} \Gamma(k/2)} x^{k/2 – 1} e^{-x/2} \quad (x > 0) $$
性質
- 期待値: $E[\chi^2] = k$
- 分散: $\mathrm{Var}(\chi^2) = 2k$
- 非負の値のみを取る($\chi^2 \geq 0$)
- $k$ が大きいとき、正規分布に近似できる(中心極限定理)
適合度検定
設定
$k$ 個のカテゴリがあり、各カテゴリの観測度数 $O_i$ と理論的な期待度数 $E_i$ が与えられているとき、データが理論的な分布に従うかを検定します。
帰無仮説: $H_0$: データは理論分布に従う
検定統計量:
$$ \chi^2 = \sum_{i=1}^{k} \frac{(O_i – E_i)^2}{E_i} $$
$H_0$ のもとで、$\chi^2 \sim \chi^2(k – 1)$(自由度 $k-1$)に近似的に従います。
直感的な理解
各カテゴリで「観測値と期待値のずれ」を計算し、期待値で正規化して合計しています。ずれが大きいほど $\chi^2$ 値が大きくなり、帰無仮説に対する証拠が強くなります。
具体例: サイコロの公正性の検定
サイコロを120回振った結果が以下のとき、サイコロが公正かを検定します。
| 目 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 観測度数 $O_i$ | 15 | 23 | 18 | 21 | 24 | 19 |
| 期待度数 $E_i$ | 20 | 20 | 20 | 20 | 20 | 20 |
$$ \chi^2 = \frac{(15-20)^2}{20} + \frac{(23-20)^2}{20} + \frac{(18-20)^2}{20} + \frac{(21-20)^2}{20} + \frac{(24-20)^2}{20} + \frac{(19-20)^2}{20} $$
$$ = \frac{25 + 9 + 4 + 1 + 16 + 1}{20} = \frac{56}{20} = 2.8 $$
自由度 $k – 1 = 5$ のカイ二乗分布で、$\chi^2_{0.05}(5) = 11.07$ です。$2.8 < 11.07$ なので帰無仮説を棄却できません。つまり、サイコロが不正であるという十分な証拠はありません。
独立性の検定
設定
2つのカテゴリカル変数が独立かどうかを分割表(クロス表)を用いて検定します。
$r \times c$ の分割表で、セル $(i, j)$ の観測度数を $O_{ij}$、期待度数を $E_{ij}$ とします。
2つの変数が独立なら、
$$ E_{ij} = \frac{R_i \times C_j}{N} $$
ここで $R_i$ は第 $i$ 行の合計、$C_j$ は第 $j$ 列の合計、$N$ は全体の合計です。
検定統計量:
$$ \chi^2 = \sum_{i=1}^{r} \sum_{j=1}^{c} \frac{(O_{ij} – E_{ij})^2}{E_{ij}} $$
自由度は $(r-1)(c-1)$ です。
具体例: 性別と商品の好みの独立性
| 商品A | 商品B | 商品C | 合計 | |
|---|---|---|---|---|
| 男性 | 30 | 20 | 50 | 100 |
| 女性 | 40 | 30 | 30 | 100 |
| 合計 | 70 | 50 | 80 | 200 |
独立を仮定した場合の期待度数:
$$ E_{11} = \frac{100 \times 70}{200} = 35, \quad E_{12} = \frac{100 \times 50}{200} = 25, \quad E_{13} = \frac{100 \times 80}{200} = 40 $$
| 商品A | 商品B | 商品C | |
|---|---|---|---|
| 男性 期待 | 35 | 25 | 40 |
| 女性 期待 | 35 | 25 | 40 |
$$ \chi^2 = \frac{(30-35)^2}{35} + \frac{(20-25)^2}{25} + \frac{(50-40)^2}{40} + \frac{(40-35)^2}{35} + \frac{(30-25)^2}{25} + \frac{(30-40)^2}{40} $$
$$ = \frac{25}{35} + \frac{25}{25} + \frac{100}{40} + \frac{25}{35} + \frac{25}{25} + \frac{100}{40} = 0.714 + 1.0 + 2.5 + 0.714 + 1.0 + 2.5 = 8.428 $$
自由度 $(2-1)(3-1) = 2$、$\chi^2_{0.05}(2) = 5.991$ なので、$8.428 > 5.991$ より帰無仮説を棄却します。性別と商品の好みには関連があると言えます。
Pythonでの実装
適合度検定
import numpy as np
from scipy import stats
# サイコロの公正性の検定
observed = np.array([15, 23, 18, 21, 24, 19])
expected = np.array([20, 20, 20, 20, 20, 20])
# 手計算
chi2_manual = np.sum((observed - expected)**2 / expected)
df = len(observed) - 1
p_manual = 1 - stats.chi2.cdf(chi2_manual, df)
print(f"手計算: χ² = {chi2_manual:.3f}, df = {df}, p = {p_manual:.4f}")
# SciPyによる検定
chi2_scipy, p_scipy = stats.chisquare(observed, expected)
print(f"SciPy: χ² = {chi2_scipy:.3f}, p = {p_scipy:.4f}")
if p_scipy < 0.05:
print("結論: サイコロは公正でない可能性がある(有意水準5%)")
else:
print("結論: サイコロが不正であるという証拠は不十分")
独立性の検定
import numpy as np
from scipy import stats
# 分割表
observed = np.array([[30, 20, 50],
[40, 30, 30]])
chi2, p, dof, expected = stats.chi2_contingency(observed)
print("観測度数:")
print(observed)
print(f"\n期待度数:")
print(np.round(expected, 1))
print(f"\nχ² = {chi2:.3f}")
print(f"自由度 = {dof}")
print(f"p値 = {p:.4f}")
if p < 0.05:
print("結論: 2変数は独立でない(関連がある)")
else:
print("結論: 2変数が独立でないとは言えない")
カイ二乗分布の可視化
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
fig, ax = plt.subplots(figsize=(10, 6))
x = np.linspace(0, 25, 500)
for df in [1, 2, 3, 5, 10]:
ax.plot(x, stats.chi2.pdf(x, df), linewidth=2, label=f'$k = {df}$')
ax.set_xlabel('$x$')
ax.set_ylabel('Density')
ax.set_title('Chi-squared distribution $\\chi^2(k)$')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_ylim(0, 0.5)
plt.tight_layout()
plt.savefig('chi_square_distribution.png', dpi=150, bbox_inches='tight')
plt.show()
適合度検定のシミュレーション
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
np.random.seed(42)
# 公正なサイコロのシミュレーション
n_simulations = 10000
n_rolls = 120
chi2_values = []
for _ in range(n_simulations):
rolls = np.random.randint(1, 7, n_rolls)
observed = np.array([np.sum(rolls == i) for i in range(1, 7)])
expected = np.full(6, n_rolls / 6)
chi2 = np.sum((observed - expected)**2 / expected)
chi2_values.append(chi2)
fig, ax = plt.subplots(figsize=(10, 6))
ax.hist(chi2_values, bins=50, density=True, alpha=0.7, color='steelblue',
edgecolor='white', label='Simulated')
x = np.linspace(0, 20, 200)
ax.plot(x, stats.chi2.pdf(x, 5), 'r-', linewidth=2, label='$\\chi^2(5)$ theory')
ax.axvline(x=stats.chi2.ppf(0.95, 5), color='green', linestyle='--', linewidth=2,
label=f'Critical value ($\\alpha=0.05$) = {stats.chi2.ppf(0.95, 5):.2f}')
ax.set_xlabel('$\\chi^2$')
ax.set_ylabel('Density')
ax.set_title('Chi-squared goodness-of-fit test: Simulated distribution')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('chi_square_simulation.png', dpi=150, bbox_inches='tight')
plt.show()
まとめ
本記事では、カイ二乗検定の理論と使い方を解説しました。
- カイ二乗分布: 標準正規変数の二乗和が従う分布
- 適合度検定: 観測データが理論分布に従うかを検定
- 独立性の検定: 2つのカテゴリカル変数が独立かを検定
- 検定統計量: $\chi^2 = \sum (O_i – E_i)^2 / E_i$
- 注意: 期待度数が5未満のセルがある場合はフィッシャーの正確検定を使うべき
次のステップとして、分散分析(ANOVA)について学びましょう。