3案の平均を比べて「どれを採用するか」を決めたいとき、分散分析(ANOVA)は比較の出発点になります。ただし、全体のp値が小さくても、すべての案が違うとは限りません。どの組にどれだけ差があり、その差が運用上意味を持つかは、比較ごとの推定と区間で確かめます。
本記事は、独立した3群の一元配置ANOVAを中心に、Welchの方法、Tukey・Games–Howell、多重比較、効果量、反復測定や二元配置に進む条件まで扱います。数値とコードはすべて仮想教材であり、実際の施策成果を示していません。
30秒で要点
- 一元配置ANOVAの帰無仮説は、比較するすべての母平均が等しいこと。棄却して分かるのは、少なくとも一つの差があることです。
- 古典的ANOVAは独立性・群内の正規分布・共通分散を仮定します。等分散を置けない独立群にはWelchを検討します。
- 全組の平均差を比較するなら、共通分散の設定ではTukey、不等分散の設定ではGames–Howellなどを選び、調整済みp値と区間を示します。
- η²は「売上が何%増えたか」ではありません。元の単位の平均差、区間、費用と必要な改善幅も併記します。
比較する群と、知りたい量を先に固定する
一元配置の「一元」は、説明する要因が一つという意味です。たとえば手順A・B・Cという要因に三つの水準があり、各人の作業時間を測る場合です。人数が多い、列が多いという意味ではありません。
表が収まらない場合は、横にスクロールできます
| データの取り方 | 検討する分析 |
|---|---|
| 別々の人がA・B・Cのどれか一つを使う | 独立群の一元配置ANOVAやWelch ANOVA |
| 同じ人がA・B・Cをすべて使う | 反復測定。人内相関、実施順序、持ち越しを扱う |
| 手順A・B・Cと、初心者・経験者を同時に比較する | 二元配置や回帰。要因間の交互作用を検討する |
| 3つのLPの購入率を比べる | 購入の有無と各群の分母を持つ比率の分析。集計済みCVRをそのまま連続測定として並べない |
同じ店舗の多数の客や、同じ人の多数の操作を、独立した反復として数えてよいかも確認します。ANOVAは、標本が依存している問題や、そもそも群の背景が違う問題を消してくれません。
分散を比べて、平均の違いを調べる仕組み
三つの母平均をμA、μB、μCとすると、帰無仮説は μA = μB = μC。対立仮説は「すべてが等しいわけではない」です。
古典的な一元配置ANOVAは、観測値と全体平均のずれを、群平均と全体平均のずれ(群間平方和)と、各観測と群平均のずれ(群内平方和)に分けます。群数をk、全人数をNとすると、群間の自由度はk - 1、群内はN - kです。
F = 群間平均平方 / 群内平均平方。平均平方は平方和を自由度で割ったものです。同じ母平均・共通分散などの仮定の下で、この比が大きいほど帰無仮説と整合しにくくなります。NISTのANOVA表と検定
ここで検定しているのは母平均であり、「三群の分散が等しいか」自体を検定しているのではありません。分散の情報を使うことと、検定対象が分散であることを区別します。
各5人の作業時間で、ANOVA表を計算する
別々の15人を三つの手順に5人ずつ割り付け、同じ課題の完了時間を測ったとする仮想例です。数値の単位は分。各人は一度だけ観測され、表の同じ位置に対応関係はありません。
表が収まらない場合は、横にスクロールできます
| 手順 | 作業時間(分) | 平均(分) | 標本標準偏差(分) |
|---|---|---|---|
| A | 3.2、3.5、3.0、3.4、3.1 | 3.24 | 0.207 |
| B | 3.6、3.8、3.7、3.5、3.9 | 3.70 | 0.158 |
| C | 3.1、3.0、2.9、3.2、3.0 | 3.04 | 0.114 |
以下は各ブロック単独で実行可能です。2026年10月4日にPython 3.12、NumPy 2.5.3、SciPy 1.16.2、Matplotlib 3.10.7、statsmodels 0.14.5で確認しました。f_oneway(equal_var=False)と、ここで使うGames–Howellの引数は、実行環境で利用できることを確認してから使ってください。
import numpy as np
from scipy import stats
from statsmodels.stats.oneway import confint_effectsize_oneway
groups = [np.array(x) for x in [
[3.2, 3.5, 3.0, 3.4, 3.1],
[3.6, 3.8, 3.7, 3.5, 3.9],
[3.1, 3.0, 2.9, 3.2, 3.0],
]]
all_values = np.concatenate(groups)
N, k = len(all_values), len(groups)
ss_between = sum(len(x) * (x.mean() - all_values.mean())**2 for x in groups)
ss_within = sum(((x - x.mean())**2).sum() for x in groups)
ss_total = ss_between + ss_within
df_between, df_within = k - 1, N - k
ms_between, ms_within = ss_between / df_between, ss_within / df_within
classic = stats.f_oneway(*groups, equal_var=True)
welch = stats.f_oneway(*groups, equal_var=False)
eta_sq = ss_between / ss_total
omega_sq = (ss_between - df_between * ms_within) / (ss_total + ms_within)
effect_ci = confint_effectsize_oneway(
classic.statistic, (df_between, df_within), alpha=0.05, nobs=N
)
print(f"SS between={ss_between:.6f}, within={ss_within:.6f}, total={ss_total:.6f}")
print(f"classical F({df_between},{df_within})={classic.statistic:.6f}, p={classic.pvalue:.9f}")
print(f"Welch F={welch.statistic:.6f}, p={welch.pvalue:.9f}")
print(f"eta_sq={eta_sq:.6f}, omega_sq={omega_sq:.6f}")
print(f"classical-model eta_sq 95% CI=[{effect_ci.eta2[0]:.6f}, {effect_ci.eta2[1]:.6f}]")
表が収まらない場合は、横にスクロールできます
| 変動 | 平方和(分²) | 自由度 | 平均平方(分²) |
|---|---|---|---|
| 群間 | 1.145333 | 2 | 0.572667 |
| 群内 | 0.324000 | 12 | 0.027000 |
| 全体 | 1.469333 | 14 | — |
古典的ANOVAではF(2,12)≈21.210、p≈0.000115です。ただし「A・B・Cの全組で差がある」「Cを導入すれば必ず速くなる」とは結論しません。どの差をどの範囲まで推定できるかを次に調べます。
不等分散ならWelchを検討し、診断は省略しない
SciPyのf_onewayは、equal_var=Trueを古典的ANOVA、FalseをWelch ANOVAとしています。今回のWelchの結果はF≈26.366、p≈0.000389です。二つを併記したのは方法の違いを学ぶためであり、小さいp値が出た方を採用する手順ではありません。
表が収まらない場合は、横にスクロールできます
| 確認する条件 | 最小限の点検 |
|---|---|
| 観測の独立性 | 人・店舗・日などのIDと割付単位を確認。繰り返しを別人扱いしない |
| 群内の分布 | 生データ、箱ひげ図、残差のQ-Qプロット。混ぜた全群の分布だけで判断しない |
| 群の分散と人数 | 群別の標準偏差・人数と、ばらつきの違いが生じる測定上の理由を確認 |
| 欠測・極端値 | 群別の欠測理由と除外規則。観測後に都合の悪い値を消さない |
各5人では、正規性検定が非有意でも分布を十分に確かめたことにはなりません。Welchも、独立性や正規分布に基づく小標本の近似を不要にする方法ではありません。重い裾、強い歪み、小さく不均衡な群などがあるなら、計測・推定対象・分析計画を再検討します。正規性検定の定義
全体の検定と、どの組が違うかの比較を分ける
3群なら比較はA-B、A-C、B-Cの3組です。各組に無調整の5%検定を繰り返すと、家族内で少なくとも一つを誤検出する確率を5%に管理したことにはなりません。比較どうしはデータを共有するため、常に独立という仮定で 1 - 0.95³ と計算することもできません。
比較の家族は、まとめて誤りを管理したい仮説の集合です。主要指標の全3組なのか、対照群との2組だけなのか、複数指標も含むのかを先に定めます。結果を見て都合のよい組だけを家族に残すことは避けます。
表が収まらない場合は、横にスクロールできます
| 問いと条件 | 選択肢 | 出力の読み方 |
|---|---|---|
| 共通分散の独立群で全組の平均差 | Tukey HSD。人数が異なる場合はTukey–Kramer | 全組を対象とする同時信頼区間と調整済みp値 |
| 共通分散を置かない独立群で全組 | Games–Howell | 群ごとの分散と自由度を使う近似的な多重比較 |
| 事前に決めた有限個の比較や対比 | Bonferroni、Holmなど | 定義した家族の誤検出を管理する設計 |
| 多数の探索候補から次の研究対象を探す | FDRを管理する方法など | 少なくとも1誤検出の確率を管理するFWERとは目標が違う |
全体検定が有意なら事後比較に進む、という報告の順序は分かりやすいものです。ただし事前に定めた対比とその多重性管理を、常に全体検定の有意性で開閉する必要があるわけではありません。どの仮説を主要とする設計かを先に決めます。
Pythonで全3比較の調整済み区間を得る
SciPyのtukey_hsdは、equal_var=FalseでGames–Howellを実装しています。ここでも両方を教材として表示します。実務で二つの方法から有意な行だけを取り出すためのコードではありません。
import numpy as np
from scipy import stats
groups = [np.array(x) for x in [
[3.2, 3.5, 3.0, 3.4, 3.1],
[3.6, 3.8, 3.7, 3.5, 3.9],
[3.1, 3.0, 2.9, 3.2, 3.0],
]]
pairs = [(1, 0, "B-A"), (2, 0, "C-A"), (2, 1, "C-B")]
for name, equal_var in [("Tukey", True), ("Games-Howell", False)]:
result = stats.tukey_hsd(*groups, equal_var=equal_var)
ci = result.confidence_interval(confidence_level=0.95)
for i, j, label in pairs:
print(f"{name} {label}: diff={result.statistic[i,j]:.3f} min, "
f"adjusted p={result.pvalue[i,j]:.6f}, "
f"95% CI=[{ci.low[i,j]:.3f}, {ci.high[i,j]:.3f}]")
古典的な共通分散モデルでのTukeyの結果は次の通りです。
表が収まらない場合は、横にスクロールできます
| 差の方向 | 平均差(分) | 95%同時信頼区間(分) | 読み方 |
|---|---|---|---|
| B - A | +0.46 | +0.183〜+0.737 | Bの方が長い方向 |
| C - A | -0.20 | -0.477〜+0.077 | ゼロをまたぎ、同等とも短いとも確定しない |
| C - B | -0.66 | -0.937〜-0.383 | Cの方が短い方向 |
時間が小さい方が望ましいため、差の符号を確認します。C-Aの標本差-0.20分は-12秒ですが、区間は約-28.64〜+4.64秒です。仮に導入には少なくとも10秒の短縮が必要でも、C-Aの区間全体がその基準を超える短縮を示したわけではありません。
Games–Howellでは区間の幅が組ごとに変わります。この教材でのC-Aは約-0.521〜+0.121分です。どちらの方法でも、非有意な比較を「差なし」と言い換えません。
効果量は、何のばらつきを分母にしたかを伝える
一元配置のη²(イータ二乗)は 群間平方和 / 全体平方和。この例では約0.779です。標本内の変動の約77.9%が群の違いに対応するという記述で、作業時間が77.9%短くなる意味ではありません。割付や交絡の検討なしに因果的な説明率とも呼べません。
ω²(オメガ二乗)は、この古典的な独立群モデルでは (群間平方和 - (k - 1) × 群内平均平方) / (全体平方和 + 群内平均平方)。約0.729です。標本のη²の上方バイアスを軽減する推定量ですが、誤差がなくなるわけではありません。別の標本では負になる場合もあり、その扱いを報告せず隠さないようにします。
partial η²は 対象効果の平方和 / (対象効果の平方和 + その誤差平方和)。ここではη²と一致しますが、複数要因や反復測定では分母が変わります。研究設計の違う値を、そのまま効果のランキングに使えません。効果量の定義と設計依存性
最初のコードには、古典的な固定効果一元配置モデルについて、非心F分布を反転して得るη²パラメータの95%区間も含めました。nobs=15を指定した結果は約0.389〜0.861で、点推定だけでは大きさを絞り切れないことが分かります。これはλ = N × f²、η² = f² / (1 + f²)というパラメータ化の区間です。statsmodelsの説明と境界上の制約を確認し、WelchのF値や反復測定のpartial η²に流用しません。
元の単位の平均差は「何分を節約できそうか」という問い、η²は「群の違いと変動がどの程度対応するか」という問いに答えます。片方で他方を置き換えず、費用・品質低下・導入対象も合わせて評価します。
図を再現し、平均区間と多重比較を混同しない
次の図は各群の生データ・箱ひげ・平均の95%t区間を描きます。各5人なので、群ごとの区間には自由度4のt分位点を用います。これは前節のTukey同時区間とは別です。平均のt区間の式
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
groups = [np.array(x) for x in [
[3.2, 3.5, 3.0, 3.4, 3.1],
[3.6, 3.8, 3.7, 3.5, 3.9],
[3.1, 3.0, 2.9, 3.2, 3.0],
]]
fig, axes = plt.subplots(1, 2, figsize=(9, 4))
axes[0].boxplot(groups, tick_labels=["A", "B", "C"])
for i, values in enumerate(groups, start=1):
axes[0].scatter(i + np.linspace(-0.08, 0.08, len(values)), values)
axes[0].set(ylabel="Task time (minutes)", title="Synthetic individual observations")
means = [x.mean() for x in groups]
half_width = [stats.t.ppf(0.975, len(x)-1) * stats.sem(x) for x in groups]
axes[1].errorbar([0, 1, 2], means, yerr=half_width, fmt="o", capsize=5)
axes[1].set(xticks=[0, 1, 2], xticklabels=["A", "B", "C"],
ylabel="Mean time (minutes)", title="95% t CI, not simultaneous")
for ax in axes:
ax.set_ylim(2.7, 4.1)
fig.tight_layout()
fig.savefig("anova-observations-and-mean-ci.png", dpi=150)
plt.close(fig)
print("Saved anova-observations-and-mean-ci.png")
小標本では箱ひげだけだと点数の少なさが伝わりにくいため、生データと人数も残します。図の軸・単位・区間の方式・比較対象をキャプションに書くと、会議用に図だけを抜き出したときの誤読も減らせます。
二元配置・反復測定・順位法へ進む条件
二元配置では、たとえば手順と習熟度を要因にします。手順の差が初心者と経験者で変わることが交互作用です。その可能性を無視し、全員を平均した「手順の主効果」だけで各層の結論を決めないようにします。要因の組合せごとの人数と観測が必要であり、セルが空の場合や著しく不均衡な場合は解釈も難しくなります。多要因モデルと残差点検
反復測定では同じ人の値が相関するので、独立群のf_onewayへそのまま渡しません。三水準以上の被験者内効果を通常の単変量反復測定ANOVAで扱う場合は、差どうしの分散に関する球面性にも注意が必要です。違反を扱う自由度補正や、相関構造を明示する混合モデルなどを設計に応じて検討します。実装の対応範囲も確認し、たとえばstatsmodelsのAnovaRMは、完全に釣り合った被験者内設計のみを支持し、球面性違反の補正は未実装としています。
順位に基づく方法では、独立群のKruskal–Wallisなどが候補です。ただし平均差の検定とは同じではなく、中央値の違いと読むにも分布形の条件が関わります。小標本で近似が適切か、全体検定後の比較をどう管理するかも必要です。SciPyのKruskal–Wallis
対数変換やブートストラップも選択肢ですが、前者は比較する量を変え、後者は独立した観測単位を保った再標本化が必要です。どの方法も、欠測による選択や計測の偏りを自動的に修復するものではありません。
採用前に残す最小の分析メモ
- 対象と設計:誰をどの単位で割り付け、何をいつ測るか。主要指標と望ましい差の方向も書く。
- 比較の計画:全体仮説、主要な比較の家族、分析法、多重性管理、有意水準、必要な改善幅を決める。
- 人数と終了:想定する平均差・ばらつき・配分から検出力を計画する。全体検定の検出力が高くても、重要な各組の比較に十分とは限らない。
- 品質点検:群別人数、欠測理由、測定誤り、残差や生データを確認する。
- 報告:平均と標準偏差、ANOVA表、主要比較の差と区間、効果量、費用と残る不確実性をまとめる。
結果を見るたびに人数を増やしたり、有意になった比較だけで終了したりすると、固定時点の検定の保証をそのまま使えません。途中判定は別の設計として扱います。継続監視と固定標本推論の違い
練習として、3つのLPで購入率を比較するなら、最初に何を集めるでしょうか。答えは率だけでなく群別の購入数と対象者数、割付・期間・重複の情報です。同じ10人が3画面を使うなら、30人の独立群ではなく10人の反復測定です。最後に「全体で有意、C-Aは非有意」なら矛盾でしょうか。全体は少なくとも一つの差を検出する問いなので、矛盾ではありません。