メインコンテンツへスキップ
ブログ一覧に戻る
統計・データサイエンス

カイ二乗検定|適合度・独立性を一目で理解(効果量V付き)

2025年10月29日
最終更新: 2026年10月8日
8分で読めます

この記事でわかること

カイ二乗検定の適合度と独立性を、度数表と帰無仮説から使い分けます。架空140人の観測度数・期待度数・自由度・p値・Cramér’s Vを計算し、Pearson残差と正しい面積のモザイク図を確認します。小さい期待度数、巨大な標本、2群の標本数近似まで、Pythonの3教材と練習問題で判断の条件を具体的に解説します。

2026年10月8日に、SciPyの独立性検定とASAのp値声明を再読しました。クロス表の問い、期待度数、自由度、小標本での近似・補正・再標本化を分け、p値を関連の大きさや因果、顧客性質の保証とは扱いません。

30秒で要点

カイ二乗検定は、カテゴリ別の人数や件数が、帰無仮説のもとで期待する内訳とどの程度ずれているかを調べます。「色と好みに関連があるか」と「購入経路の割合が事前に決めた比率と合うか」では、同じ度数でも帰無仮説が違います。

表が収まらない場合は、横にスクロールできます

確かめたいこと検定と帰無仮説先に確認すること
1つのカテゴリ変数の分布適合度検定:確率が指定した分布に等しい比率をデータを見る前に決めたか、合計が1か
2つのカテゴリ変数の関連独立性検定:両変数が独立1人を重複計上していないか、各セルの期待度数
差が実務上どれくらいあるか割合差とCramér’s Vも確認母数、費用、重要とする差を事前に決めたか

小さいp値は、帰無仮説と観測結果の食い違いを示します。効果の大きさ、施策の利益、因果関係まで一度に答える値ではありません。度数や母数の整理は統計学の基礎、検定の考え方は仮説検定の読み方で確認できます。

度数にする前に、観測の単位を決める

色の違うボタンを利用者に無作為に割り当て、利用者ごとに「クリックした/しなかった」を1回ずつ数えるなら、2×2表を作れます。一方、同じ利用者の多数の表示回数を独立した人数として数えると、検定の前提が変わります。対応のある前後比較、クラスタ化された観測、多重回答には、その構造に合う方法が必要です。

入力は割合だけでなく度数です。例えば10%という値だけでは、10人中1人か、10万人中1万人かが分かりません。欠測や重複をどう扱ったか、何人が集計対象かも表に添えます。SciPyの独立性検定の資料は、周辺度数から期待度数を求める方法と近似上の注意を示しています。

架空の140人で、観測度数と期待度数を比べる

次は計算を学ぶための架空データです。赤・青を見た各70人の回答であり、実際の顧客調査や施策実績ではありません。

表が収まらない場合は、横にスクロールできます

見た色好き普通嫌い行合計
赤30251570
青20302070
列合計505535140

独立なら、各行の好みの構成比は全体と同じになるはずです。期待度数は Eij = 行合計i × 列合計j / N。赤・好きなら 70 × 50 / 140 = 25 です。

表が収まらない場合は、横にスクロールできます

独立を仮定した期待度数好き普通嫌い行合計
赤2527.517.570
青2527.517.570

期待度数は人数の予測値なので、小数でも問題ありません。観測度数Oとの差を χ² = Σ (Oij − Eij)² / Eij でまとめます。この2×3表では自由度は (2−1)×(3−1)=2、χ² ≈ 3.168831、p ≈ 0.205068 になります。事前に有意水準を5%とした場合は棄却しませんが、独立が証明されたわけではありません。

架空の140人について、独立なら期待する25・27.5・17.5人と、赤30・25・15人、青20・30・20人の観測値を面積で比較するモザイク図
幅は各行70/140、縦の割合はその行内の回答割合。右の観測図では、各長方形の面積が観測度数/140になる。左は帰無仮説の期待度数を同じ尺度で示す。数値と条件は本文の表でも確認できる。

面積と残差は、違う情報を見ている

モザイク図の縦の割合を全体の列比率で固定すると、描けるのは「独立ならこうなる」という期待分布です。観測分布を描くときは、各行内の観測比率を使います。図の面積を比べれば、赤では「好き」が期待より多く、「普通」「嫌い」が少ないことが分かります。

(Oij−Eij)/√Eij はPearson残差です。赤の3セルは約 1.000、−0.477、−0.598、青は逆符号になります。ここで使う残差は、行・列の比率でも調整する「調整済み標準化残差」とは別です。セルを見つけた後に多数の有意性判定をする場合は、多重比較の扱いも必要になります。

効果量Vを、判断の大きさにつなげる

V = √[χ² / {N × min(r-1, c-1)}] で求めるCramér’s Vは、この表では約 0.150448。カテゴリ同士の関連の強さを表す0〜1の値です。符号や「赤が良い」という方向は持たないので、セルごとの割合と併せて読みます。SciPyのassociationも、連続変数の相関とは区別しています。

0.1・0.3・0.5などの目安だけで、投資価値を決めることはできません。同じVでも表の大きさ、分野、判断の費用が違います。クリック率なら割合差と不確実性、売上なら利益と実装費も確認します。無作為割り当てのない集計では、交絡や選択の影響も残ります。

Python教材1:検定・期待度数・Vを同時に出す

NumPyとSciPyを使います。2×3の教材なのでYates補正は適用しません。実務で2×2表を使うときは、補正や別手法を含めた検定仕様を結果を見る前に決めてください。

import numpy as np
from scipy.stats import chi2_contingency

# Fictional counts: rows=red/blue, columns=like/neutral/dislike
obs = np.array([[30, 25, 15], [20, 30, 20]])
chi2, p, dof, expected = chi2_contingency(obs, correction=False)
n = obs.sum()
v = np.sqrt(chi2 / (n * min(obs.shape[0]-1, obs.shape[1]-1)))
print(expected)
print(f"chi2={chi2:.6f}, df={dof}, p={p:.6f}, V={v:.6f}")
assert np.allclose(expected, [[25, 27.5, 17.5]] * 2)
assert dof == 2 and np.isclose(chi2, 244 / 77)

Python教材2:Pearson残差と観測モザイクを描く

次は上と同じ架空度数を使う独立したコードです。Matplotlibで残差の色と観測面積を分けて描きます。SVGファイルは実行した作業ディレクトリに保存されます。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

obs = np.array([[30, 25, 15], [20, 30, 20]], dtype=float)
row = obs.sum(axis=1)
expected = np.outer(row, obs.sum(axis=0)) / obs.sum()
residual = (obs - expected) / np.sqrt(expected)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
im = axes[0].imshow(residual, cmap="RdBu_r", vmin=-1.5, vmax=1.5)
for i in range(2):
    for j in range(3):
        axes[0].text(j, i, f"{residual[i,j]:.3f}", ha="center", va="center")
axes[0].set_xticks(range(3), ["Like", "Neutral", "Dislike"])
axes[0].set_yticks(range(2), ["Red", "Blue"])
axes[0].set_title("Pearson residuals")
fig.colorbar(im, ax=axes[0])
colors = ["#126e82", "#d29c36", "#aa5365"]
x = 0.0
for i in range(2):
    width = row[i] / obs.sum()
    y = 0.0
    for j in range(3):
        height = obs[i,j] / row[i]  # conditional observed proportion
        axes[1].add_patch(Rectangle((x,y), width, height,
                                   facecolor=colors[j], edgecolor="white"))
        axes[1].text(x+width/2, y+height/2, f"{obs[i,j]:.0f}",
                     ha="center", va="center", color="white")
        assert np.isclose(width * height, obs[i,j] / obs.sum())
        y += height
    x += width
axes[1].set(xlim=(0,1), ylim=(0,1), title="Observed mosaic")
axes[1].set_xticks([0.25, 0.75], ["Red", "Blue"])
fig.tight_layout()
fig.savefig("chi-square-teaching.svg")
plt.close(fig)

適合度検定は「指定した比率」と比べる

架空の80件の購入経路を4種類に分け、事前の仮説がすべて25%なら期待度数は各20件です。観測が 24、18、22、16 件なら、χ² = 0.8+0.2+0.2+0.8 = 2、確率を事前指定した自由度は 4−1=3 です。独立性検定のように行・列の周辺度数から期待値を作るわけではありません。

同じデータから分布のパラメータを推定してから適合度を検定すると、自由度や近似の扱いが変わる場合があります。chisquareの資料に沿い、観測と期待の合計も一致させます。

小さい期待度数と、大きすぎる標本への対応

一般的な近似の目安としてセルの期待度数5などが使われますが、これは保証の境界線ではありません。期待度数3のセルを見つけたら、入力誤り、カテゴリの定義、集計単位を先に確認します。結果を有意にするためにカテゴリを統合するのは避けます。設計に応じた正確検定・再標本化法を検討し、利用できる表の形やライブラリの条件も確かめます。

反対にNが大きいと、小さなずれでもp値が小さくなります。統計的に有意かだけでなく、V、割合差、区間推定、実務上必要な差を報告します。連続量なら、区切って情報を落とす前にt検定や分散分析など元の尺度を使う方法も考えます。

Python教材3:2群の割合差に限った標本数の近似

「10%から13%への変化」を検出したい架空の2群計画です。有意水準5%、検出力80%、同数割り当て、両側検定とし、Cohenのhを使った正規近似で1群当たり人数を求めます。これは2×3表の一般的な検出力計算ではありません。NormalIndPowerの対象である独立2標本の近似です。脱落、クラスタ、途中のぞき見、多重比較はこの計算に含めていません。

import math
from statsmodels.stats.power import NormalIndPower
from statsmodels.stats.proportion import proportion_effectsize

h = abs(proportion_effectsize(0.10, 0.13))
n_per_group = NormalIndPower().solve_power(
    effect_size=h, alpha=0.05, power=0.80, ratio=1.0,
    alternative="two-sided")
print(f"h={h:.6f}, n_per_group={math.ceil(n_per_group)}")

練習問題:検定後に何を決めるか

  1. 2×3表に期待度数3のセルがある。 近似条件と集計設計を調べ、正確検定や再標本化法を検討します。都合のよい統合でp値を小さくしません。
  2. p値は小さいがV=0.08だった。 一律の「価値なし」とせず、割合差、対象人数、利益、実装費と照らします。表の中身がなければ方向も分かりません。
  3. 巨大なNでほぼすべて有意になる。 判定の中心を事前に必要とした効果とその不確実性へ戻し、p値だけで優先順位を付けません。

少数セルへの対応と巨大標本の読み方は、この2つを対で覚えると誤解しにくくなります。前者は近似の信頼性、後者は検出された差の重要性という別の問題です。

実務で残す記録

観測単位、対象期間、除外理由、元の度数表、期待度数、検定・補正の指定、χ²・自由度・p値・V、割合差と意思決定を残します。色の変更を採用する判断なら、A/Bテストの統計とコンバージョン改善をつなげ、観測上の関連と介入の結果を分けてください。

分析の位置づけはデータ分析手法の入口、p値と効果の判断は統計判断のハブ、利用者の行動へ結び付ける進め方はデータに基づくUX改善に整理しています。

よくある質問(FAQ)