効果量(Cohen’s d・相関比・オッズ比)をわかりやすく解説

統計的仮説検定では、p値によって「差があるかどうか」を判定します。しかし、p値には重大な限界があります。標本サイズが大きければ、実質的にはほとんど意味のない微小な差でも統計的に有意になってしまうのです。「差があるか」だけでなく「差はどれくらい大きいか」を定量化するために必要なのが 効果量(effect size) です。

効果量は、標本サイズに依存しない指標として、検定結果の実質的な意味を評価するために用いられます。APA(米国心理学会)の論文執筆ガイドラインでも、p値とともに効果量を報告することが強く推奨されています。

本記事の内容

  • なぜ p値だけでは不十分なのか
  • 効果量の分類(差の効果量・関連の効果量)
  • Cohen’s d, Glass’ Δ, Hedges’ g の定義と導出
  • η²(イータ二乗)と ω²(オメガ二乗)の定義と導出
  • オッズ比とリスク比
  • 効果量の解釈基準(小・中・大)
  • Pythonでの計算と可視化

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

なぜp値だけでは不十分なのか

p値の復習

p値は「帰無仮説が正しいと仮定したとき、実際に観測されたデータと同程度以上に極端なデータが得られる確率」です。p値が小さいほど、帰無仮説に対する証拠が強いことを意味します。

p値の限界

p値には以下の問題があります。

問題1: 標本サイズへの依存

検定統計量は一般に

$$ \text{検定統計量} = \frac{\text{効果の大きさ}}{\text{標準誤差}} = \frac{\text{効果の大きさ}}{\text{ばらつき} / \sqrt{n}} $$

という構造をしています。分母は $\sqrt{n}$ に反比例するため、標本サイズ $n$ を大きくすれば、どんなに小さな効果でも検定統計量を大きくでき、p値を小さくできます。

例えば、2群の平均差が $\bar{x}_1 – \bar{x}_2 = 0.01$(実質的に無視できる差)でも、$n$ を十分大きくすれば $p < 0.05$ にできてしまいます。

問題2: 効果の方向と大きさの情報がない

p値は「差がないという仮説との矛盾の程度」を示すだけであり、差の大きさや実質的な重要性については何も教えてくれません。

問題3: 有意/非有意の二分法

$\alpha = 0.05$ の閾値を使って「有意」「非有意」に二分することは、$p = 0.049$ と $p = 0.051$ の結果に本質的な違いがないにもかかわらず、異なる結論を導いてしまいます。

効果量の必要性

効果量は 標本サイズに依存しない、効果の大きさの標準化された指標です。p値が「差があるか」を判断するのに対し、効果量は「差はどれくらいか」を定量化します。

効果量の分類

効果量は大きく2つのファミリーに分けられます。

$d$ ファミリー(差の効果量)

2群の差の大きさを標準化して表す指標群です。

指標 定義 用途
Cohen’s $d$ プールされた標準偏差で標準化 独立2群の比較
Glass’ $\Delta$ 統制群の標準偏差で標準化 2群の分散が異なる場合
Hedges’ $g$ Cohen’s $d$ の小標本バイアスを補正 小標本の場合

$r$ ファミリー(関連の効果量)

関連の強さや分散の説明率を表す指標群です。

指標 定義 用途
$\eta^2$(イータ二乗) $SS_{\text{between}} / SS_{\text{total}}$ 分散分析
$\omega^2$(オメガ二乗) $\eta^2$ のバイアス補正版 分散分析
$R^2$(決定係数) 回帰による分散説明率 回帰分析
オッズ比 2群のオッズの比 カテゴリデータ

Cohen’s d

定義

2つの独立な群の平均差を、プールされた標準偏差で割って標準化した指標です。

$$ d = \frac{\bar{X}_1 – \bar{X}_2}{s_p} $$

ここで $s_p$ は プールされた標準偏差(pooled standard deviation) です。

プールされた標準偏差の導出

2群のデータがそれぞれ $n_1$ 個、$n_2$ 個で、不偏分散が $s_1^2$, $s_2^2$ のとき、プールされた分散は各群の偏差平方和の重みつき平均として定義されます。

$$ s_p^2 = \frac{(n_1 – 1)s_1^2 + (n_2 – 1)s_2^2}{n_1 + n_2 – 2} $$

この式の導出を確認しましょう。各群の偏差平方和は

$$ SS_1 = (n_1 – 1)s_1^2 = \sum_{i=1}^{n_1}(X_{1i} – \bar{X}_1)^2, \quad SS_2 = (n_2 – 1)s_2^2 = \sum_{j=1}^{n_2}(X_{2j} – \bar{X}_2)^2 $$

2群の母分散が等しい($\sigma_1^2 = \sigma_2^2 = \sigma^2$)と仮定すると、$\sigma^2$ の最良の推定量は全偏差平方和を全自由度で割ったものです。

$$ s_p^2 = \frac{SS_1 + SS_2}{(n_1 – 1) + (n_2 – 1)} = \frac{(n_1 – 1)s_1^2 + (n_2 – 1)s_2^2}{n_1 + n_2 – 2} $$

したがって、プールされた標準偏差は

$$ s_p = \sqrt{\frac{(n_1 – 1)s_1^2 + (n_2 – 1)s_2^2}{n_1 + n_2 – 2}} $$

Cohen’s d と t統計量の関係

独立2標本t検定の検定統計量は

$$ t = \frac{\bar{X}_1 – \bar{X}_2}{s_p\sqrt{\frac{1}{n_1} + \frac{1}{n_2}}} $$

Cohen’s $d = \frac{\bar{X}_1 – \bar{X}_2}{s_p}$ なので、

$$ t = \frac{d}{\sqrt{\frac{1}{n_1} + \frac{1}{n_2}}} $$

逆に、t統計量からCohen’s dを求めるには、

$$ d = t \cdot \sqrt{\frac{1}{n_1} + \frac{1}{n_2}} $$

等サイズの場合($n_1 = n_2 = n$)は特に簡潔です。

$$ d = t \cdot \sqrt{\frac{2}{n}} $$

この関係式から、t統計量が標本サイズとともに大きくなるのに対し、Cohen’s d は標本サイズに依存しないことが明確にわかります。

Cohen’s d の解釈基準

Cohen(1988)が提案した目安は以下の通りです。

$|d|$ 効果の大きさ 直感的な意味
0.2 小(small) 注意深く見ないと気づかない差
0.5 中(medium) 肉眼で見てわかる差
0.8 大(large) 明らかに大きい差

$d = 0.5$ は、2つの群の分布の重なりが約67%であることに対応します。

Glass’ Δ

定義と使い分け

Glass’ $\Delta$ は、統制群(コントロール群)の標準偏差のみで標準化します。

$$ \Delta = \frac{\bar{X}_1 – \bar{X}_2}{s_2} $$

ここで $s_2$ は統制群の標準偏差です。

なぜ必要か

Cohen’s d はプールされた標準偏差を使うため、2群の分散が等しいことを前提としています。しかし、処理の効果がばらつきも変化させる場合(例: 教育介入により成績の分散が増加する)、プールされた標準偏差は処理の影響を含んでしまいます。

Glass’ $\Delta$ は統制群の標準偏差のみを使うため、処理がばらつきに影響しても、基準となるスケールが一定 に保たれます。

Hedges’ g

小標本でのバイアス

Cohen’s d は、母集団の効果量 $\delta = (\mu_1 – \mu_2)/\sigma$ の不偏推定量ではありません。特に小標本では正の方向にバイアスを持ちます。

$E[d] = \delta$ ではなく、

$$ E[d] \approx \delta \cdot \left(1 + \frac{3}{4(n_1 + n_2) – 9}\right) $$

であることが知られています(近似式)。

Hedges’ g の定義

このバイアスを補正した効果量が Hedges’ $g$ です。

$$ g = d \cdot J(df) $$

ここで $df = n_1 + n_2 – 2$ として、補正係数 $J(df)$ は

$$ J(df) = \frac{\Gamma(df/2)}{\sqrt{df/2} \cdot \Gamma((df-1)/2)} $$

実用上は近似式

$$ J(df) \approx 1 – \frac{3}{4 \cdot df – 1} $$

が広く使われています。

導出

$s_p^2$ は $\sigma^2$ の不偏推定量ですが、$s_p$ は $\sigma$ の不偏推定量ではありません。$s_p = \sigma \cdot \sqrt{\chi^2_{df}/df}$ の逆数の期待値を計算すると、

$$ E\left[\frac{1}{s_p}\right] = \frac{1}{\sigma} \cdot \frac{1}{J(df)} $$

したがって $E[d] = E\left[\frac{\bar{X}_1 – \bar{X}_2}{s_p}\right] = \frac{\delta}{J(df)} \cdot J(df)^{-1} \cdot J(df)$… より正確には、$d$ の期待値を $\delta$ に一致させるために、$d$ に $J(df)$ を乗じて

$$ g = d \cdot J(df) \approx d \cdot \left(1 – \frac{3}{4(n_1 + n_2 – 2) – 1}\right) $$

とすると、$E[g] \approx \delta$ が近似的に成り立ちます。

η²(イータ二乗)

定義

分散分析(ANOVA)における効果量で、独立変数によって説明される従属変数の分散の割合を表します。

$$ \eta^2 = \frac{SS_{\text{between}}}{SS_{\text{total}}} $$

ここで、

  • $SS_{\text{between}} = \sum_{j=1}^{k} n_j(\bar{X}_j – \bar{X})^2$ は群間平方和
  • $SS_{\text{total}} = \sum_{j=1}^{k}\sum_{i=1}^{n_j}(X_{ij} – \bar{X})^2$ は全平方和
  • $k$ は群の数、$n_j$ は第 $j$ 群の標本サイズ、$\bar{X}$ は全体平均

η² と F統計量の関係

分散分析のF統計量は

$$ F = \frac{SS_{\text{between}} / (k-1)}{SS_{\text{within}} / (N-k)} = \frac{MS_{\text{between}}}{MS_{\text{within}}} $$

$SS_{\text{total}} = SS_{\text{between}} + SS_{\text{within}}$ なので、$SS_{\text{within}} = SS_{\text{total}} – SS_{\text{between}}$ です。

$$ \begin{align} \eta^2 &= \frac{SS_{\text{between}}}{SS_{\text{total}}} \\ &= \frac{SS_{\text{between}}}{SS_{\text{between}} + SS_{\text{within}}} \end{align} $$

F統計量から変換すると、

$$ F = \frac{SS_{\text{between}} / (k-1)}{SS_{\text{within}} / (N-k)} $$

$$ \frac{SS_{\text{between}}}{SS_{\text{within}}} = \frac{F(k-1)}{N-k} $$

したがって、

$$ \begin{align} \eta^2 &= \frac{SS_{\text{between}}}{SS_{\text{between}} + SS_{\text{within}}} \\ &= \frac{1}{1 + SS_{\text{within}}/SS_{\text{between}}} \\ &= \frac{1}{1 + (N-k)/[F(k-1)]} \\ &= \frac{F(k-1)}{F(k-1) + (N-k)} \end{align} $$

η² のバイアス

$\eta^2$ は母集団の効果量を過大推定する傾向があります。これは、$SS_{\text{between}}$ には群間の真の差だけでなく、偶然変動も含まれるためです。

$E[\eta^2] > \eta^2_{\text{population}}$

ω²(オメガ二乗)

バイアスの補正

$\eta^2$ のバイアスを補正した指標が $\omega^2$ です。

$$ \omega^2 = \frac{SS_{\text{between}} – (k-1) \cdot MS_{\text{within}}}{SS_{\text{total}} + MS_{\text{within}}} $$

導出

母集団パラメータとして、

$$ \omega^2_{\text{pop}} = \frac{\sigma^2_{\text{between}}}{\sigma^2_{\text{between}} + \sigma^2_{\text{within}}} $$

ここで $\sigma^2_{\text{between}} = \frac{1}{k}\sum_{j=1}^{k}(\mu_j – \mu)^2$ は群間分散、$\sigma^2_{\text{within}} = \sigma^2$ は群内分散です。

分散分析の平均平方の期待値は以下のようになります。

$$ E[MS_{\text{between}}] = \sigma^2 + \frac{n \sum(\mu_j – \mu)^2}{k – 1} = \sigma^2 + n\sigma^2_{\alpha} $$

$$ E[MS_{\text{within}}] = \sigma^2 $$

ここで $\sigma^2_{\alpha} = \frac{\sum(\mu_j – \mu)^2}{k-1}$ です。$MS_{\text{between}}$ から $MS_{\text{within}}$ を引くと、

$$ E[MS_{\text{between}} – MS_{\text{within}}] = n\sigma^2_{\alpha} $$

$$ E[MS_{\text{between}}] = \sigma^2 + n\sigma^2_{\alpha} $$

$SS_{\text{between}} = (k-1) \cdot MS_{\text{between}}$ の期待値は

$$ E[SS_{\text{between}}] = (k-1)(\sigma^2 + n\sigma^2_{\alpha}) $$

$SS_{\text{between}}$ から群内分散の影響を除去するために $(k-1)\sigma^2$ を引きます。

$$ SS_{\text{between}} – (k-1) \cdot MS_{\text{within}} $$

の期待値は

$$ (k-1)(\sigma^2 + n\sigma^2_{\alpha}) – (k-1)\sigma^2 = (k-1)n\sigma^2_{\alpha} $$

同様に、$SS_{\text{total}} + MS_{\text{within}}$ の期待値は $(N-1)\sigma^2 + N\sigma^2_{\alpha} \cdot \frac{k-1}{1} + \sigma^2$… ここは標本の推定量として、

$$ \hat{\omega}^2 = \frac{SS_{\text{between}} – (k-1) \cdot MS_{\text{within}}}{SS_{\text{total}} + MS_{\text{within}}} $$

が母集団の $\omega^2$ のほぼ不偏な推定量になります。

解釈基準

効果量
$\eta^2$ 0.01 0.06 0.14
$\omega^2$ 0.01 0.06 0.14

オッズ比(Odds Ratio)

オッズの定義

ある事象が起こる確率を $p$ とすると、オッズ(odds)

$$ \text{Odds} = \frac{p}{1 – p} $$

例えば、成功確率が $p = 0.75$ のとき、オッズ $= 0.75/0.25 = 3$ です。「失敗1回に対して成功3回」の割合を意味します。

オッズ比の定義

2つの群のオッズの比が オッズ比(Odds Ratio, OR) です。

2×2分割表を考えます。

事象あり 事象なし 合計
群1(曝露群) $a$ $b$ $a + b$
群2(非曝露群) $c$ $d$ $c + d$

群1のオッズ: $\frac{a/(a+b)}{b/(a+b)} = \frac{a}{b}$

群2のオッズ: $\frac{c/(c+d)}{d/(c+d)} = \frac{c}{d}$

$$ \text{OR} = \frac{a/b}{c/d} = \frac{ad}{bc} $$

オッズ比の性質

  • $\text{OR} = 1$: 2群でオッズが等しい(差がない)
  • $\text{OR} > 1$: 群1でオッズが高い
  • $\text{OR} < 1$: 群2でオッズが高い

対数オッズ比

オッズ比は対称性の観点から対数変換して使うことが多いです。

$$ \log\text{OR} = \log(ad) – \log(bc) $$

$\log\text{OR}$ の漸近分散は

$$ \text{Var}(\log\text{OR}) \approx \frac{1}{a} + \frac{1}{b} + \frac{1}{c} + \frac{1}{d} $$

この式は、$\log\text{OR}$ がセル度数の対数の線形結合 $\log a – \log b – \log c + \log d$ であり、各セルがポアソン分布に近似的に従う($\text{Var}(\log X) \approx 1/E[X]$)ことから導かれます。

オッズ比の信頼区間

$\log\text{OR}$ の $100(1-\alpha)\%$ 信頼区間は

$$ \log\text{OR} \pm z_{\alpha/2}\sqrt{\frac{1}{a} + \frac{1}{b} + \frac{1}{c} + \frac{1}{d}} $$

オッズ比の信頼区間は、これを指数変換して得られます。

$$ \exp\left(\log\text{OR} \pm z_{\alpha/2}\sqrt{\frac{1}{a} + \frac{1}{b} + \frac{1}{c} + \frac{1}{d}}\right) $$

リスク比(Relative Risk)

定義

リスク比はオッズ比と似ていますが、オッズではなく確率(リスク)の比を取ります。

$$ \text{RR} = \frac{a/(a+b)}{c/(c+d)} $$

オッズ比とリスク比の関係

事象が稀($a \ll b$ かつ $c \ll d$)のとき、$a + b \approx b$, $c + d \approx d$ なので

$$ \text{RR} = \frac{a/b \cdot (c+d)/(a+b) \cdot d/d}{c/d} \approx \frac{a/b}{c/d} = \text{OR} $$

すなわち、稀な事象ではオッズ比はリスク比の良い近似 になります。これがケースコントロール研究でオッズ比が広く使われる理由のひとつです。

具体例

数値例: Cohen’s d

教授法AとBで試験成績を比較します。

  • 教授法A: $n_1 = 30$, $\bar{x}_1 = 78.5$, $s_1 = 10.2$
  • 教授法B: $n_2 = 35$, $\bar{x}_2 = 72.3$, $s_2 = 11.5$

プールされた標準偏差:

$$ s_p = \sqrt{\frac{29 \times 10.2^2 + 34 \times 11.5^2}{30 + 35 – 2}} = \sqrt{\frac{29 \times 104.04 + 34 \times 132.25}{63}} = \sqrt{\frac{3017.16 + 4496.50}{63}} = \sqrt{\frac{7513.66}{63}} = \sqrt{119.26} = 10.92 $$

Cohen’s d:

$$ d = \frac{78.5 – 72.3}{10.92} = \frac{6.2}{10.92} = 0.568 $$

$|d| = 0.568$ は中程度の効果量に相当します。

Hedges’ g:

$$ g = d \cdot \left(1 – \frac{3}{4 \times 63 – 1}\right) = 0.568 \times \left(1 – \frac{3}{251}\right) = 0.568 \times 0.988 = 0.561 $$

Pythonでの実装

効果量の計算

import numpy as np
from scipy import stats
from scipy.special import gamma

def cohens_d(group1, group2):
    """Cohen's d を計算する"""
    n1, n2 = len(group1), len(group2)
    s1, s2 = np.std(group1, ddof=1), np.std(group2, ddof=1)

    # プールされた標準偏差
    sp = np.sqrt(((n1 - 1) * s1**2 + (n2 - 1) * s2**2) / (n1 + n2 - 2))

    d = (np.mean(group1) - np.mean(group2)) / sp
    return d

def glass_delta(group1, group2):
    """Glass' Δ を計算する(group2 を統制群とする)"""
    return (np.mean(group1) - np.mean(group2)) / np.std(group2, ddof=1)

def hedges_g(group1, group2):
    """Hedges' g を計算する(小標本バイアス補正)"""
    d = cohens_d(group1, group2)
    n1, n2 = len(group1), len(group2)
    df = n1 + n2 - 2

    # 補正係数の近似式
    j = 1 - 3 / (4 * df - 1)

    return d * j

def eta_squared(groups):
    """η²(イータ二乗)を計算する"""
    all_data = np.concatenate(groups)
    grand_mean = np.mean(all_data)

    ss_between = sum(len(g) * (np.mean(g) - grand_mean)**2 for g in groups)
    ss_total = np.sum((all_data - grand_mean)**2)

    return ss_between / ss_total

def omega_squared(groups):
    """ω²(オメガ二乗)を計算する"""
    all_data = np.concatenate(groups)
    grand_mean = np.mean(all_data)
    k = len(groups)
    N = len(all_data)

    ss_between = sum(len(g) * (np.mean(g) - grand_mean)**2 for g in groups)
    ss_within = sum(np.sum((g - np.mean(g))**2) for g in groups)
    ss_total = np.sum((all_data - grand_mean)**2)

    ms_within = ss_within / (N - k)

    return (ss_between - (k - 1) * ms_within) / (ss_total + ms_within)

def odds_ratio(a, b, c, d):
    """オッズ比を計算する"""
    OR = (a * d) / (b * c)
    log_OR = np.log(OR)
    se_log_OR = np.sqrt(1/a + 1/b + 1/c + 1/d)

    # 95%信頼区間
    ci_lower = np.exp(log_OR - 1.96 * se_log_OR)
    ci_upper = np.exp(log_OR + 1.96 * se_log_OR)

    return OR, ci_lower, ci_upper


# --- 具体例 ---
np.random.seed(42)

# 2群のデータ生成
group_a = np.random.normal(78.5, 10.2, 30)
group_b = np.random.normal(72.3, 11.5, 35)

print("=== 差の効果量 ===")
print(f"Cohen's d  = {cohens_d(group_a, group_b):.4f}")
print(f"Glass' Δ   = {glass_delta(group_a, group_b):.4f}")
print(f"Hedges' g  = {hedges_g(group_a, group_b):.4f}")

# t検定
t_stat, p_value = stats.ttest_ind(group_a, group_b)
print(f"\nt検定: t = {t_stat:.4f}, p = {p_value:.4f}")

# t統計量からCohen's dを逆算
d_from_t = t_stat * np.sqrt(1/len(group_a) + 1/len(group_b))
print(f"t統計量から算出したd = {d_from_t:.4f}")

# 分散分析の効果量
print("\n=== 関連の効果量(ANOVA) ===")
group_c = np.random.normal(75.0, 10.0, 28)
groups = [group_a, group_b, group_c]

print(f"η² = {eta_squared(groups):.4f}")
print(f"ω² = {omega_squared(groups):.4f}")

# ANOVA
f_stat, p_anova = stats.f_oneway(*groups)
print(f"\nANOVA: F = {f_stat:.4f}, p = {p_anova:.4f}")

# オッズ比
print("\n=== オッズ比 ===")
a, b, c, d_val = 40, 60, 20, 80  # 2×2分割表
OR, ci_low, ci_high = odds_ratio(a, b, c, d_val)
print(f"2×2分割表: a={a}, b={b}, c={c}, d={d_val}")
print(f"オッズ比 = {OR:.4f}")
print(f"95%信頼区間 = [{ci_low:.4f}, {ci_high:.4f}]")

効果量の可視化

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(42)

# 異なる効果量でのCohens' dの可視化
effect_sizes = [0.2, 0.5, 0.8, 1.2]
labels = ['Small (d=0.2)', 'Medium (d=0.5)', 'Large (d=0.8)', 'Very Large (d=1.2)']
colors = ['#2196F3', '#FF9800', '#F44336', '#9C27B0']

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes = axes.flatten()

x = np.linspace(-4, 6, 500)

for idx, (d, label, color) in enumerate(zip(effect_sizes, labels, colors)):
    # 群1: N(0, 1)、群2: N(d, 1)
    pdf1 = stats.norm.pdf(x, 0, 1)
    pdf2 = stats.norm.pdf(x, d, 1)

    axes[idx].fill_between(x, pdf1, alpha=0.3, color='blue', label='Group 1 ($\\mu=0$)')
    axes[idx].fill_between(x, pdf2, alpha=0.3, color=color, label=f'Group 2 ($\\mu={d}$)')
    axes[idx].plot(x, pdf1, 'b-', linewidth=2)
    axes[idx].plot(x, pdf2, '-', color=color, linewidth=2)

    # 重なりの割合(近似計算)
    overlap = np.trapz(np.minimum(pdf1, pdf2), x) * 100

    axes[idx].set_title(f"{label}\nOverlap: {overlap:.1f}%", fontsize=12)
    axes[idx].set_xlabel('Value', fontsize=11)
    axes[idx].set_ylabel('Density', fontsize=11)
    axes[idx].legend(fontsize=9)
    axes[idx].grid(True, alpha=0.3)

plt.suptitle("Cohen's d: Distribution Overlap for Different Effect Sizes",
             fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

p値と効果量の関係

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(42)

# 固定された効果量で標本サイズを変えたときのp値
d_true = 0.3  # 小さな効果量
sample_sizes = np.arange(10, 510, 10)
n_simulations = 2000

mean_p_values = []
power_values = []
alpha = 0.05

for n in sample_sizes:
    p_vals = []
    rejects = 0

    for _ in range(n_simulations):
        group1 = np.random.normal(0, 1, n)
        group2 = np.random.normal(d_true, 1, n)
        _, p = stats.ttest_ind(group1, group2)
        p_vals.append(p)
        if p < alpha:
            rejects += 1

    mean_p_values.append(np.median(p_vals))
    power_values.append(rejects / n_simulations)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# p値の変化
ax1.plot(sample_sizes, mean_p_values, 'b-o', markersize=3, linewidth=1.5)
ax1.axhline(y=0.05, color='red', linestyle='--', linewidth=1.5, label='$\\alpha = 0.05$')
ax1.set_xlabel('Sample Size per Group', fontsize=12)
ax1.set_ylabel('Median p-value', fontsize=12)
ax1.set_title(f"Median p-value vs Sample Size (True $d$ = {d_true})", fontsize=13)
ax1.set_yscale('log')
ax1.legend(fontsize=11)
ax1.grid(True, alpha=0.3)

# 検出力の変化
ax2.plot(sample_sizes, power_values, 'r-s', markersize=3, linewidth=1.5)
ax2.axhline(y=0.8, color='gray', linestyle='--', linewidth=1.5, label='Power = 0.80')
ax2.set_xlabel('Sample Size per Group', fontsize=12)
ax2.set_ylabel('Power', fontsize=12)
ax2.set_title(f"Statistical Power vs Sample Size (True $d$ = {d_true})", fontsize=13)
ax2.legend(fontsize=11)
ax2.grid(True, alpha=0.3)
ax2.set_ylim(0, 1.05)

plt.tight_layout()
plt.show()

print(f"Cohen's d = {d_true} を80%の検出力で検出するには:")
# 理論値: n = (z_α/2 + z_β)² × 2 / d²
z_alpha = stats.norm.ppf(1 - 0.05/2)
z_beta = stats.norm.ppf(0.8)
n_required = 2 * ((z_alpha + z_beta) / d_true)**2
print(f"  理論上必要な標本サイズ(各群): n ≈ {n_required:.0f}")

η² と ω² の比較シミュレーション

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(42)

n_simulations = 5000
group_sizes = [10, 20, 50, 100]

# 真の効果量(母集団のη²)
# 3群: μ1=0, μ2=0, μ3=0(帰無仮説が真、η²_pop = 0)
sigma = 1.0

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes = axes.flatten()

for idx, n_per_group in enumerate(group_sizes):
    eta2_vals = []
    omega2_vals = []

    for _ in range(n_simulations):
        # H0のもとでデータ生成(3群、すべて同じ分布)
        g1 = np.random.normal(0, sigma, n_per_group)
        g2 = np.random.normal(0, sigma, n_per_group)
        g3 = np.random.normal(0, sigma, n_per_group)

        groups = [g1, g2, g3]
        all_data = np.concatenate(groups)
        grand_mean = np.mean(all_data)
        k = 3
        N = 3 * n_per_group

        ss_between = sum(len(g) * (np.mean(g) - grand_mean)**2 for g in groups)
        ss_within = sum(np.sum((g - np.mean(g))**2) for g in groups)
        ss_total = np.sum((all_data - grand_mean)**2)
        ms_within = ss_within / (N - k)

        eta2 = ss_between / ss_total
        omega2 = (ss_between - (k - 1) * ms_within) / (ss_total + ms_within)

        eta2_vals.append(eta2)
        omega2_vals.append(omega2)

    eta2_vals = np.array(eta2_vals)
    omega2_vals = np.array(omega2_vals)

    axes[idx].hist(eta2_vals, bins=50, density=True, alpha=0.6, color='blue',
                   label=f'$\\eta^2$ (mean={np.mean(eta2_vals):.4f})')
    axes[idx].hist(omega2_vals, bins=50, density=True, alpha=0.6, color='red',
                   label=f'$\\omega^2$ (mean={np.mean(omega2_vals):.4f})')
    axes[idx].axvline(x=0, color='gray', linestyle='--', linewidth=1.5,
                      label='True effect = 0')

    axes[idx].set_title(f'n per group = {n_per_group}', fontsize=13)
    axes[idx].set_xlabel('Effect Size', fontsize=11)
    axes[idx].set_ylabel('Density', fontsize=11)
    axes[idx].legend(fontsize=9)
    axes[idx].grid(True, alpha=0.3)

plt.suptitle('$\\eta^2$ vs $\\omega^2$ under $H_0$ (True Effect = 0)',
             fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

オッズ比の可視化

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# さまざまなオッズ比の信頼区間を可視化
studies = [
    {'name': 'Study A', 'a': 40, 'b': 60, 'c': 20, 'd': 80},
    {'name': 'Study B', 'a': 15, 'b': 85, 'c': 10, 'd': 90},
    {'name': 'Study C', 'a': 55, 'b': 45, 'c': 25, 'd': 75},
    {'name': 'Study D', 'a': 30, 'b': 70, 'c': 28, 'd': 72},
    {'name': 'Study E', 'a': 8, 'b': 92, 'c': 3, 'd': 97},
]

fig, ax = plt.subplots(figsize=(10, 6))

for i, study in enumerate(studies):
    a, b, c, d = study['a'], study['b'], study['c'], study['d']
    OR = (a * d) / (b * c)
    log_OR = np.log(OR)
    se = np.sqrt(1/a + 1/b + 1/c + 1/d)
    ci_lower = np.exp(log_OR - 1.96 * se)
    ci_upper = np.exp(log_OR + 1.96 * se)

    color = 'red' if ci_lower > 1 or ci_upper < 1 else 'gray'
    ax.errorbar(OR, i, xerr=[[OR - ci_lower], [ci_upper - OR]],
                fmt='o', color=color, markersize=8, capsize=5,
                linewidth=2, elinewidth=2)
    ax.text(ci_upper + 0.3, i, f'OR = {OR:.2f} [{ci_lower:.2f}, {ci_upper:.2f}]',
            va='center', fontsize=10)

ax.axvline(x=1, color='gray', linestyle='--', linewidth=1.5, label='OR = 1 (No effect)')
ax.set_yticks(range(len(studies)))
ax.set_yticklabels([s['name'] for s in studies], fontsize=11)
ax.set_xlabel('Odds Ratio (95% CI)', fontsize=12)
ax.set_title('Forest Plot: Odds Ratios with 95% Confidence Intervals', fontsize=14)
ax.set_xscale('log')
ax.set_xlim(0.3, 15)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3, axis='x')

plt.tight_layout()
plt.show()

まとめ

本記事では、効果量の理論と計算方法について解説しました。

  • p値の限界: 標本サイズを大きくすればどんな小さな差でも有意になるため、p値だけでは効果の実質的な大きさを判断できない
  • Cohen’s d: 2群の平均差をプールされた標準偏差で標準化。$d = (\bar{X}_1 – \bar{X}_2)/s_p$。$t$ 統計量との関係は $d = t\sqrt{1/n_1 + 1/n_2}$
  • Hedges’ g: Cohen’s d の小標本バイアスを補正。$g = d \cdot (1 – 3/(4df – 1))$
  • η² と ω²: 分散分析における分散説明率。$\omega^2$ は $\eta^2$ のバイアスを補正
  • オッズ比: カテゴリデータにおける効果の指標。$\text{OR} = ad/bc$
  • Cohen の基準: $d = 0.2$(小)、$0.5$(中)、$0.8$(大)

次のステップとして、以下の記事も参考にしてください。