はじめに
カイ二乗分布(χ² 分布)は、統計検定やデータ解析で最もよく使われる分布の一つです。
実務では、
モデルでフィットして χ² が自由度くらいなら、まあ良い
といった経験則で判断することも多いと思います。
ただ、
- それは どれくらい起こりやすい値なのか
- 「良い」「悪い」は どこから決まるのか
- reduced χ²が なぜ便利なのか
を、分布として意識する機会は案外多くありません。
この記事では、
- カイ二乗分布そのものの形
- 有意水準(右側・左側)
- 自由度による振る舞いの違い
- reduced χ²(縮約カイ二乗)の分布
を 可視化しながら 整理してみます。
本記事の実装コードは Google Colab こちら から実行できます。
カイ二乗分布とは
カイ二乗分布は、標準正規分布に従う独立な確率変数 $z_i$ を用いて、
\chi^2 = \sum_{i=1}^{\nu} z_i^2
と定義されます。
ここで $z_i$ は平均 0・分散 1 の標準正規分布、$\nu$ は自由度です。
直感的には、
正規分布に従うズレを標準化して二乗し、それを $\nu$ 個足し合わせた量
だと考えれば十分です。
この定義から、次の性質が従います。
- $\chi^2 \ge 0$(二乗和なので負にならない)
- 分布は右に裾を引く非対称な形
- 平均:$\langle \chi^2 \rangle = \nu$
- 分散:$\mathrm{Var}(\chi^2) = 2\nu$
つまり、
$\chi^2 \approx \nu$
という状態は、「モデルが正しければ最も起こりやすい値」 を意味しています。
reduced χ²とは
reduced χ² (補正カイ二乗)は、χ² を自由度で割った量として
\chi^2_{\rm red} = \frac{\chi^2}{\nu}
と定義されます。
χ² の性質から、
- 平均:$\langle \chi^2_{\rm red} \rangle = 1$
- 分散:$\mathrm{Var}(\chi^2_{\rm red}) = \frac{2}{\nu}$
となり、期待値が常に 1 に揃うのが特徴です。
この性質によって、
自由度の異なるフィット結果を 同じ尺度で比較できるようになります。
有意水準を考える
なぜ右側だけを見るのか
多くの場合、カイ二乗検定では
χ² が 大きすぎる
かどうかを問題にします。
誤差の見積もりが妥当で、モデルが正しければ、
χ² は平均 $\nu$ の周りに分布するはずだからです。
右側の裾(たとえば 95% 点)を超える場合、
「こんな大きな χ² は、偶然では起きにくい」
と判断します。
左側も実は意味がある
一方で、
- χ² が 小さすぎる
場合も、実務的には無視できません。
例えば、
- 誤差を過大評価している
- データ点やモデルが独立でない
といった可能性が考えられます。
つまり、
右側だけでなく、左側も含めて分布全体を見る
ことが、モデル診断として重要になります。
χ² と reduced χ² の分布をまず見てみる
ここまでの話を踏まえて、まずは 分布そのものの形を見てみます。
- 曲線:自由度 $\nu = 3, 13, 23, \dots, 193$
- 左:χ² の分布
- 右:reduced χ² の分布
| χ² | reduced χ² |
|---|---|
![]() |
![]() |
χ² コードはこちら
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import chi2
from matplotlib import colors, colormaps
# ============================================================
# Chi-square PDF (raw chi-square)
# ============================================================
# Chi-square axis
x = np.linspace(0, 250, 2000)
# Degrees of freedom to display
nu_list = np.arange(3, 201, 10)
fig, ax = plt.subplots(figsize=(6, 4))
# Colormap setup
norm = colors.Normalize(vmin=nu_list.min(), vmax=nu_list.max())
cmap = colormaps["turbo"]
for nu in nu_list:
pdf = chi2.pdf(x, nu)
ax.plot(
x, pdf,
color=cmap(norm(nu)),
lw=1,
alpha=0.9
)
ax.set_xlabel(r"$\chi^2$")
ax.set_ylabel("PDF")
# Colorbar
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
sm.set_array([])
cbar = fig.colorbar(sm, ax=ax)
cbar.set_label(r"degrees of freedom $\nu$")
fig.tight_layout()
plt.show()
reduced χ² コードはこちら
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import chi2
from matplotlib import colors, colormaps
# ============================================================
# Chi-square PDF (reduced chi-square)
# ============================================================
# Reduced chi-square axis
x = np.linspace(0, 3, 1000)
# Degrees of freedom to display
nu_list = np.arange(3, 201, 10)
fig, ax = plt.subplots(figsize=(6, 4))
# Colormap setup
norm = colors.Normalize(vmin=nu_list.min(), vmax=nu_list.max())
cmap = colormaps["turbo"]
for nu in nu_list:
pdf = chi2.pdf(x * nu, nu) * nu
ax.plot(
x, pdf,
color=cmap(norm(nu)),
lw=1,
alpha=0.9
)
# Mean (reduced)
ax.axvline(
1,
color="k", ls="--", lw=1.5,
label=r"$\langle \chi^2_{\rm red} \rangle = 1$"
)
ax.set_xlabel(r"$\chi^2_{\rm red}$")
ax.set_ylabel("PDF")
# Colorbar
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
sm.set_array([])
cbar = fig.colorbar(sm, ax=ax)
cbar.set_label(r"degrees of freedom $\nu$")
ax.legend()
fig.tight_layout()
plt.show()
χ² のまま見ると、自由度が増えるにつれて分布は右へ広がっていきます。
一方で、自由度で正規化した reduced χ² では、分布が 1 に向かってどんどん尖っていく 様子がわかります。
自由度ごとの信頼区間を可視化する
次に、自由度ごとに χ² と reduced χ² の信頼区間を並べて可視化します。
- 横軸:自由度 $\nu$
- 縦軸:左が $\chi^2$、右が reduced $\chi^2$
- 曲線:5%, 10%, 90%, 95% 分位点
| χ² | reduced χ² |
|---|---|
![]() |
![]() |
χ² コードはこちら
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import chi2
# ============================================================
# Chi-square quantiles (raw chi-square)
# ============================================================
# Degrees of freedom
nu = np.arange(3, 201)
fig, ax = plt.subplots(figsize=(6, 6))
# --- Lower tail ---
ax.plot(
nu, chi2.ppf(0.05, nu),
color="tab:blue", ls="--", label="5 %"
)
ax.plot(
nu, chi2.ppf(0.10, nu),
color="tab:blue", ls="-", label="10 %"
)
# --- Upper tail ---
ax.plot(
nu, chi2.ppf(0.90, nu),
color="tab:orange", ls="-", label="90 %"
)
ax.plot(
nu, chi2.ppf(0.95, nu),
color="tab:orange", ls="--", label="95 %"
)
# Mean
ax.plot(
nu, nu,
color="k", ls="--",
label=r"$\langle \chi^2 \rangle = \nu$"
)
ax.set_xlabel("degrees of freedom")
ax.set_ylabel(r"$\chi^2$")
ax.set_aspect("equal", adjustable="box")
ax.legend()
fig.tight_layout()
plt.show()
reduced χ² コードはこちら
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import chi2
# ============================================================
# Chi-square quantiles (reduced chi-square)
# ============================================================
# Degrees of freedom
nu = np.arange(3, 201)
fig, ax = plt.subplots(figsize=(6, 6))
# --- Lower tail ---
ax.plot(
nu, chi2.ppf(0.05, nu) / nu,
color="tab:blue", ls="--", label="5 %"
)
ax.plot(
nu, chi2.ppf(0.10, nu) / nu,
color="tab:blue", ls="-", label="10 %"
)
# --- Upper tail ---
ax.plot(
nu, chi2.ppf(0.90, nu) / nu,
color="tab:orange", ls="-", label="90 %"
)
ax.plot(
nu, chi2.ppf(0.95, nu) / nu,
color="tab:orange", ls="--", label="95 %"
)
# Mean (reduced)
ax.axhline(
1,
color="k", ls="--",
label=r"$\langle \chi^2_{\rm red} \rangle = 1$"
)
ax.set_xlabel("degrees of freedom")
ax.set_ylabel(r"$\chi^2_{\rm red}$")
ax.legend()
fig.tight_layout()
plt.show()
左図では、$\chi^2$ の揺れ幅が自由度とともに大きくなる様子がわかります。
一方、右図では reduced χ² が 1 を中心に収束していく様子がはっきり確認できます。
補足:自由度ごとの目安表について
下の表では、解析結果を ざっと確認するための目安 として、自由度 $\nu = 2$ から $10,000$ までを並べています。
(低自由度では細かく、高自由度では見やすさを保つために間引いて掲載しています。)
目安表はこちら
| nu | chi2red_5% | chi2red_10% | chi2red_90% | chi2red_95% |
|---|---|---|---|---|
| 2 | 0.051 | 0.105 | 2.303 | 2.996 |
| 4 | 0.178 | 0.266 | 1.945 | 2.372 |
| 6 | 0.273 | 0.367 | 1.774 | 2.099 |
| 8 | 0.342 | 0.436 | 1.670 | 1.938 |
| 10 | 0.394 | 0.487 | 1.599 | 1.831 |
| 12 | 0.436 | 0.525 | 1.546 | 1.752 |
| 14 | 0.469 | 0.556 | 1.505 | 1.692 |
| 16 | 0.498 | 0.582 | 1.471 | 1.644 |
| 18 | 0.522 | 0.604 | 1.444 | 1.604 |
| 20 | 0.543 | 0.622 | 1.421 | 1.571 |
| 22 | 0.561 | 0.638 | 1.401 | 1.542 |
| 24 | 0.577 | 0.652 | 1.383 | 1.517 |
| 26 | 0.592 | 0.665 | 1.368 | 1.496 |
| 28 | 0.605 | 0.676 | 1.354 | 1.476 |
| 30 | 0.616 | 0.687 | 1.342 | 1.459 |
| 32 | 0.627 | 0.696 | 1.331 | 1.444 |
| 34 | 0.637 | 0.704 | 1.321 | 1.429 |
| 36 | 0.646 | 0.712 | 1.311 | 1.417 |
| 38 | 0.655 | 0.720 | 1.303 | 1.405 |
| 40 | 0.663 | 0.726 | 1.295 | 1.394 |
| 42 | 0.670 | 0.733 | 1.288 | 1.384 |
| 44 | 0.677 | 0.738 | 1.281 | 1.375 |
| 46 | 0.683 | 0.744 | 1.275 | 1.366 |
| 48 | 0.690 | 0.749 | 1.269 | 1.358 |
| 50 | 0.695 | 0.754 | 1.263 | 1.350 |
| 52 | 0.701 | 0.758 | 1.258 | 1.343 |
| 54 | 0.706 | 0.763 | 1.253 | 1.336 |
| 56 | 0.711 | 0.767 | 1.249 | 1.330 |
| 58 | 0.715 | 0.771 | 1.244 | 1.324 |
| 60 | 0.720 | 0.774 | 1.240 | 1.318 |
| 62 | 0.724 | 0.778 | 1.236 | 1.313 |
| 64 | 0.728 | 0.781 | 1.232 | 1.307 |
| 66 | 0.732 | 0.784 | 1.229 | 1.302 |
| 68 | 0.736 | 0.787 | 1.225 | 1.298 |
| 70 | 0.739 | 0.790 | 1.222 | 1.293 |
| 72 | 0.743 | 0.793 | 1.219 | 1.289 |
| 74 | 0.746 | 0.796 | 1.216 | 1.285 |
| 76 | 0.749 | 0.799 | 1.213 | 1.281 |
| 78 | 0.752 | 0.801 | 1.210 | 1.277 |
| 80 | 0.755 | 0.803 | 1.207 | 1.273 |
| 82 | 0.758 | 0.806 | 1.205 | 1.270 |
| 84 | 0.760 | 0.808 | 1.202 | 1.267 |
| 86 | 0.763 | 0.810 | 1.200 | 1.263 |
| 88 | 0.766 | 0.812 | 1.197 | 1.260 |
| 90 | 0.768 | 0.814 | 1.195 | 1.257 |
| 92 | 0.770 | 0.816 | 1.193 | 1.254 |
| 94 | 0.773 | 0.818 | 1.191 | 1.251 |
| 96 | 0.775 | 0.820 | 1.189 | 1.249 |
| 98 | 0.777 | 0.822 | 1.187 | 1.246 |
| 100 | 0.779 | 0.824 | 1.185 | 1.243 |
| 102 | 0.781 | 0.825 | 1.183 | 1.241 |
| 104 | 0.783 | 0.827 | 1.181 | 1.238 |
| 106 | 0.785 | 0.828 | 1.180 | 1.236 |
| 108 | 0.787 | 0.830 | 1.178 | 1.234 |
| 110 | 0.789 | 0.832 | 1.176 | 1.232 |
| 112 | 0.791 | 0.833 | 1.175 | 1.229 |
| 114 | 0.793 | 0.834 | 1.173 | 1.227 |
| 116 | 0.794 | 0.836 | 1.172 | 1.225 |
| 118 | 0.796 | 0.837 | 1.170 | 1.223 |
| 120 | 0.798 | 0.839 | 1.169 | 1.221 |
| 122 | 0.799 | 0.840 | 1.167 | 1.220 |
| 124 | 0.801 | 0.841 | 1.166 | 1.218 |
| 126 | 0.802 | 0.842 | 1.164 | 1.216 |
| 128 | 0.804 | 0.844 | 1.163 | 1.214 |
| 130 | 0.805 | 0.845 | 1.162 | 1.212 |
| 132 | 0.807 | 0.846 | 1.161 | 1.211 |
| 134 | 0.808 | 0.847 | 1.159 | 1.209 |
| 136 | 0.809 | 0.848 | 1.158 | 1.207 |
| 138 | 0.811 | 0.849 | 1.157 | 1.206 |
| 140 | 0.812 | 0.850 | 1.156 | 1.204 |
| 142 | 0.813 | 0.851 | 1.155 | 1.203 |
| 144 | 0.814 | 0.852 | 1.154 | 1.201 |
| 146 | 0.816 | 0.853 | 1.153 | 1.200 |
| 148 | 0.817 | 0.854 | 1.152 | 1.199 |
| 150 | 0.818 | 0.855 | 1.151 | 1.197 |
| 152 | 0.819 | 0.856 | 1.150 | 1.196 |
| 154 | 0.820 | 0.857 | 1.149 | 1.195 |
| 156 | 0.821 | 0.858 | 1.148 | 1.193 |
| 158 | 0.822 | 0.859 | 1.147 | 1.192 |
| 160 | 0.823 | 0.860 | 1.146 | 1.191 |
| 162 | 0.825 | 0.861 | 1.145 | 1.190 |
| 164 | 0.826 | 0.861 | 1.144 | 1.188 |
| 166 | 0.827 | 0.862 | 1.143 | 1.187 |
| 168 | 0.828 | 0.863 | 1.142 | 1.186 |
| 170 | 0.829 | 0.864 | 1.141 | 1.185 |
| 172 | 0.829 | 0.865 | 1.140 | 1.184 |
| 174 | 0.830 | 0.865 | 1.140 | 1.183 |
| 176 | 0.831 | 0.866 | 1.139 | 1.182 |
| 178 | 0.832 | 0.867 | 1.138 | 1.181 |
| 180 | 0.833 | 0.868 | 1.137 | 1.179 |
| 182 | 0.834 | 0.868 | 1.136 | 1.178 |
| 184 | 0.835 | 0.869 | 1.136 | 1.177 |
| 186 | 0.836 | 0.870 | 1.135 | 1.176 |
| 188 | 0.837 | 0.870 | 1.134 | 1.175 |
| 190 | 0.837 | 0.871 | 1.134 | 1.175 |
| 192 | 0.838 | 0.872 | 1.133 | 1.174 |
| 194 | 0.839 | 0.872 | 1.132 | 1.173 |
| 196 | 0.840 | 0.873 | 1.131 | 1.172 |
| 198 | 0.841 | 0.874 | 1.131 | 1.171 |
| 200 | 0.841 | 0.874 | 1.130 | 1.170 |
| 250 | 0.858 | 0.887 | 1.116 | 1.152 |
| 300 | 0.870 | 0.897 | 1.106 | 1.138 |
| 350 | 0.879 | 0.904 | 1.098 | 1.128 |
| 400 | 0.887 | 0.911 | 1.092 | 1.119 |
| 450 | 0.893 | 0.916 | 1.086 | 1.112 |
| 500 | 0.898 | 0.920 | 1.082 | 1.106 |
| 550 | 0.903 | 0.924 | 1.078 | 1.101 |
| 600 | 0.907 | 0.927 | 1.075 | 1.097 |
| 650 | 0.911 | 0.930 | 1.072 | 1.093 |
| 700 | 0.914 | 0.932 | 1.069 | 1.090 |
| 750 | 0.917 | 0.934 | 1.067 | 1.086 |
| 800 | 0.919 | 0.936 | 1.065 | 1.084 |
| 850 | 0.922 | 0.938 | 1.063 | 1.081 |
| 900 | 0.924 | 0.940 | 1.061 | 1.079 |
| 950 | 0.926 | 0.942 | 1.059 | 1.077 |
| 1000 | 0.928 | 0.943 | 1.058 | 1.075 |
| 1050 | 0.929 | 0.944 | 1.056 | 1.073 |
| 1100 | 0.931 | 0.946 | 1.055 | 1.071 |
| 1150 | 0.932 | 0.947 | 1.054 | 1.070 |
| 1200 | 0.934 | 0.948 | 1.053 | 1.068 |
| 1250 | 0.935 | 0.949 | 1.052 | 1.067 |
| 1300 | 0.936 | 0.950 | 1.051 | 1.065 |
| 1350 | 0.938 | 0.951 | 1.050 | 1.064 |
| 1400 | 0.939 | 0.952 | 1.049 | 1.063 |
| 1450 | 0.940 | 0.953 | 1.048 | 1.062 |
| 1500 | 0.941 | 0.953 | 1.047 | 1.061 |
| 1550 | 0.942 | 0.954 | 1.046 | 1.060 |
| 1600 | 0.943 | 0.955 | 1.046 | 1.059 |
| 1650 | 0.943 | 0.956 | 1.045 | 1.058 |
| 1700 | 0.944 | 0.956 | 1.044 | 1.057 |
| 1750 | 0.945 | 0.957 | 1.044 | 1.056 |
| 1800 | 0.946 | 0.958 | 1.043 | 1.055 |
| 1850 | 0.947 | 0.958 | 1.042 | 1.055 |
| 1900 | 0.947 | 0.959 | 1.042 | 1.054 |
| 1950 | 0.948 | 0.959 | 1.041 | 1.053 |
| 2000 | 0.949 | 0.960 | 1.041 | 1.053 |
| 3000 | 0.958 | 0.967 | 1.033 | 1.043 |
| 3500 | 0.961 | 0.969 | 1.031 | 1.040 |
| 4000 | 0.964 | 0.971 | 1.029 | 1.037 |
| 4500 | 0.966 | 0.973 | 1.027 | 1.035 |
| 5000 | 0.967 | 0.974 | 1.026 | 1.033 |
| 5500 | 0.969 | 0.976 | 1.025 | 1.032 |
| 6000 | 0.970 | 0.977 | 1.023 | 1.030 |
| 6500 | 0.971 | 0.978 | 1.023 | 1.029 |
| 7000 | 0.972 | 0.978 | 1.022 | 1.028 |
| 7500 | 0.973 | 0.979 | 1.021 | 1.027 |
| 8000 | 0.974 | 0.980 | 1.020 | 1.026 |
| 8500 | 0.975 | 0.980 | 1.020 | 1.025 |
| 9000 | 0.976 | 0.981 | 1.019 | 1.025 |
| 9500 | 0.976 | 0.981 | 1.019 | 1.024 |
| 10000 | 0.977 | 0.982 | 1.018 | 1.023 |
より大きな自由度が必要な場合は、コード内の自由度の刻みを適宜調整してください。
目安表作成コードはこちら
import numpy as np
import pandas as pd
from scipy.stats import chi2
# -----------------------------
# 自由度の定義
# -----------------------------
nu_fine = np.arange(2, 201, 2) # 2〜200 : 2刻み
nu_mid = np.arange(250, 2001, 50) # 250〜2000 : 50刻み
nu_coarse = np.arange(3000, 10001, 500) # 3000〜10000 : 500刻み
nus = np.concatenate([nu_fine, nu_mid, nu_coarse])
# -----------------------------
# 有意水準
# -----------------------------
levels = [0.05, 0.10, 0.90, 0.95]
# -----------------------------
# テーブル作成(reduced χ² のみ)
# -----------------------------
data = {"nu": nus}
for cl in levels:
data[f"chi2red_{int(cl*100)}%"] = chi2.ppf(cl, nus) / nus
df = pd.DataFrame(data)
df["nu"] = df["nu"].astype(int)
# -----------------------------
# Markdown 出力
# -----------------------------
floatfmt = [".0f"] + [".3f"] * (len(df.columns) - 1)
print(df.to_markdown(index=False, floatfmt=floatfmt))
おわりに
χ² や reduced χ²(縮約カイ二乗)は、「良さそう」「悪さそう」 を直感的に判断するための便利な指標として使われがちです。
しかしその裏には、
- 分布の形
- 自由度による揺れ
- 有意水準の意味
といった、明確な確率的背景があります。
この記事で見たように、一度 分布として眺めておく ことで、
なぜ「$\chi^2 \approx \nu$」や「reduced χ² $\approx 1$」といった目安が使われているのか
について、少しイメージしやすくなるかもしれません。
本記事が、その理解のきっかけになれば幸いです。
関連記事



