「anova」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「anova」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「anova の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
3つ以上のグループを比べる道具です。
平均に差があるか調べるために使います。
部活ごとの練習時間の違いを調べます。
分析の種類や結果の見方を学びます。
🍰 まずはやさしく
グループの平均を比べる方法です。
データのばらつきから差を判定します。
スマホの機種による利用時間を比べます。
分析の手順と使い分けを解説します。
本ページでは、 分散分析 (ANOVA)を統合的に解説します。 一元配置・二元配置・反復測定・post-hoc 検定・効果量を一気通貫で扱います。
ANOVA は「3 群以上の平均が等しいか」を検定する手法。 名前は「分散」ですが、 検定対象は平均値です。 群内変動と群間変動を比較することから「分散」と呼ばれます。
🍰 まずはやさしく
ばらつきを切り分けるイメージです。
差が本物かどうかを判断するために使います。
クラスごとのテスト点数の差を調べます。
ばらつきの比率で考える仕組みを学びます。
ANOVA(分散分析)の核心は名前のとおり 「分散の分解」。 SSDSE-B-2026 で 47 都道府県を 北日本・東日本・西日本 の 3 ブロックに分けて出生率を比較するとき、 「全体のバラつき」を 群間のバラつき(地域差) と 群内のバラつき(地域内の差) に切り分け、 その比率(F値)で「地域差は本物か」を検定します。
比喩で言うと、 ある楽団の演奏のばらつきを「楽器セクション間のばらつき」と「同じセクション内のばらつき」に切り分けて、 もしセクション間のバラつきが圧倒的に大きければ「セクションごとに本当に差がある」と判断するイメージ。 ANOVAは t検定の自然な拡張で、 群が 3 つ以上のときに 多重比較の罠 を避けながら一発で検定できる点が魅力です。
3 群(A・B・C)の平均を 3 本のスライダーで、 群内のばらつき(標準偏差)を 1 本のスライダーで動かしてみましょう。 群間平方和 SSbetween・群内平方和 SSwithin・自由度・F 統計量・p 値がリアルタイムで再計算されます。 群間の平均差が大きいほど、 また群内のばらつきが小さいほど F は大きく p は小さくなる —— これが「分散の比で平均の差を検定する」という ANOVA の核心です(各群 n = 8、 k = 3 群、 全体 N = 24 で固定)。
💡 体感ポイント: 3 つの平均を近づけると SSbetween が縮み F が 1 に近づいて p が大きくなります(=差なし)。 逆に 1 群だけ大きく離すと SSbetween が伸びて F が跳ね上がり、 観測 F が右側の赤い棄却域へ入って p < 0.05 になります。 σ(群内ばらつき)を上げると分母 MSwithin が増えて同じ平均差でも F が下がる —— 「シグナル(群間差)÷ ノイズ(群内差)」という F 比の意味がそのまま見えます。
この体験と直結する概念: t 検定(2 群版 ANOVA・F = t²)/ 効果量 η²(= SSbetween/SStotal、 差の実質的大きさ)/ 多重比較・多重検定(有意後に Tukey HSD でどの群ペアが違うか特定)/ F 検定・仮説検定。 なお F 検定は正規性・等分散性・独立性を前提とし、 崩れる場合は Kruskal–Wallis や Welch ANOVA を用います。
🍰 まずはやさしく
差の大きさを表す数値のことです。
平均の差がどれだけ重要かを知るために使います。
買い物への影響度が強いか調べます。
効果量の計算方法と基準を学びます。
ANOVA の効果量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import numpy as np import pandas as pd from scipy import stats # ── この抜粋だけで動くように、8 地方ブロック別の年平均気温を用意する ── # 英字の項目コード(A1101 など)を使うので、2 行目の日本語名は読み飛ばす _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _df = _df[_df['SSDSE-B-2026'] == 2023] # 最新年度の 47 都道府県 # 「地方ブロック」は SSDSE に無い列なので、都道府県名から 8 区分を作る _BLOCKS = { '北海道': ['北海道'], '東北': ['青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県'], '関東': ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'], '中部': ['新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県', '静岡県', '愛知県'], '近畿': ['三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県'], '中国': ['鳥取県', '島根県', '岡山県', '広島県', '山口県'], '四国': ['徳島県', '香川県', '愛媛県', '高知県'], '九州': ['福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県', '鹿児島県', '沖縄県'], } _R = {p: g for g, ps in _BLOCKS.items() for p in ps} _df = _df.assign(block=_df['Prefecture'].map(_R), temp=pd.to_numeric(_df['B4101'], errors='coerce')) # 年平均気温(℃) # groups = 「ブロックごとの年平均気温の配列」のリスト(8 群) groups = [g['temp'].values for _, g in _df.groupby('block', sort=False)] def eta_squared(groups): grand = np.mean(np.concatenate(groups)) ss_b = sum(len(g)*(np.mean(g)-grand)**2 for g in groups) ss_t = sum(((np.concatenate(groups)-grand)**2)) return ss_b / ss_t F, p = stats.f_oneway(*groups) grand = np.mean(np.concatenate(groups)) ss_b = sum(len(g) * (np.mean(g) - grand) ** 2 for g in groups) ss_t = sum((np.concatenate(groups) - grand) ** 2) print(f'F統計量 = {F:.3f}, p値 = {p:.3g}') print(f'群間平方和 = {ss_b:.1f}, 群内平方和 = {ss_t - ss_b:.1f}') print(f'η² = {eta_squared(groups):.3f}') |
| 前提 | 確認方法 | 違反したら |
|---|---|---|
| 残差の正規性 | QQプロット・Shapiro | Kruskal–Wallis |
| 等分散性 | Levene 検定 | Welch's ANOVA |
| 独立性 | サンプリング設計 | 混合効果モデル |
| 球面性 (反復測定) | Mauchly検定 | Greenhouse–Geisser補正 |
1 2 3 | from scipy import stats H, p = stats.kruskal(*groups) print(f'Kruskal-Wallis H = {H:.3f}, p = {p:.4f}') |
| 記号 | 意味 | SSDSE-B-2026 例 |
|---|---|---|
| $k$ | 群数 | 3 地域ブロック |
| $N$ | 総サンプル数 | 47 都道府県 |
| $\text{SS}_{\text{between}}$ | 群間平方和(地域差) | 地域ブロックの平均出生率のバラつき |
| $\text{SS}_{\text{within}}$ | 群内平方和(誤差) | 同じブロック内の県間バラつき |
| $F$ | 「シグナル/ノイズ」比 | 1 を大幅に超えれば群差あり |
有意になったら post-hoc 検定(Tukey HSDなど) で「どの群間に差があるか」を特定し、 効果量 $\eta^2 = \text{SS}_{\text{between}}/\text{SS}_{\text{total}}$ を必ず併記します。
SSDSE-B-2026(47都道府県、 2023年)で、 都道府県を北海道・東北・関東・中部・近畿・中国・四国・九州沖縄の8地域に分け、 食料費(L322101、 二人以上の世帯、 円)に地域差があるかを一元配置 ANOVA で確認します。
| 地域 | 県数 | 食料費平均(円) | 標準偏差 |
|---|---|---|---|
| 北海道 | 1 | 74,341 | — |
| 東北 | 6 | 80,824 | 2,844 |
| 関東 | 7 | 86,670 | 8,305 |
| 中部 | 9 | 82,182 | 4,771 |
| 近畿 | 7 | 83,084 | 4,028 |
| 中国 | 5 | 78,177 | 4,225 |
| 四国 | 4 | 75,821 | 3,792 |
| 九州沖縄 | 8 | 75,842 | 2,844 |
※実値は SSDSE-B-2026(2023年)から集計。 ANOVA の主結果は F(7, 39) = 4.09、 p = 0.0019、 η² = 0.42。 「地域は食料費に影響あり」を示唆。
有意な ANOVA が出ても、 どの地域ペアが違うかは分かりません。 Tukey HSD で全ペアを比較すると、 「関東 vs 九州沖縄」(差 = 10,828 円, p = 0.002)と「関東 vs 四国」(差 = 10,849 円, p = 0.019)が有意(p < 0.05)で、 「食料費が高い関東と、 低めの四国・九州沖縄で差が大きい」構造が浮かびます。
η² = 0.42 は「食料費の総変動のうち、 42% が地域差で説明できる」と読みます。 Cohen 基準では η² > 0.14 が「大きい効果」なので、 統計的にも実質的にも大きな地域差と結論できます。
合成データ (3 群、 各 $n=4$) を一元配置 ANOVA の数式に代入し、 Step 1〜6 で SS / MS / F / p を手計算する。 同じ計算を Python (scipy.stats.f_oneway) で再現し、 結果が完全一致することを確認する。
$$ F = \frac{MS_\text{between}}{MS_\text{within}} = \frac{SS_\text{between}/(k-1)}{SS_\text{within}/(N-k)} $$
ただし $SS_\text{between} = \sum_{j=1}^{k} n_j (\bar{x}_j - \bar{x})^2$、 $SS_\text{within} = \sum_{j=1}^{k}\sum_{i=1}^{n_j}(x_{ij} - \bar{x}_j)^2$。
| 群 | 観測値 | $n_j$ | 群平均 $\bar{x}_j$ |
|---|---|---|---|
| A | 4, 6, 5, 5 | 4 | 5.0 |
| B | 7, 8, 6, 7 | 4 | 7.0 |
| C | 9, 11, 10, 10 | 4 | 10.0 |
| 全体 | $N=12$ | — | $\bar{x} = (5+7+10) \cdot 4 / 12 = 7.333333$ |
| 群 | $x_{ij} - \bar{x}_j$ | $(x_{ij}-\bar{x}_j)^2$ の合計 |
|---|---|---|
| A (mean=5) | $-1, +1, 0, 0$ | $1+1+0+0 = 2$ |
| B (mean=7) | $0, +1, -1, 0$ | $0+1+1+0 = 2$ |
| C (mean=10) | $-1, +1, 0, 0$ | $1+1+0+0 = 2$ |
| $SS_\text{within}$ | — | $2 + 2 + 2 = 6$ |
| 群 | $\bar{x}_j - \bar{x}$ | $n_j (\bar{x}_j - \bar{x})^2$ |
|---|---|---|
| A | $5 - 22/3 = -7/3$ | $4 \times (7/3)^2 = 196/9$ |
| B | $7 - 22/3 = -1/3$ | $4 \times (1/3)^2 = 4/9$ |
| C | $10 - 22/3 = +8/3$ | $4 \times (8/3)^2 = 256/9$ |
| $SS_\text{between}$ | — | $(196+4+256)/9 = 456/9 \approx 50.666667$ |
| 項目 | 計算 | 結果 |
|---|---|---|
| $df_\text{between} = k - 1$ | $3 - 1$ | 2 |
| $df_\text{within} = N - k$ | $12 - 3$ | 9 |
| 項目 | 計算 | 結果 |
|---|---|---|
| $MS_\text{between}$ | $50.666667 / 2$ | 25.333333 |
| $MS_\text{within}$ | $6 / 9$ | 0.666667 |
| $F$ | $25.333333 / 0.666667$ | 38.000000 |
| 項目 | 結果 |
|---|---|
| $F(2, 9)$ 分布の上側確率 | $p \approx 0.0000409$ |
| 有意水準 $\alpha = 0.05$ で判定 | $p < 0.05$ → 3 群の母平均は等しくない |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import numpy as np from scipy import stats # Step 1: 合成データ (3 群、 各 n=4) A = np.array([4, 6, 5, 5]) B = np.array([7, 8, 6, 7]) C = np.array([9, 11, 10, 10]) groups = [A, B, C] all_data = np.concatenate(groups) k = len(groups) N = len(all_data) grand_mean = all_data.mean() # Step 2: SS_within ss_within = sum(((g - g.mean())**2).sum() for g in groups) # Step 3: SS_between ss_between = sum(len(g) * (g.mean() - grand_mean)**2 for g in groups) # Step 4: df df_b = k - 1 df_w = N - k # Step 5: MS と F ms_b = ss_between / df_b ms_w = ss_within / df_w F = ms_b / ms_w # Step 6: p 値 p = 1 - stats.f.cdf(F, df_b, df_w) print(f"grand_mean = {grand_mean:.6f}") print(f"SS_within = {ss_within}") print(f"SS_between = {ss_between:.6f}") print(f"df_between = {df_b}, df_within = {df_w}") print(f"MS_between = {ms_b:.6f}") print(f"MS_within = {ms_w:.6f}") print(f"F = {F:.6f}") print(f"p-value = {p:.7f}") # 検算: scipy.stats.f_oneway F2, p2 = stats.f_oneway(A, B, C) print(f"scipy F = {F2:.6f}, p = {p2:.7f}") |
💬 手計算 (Step 2〜6) と Python 出力 (および scipy.stats.f_oneway の検算) が完全一致 ($F = 38.000$、 $p \approx 4.09 \times 10^{-5}$)。 群内ばらつきが小さく群間ばらつきが大きいため、 $\alpha = 0.05$ で「3 群の母平均は等しい」を棄却できる。
| 用途 | 関数 |
|---|---|
| 一元配置ANOVA | scipy.stats.f_oneway |
| 二元配置ANOVA | statsmodels.formula.api.ols + anova_lm |
| 反復測定ANOVA | statsmodels.stats.anova.AnovaRM |
| Welch's ANOVA | pingouin.welch_anova |
| Tukey HSD | statsmodels.stats.multicomp.pairwise_tukeyhsd |
| Bonferroni | statsmodels.stats.multitest.multipletests(method='bonferroni') |
| Kruskal–Wallis | scipy.stats.kruskal |
| Friedman | scipy.stats.friedmanchisquare |
statsmodels の anova_lm が出力する表の各列:
| 列 | 意味 |
|---|---|
| sum_sq | 平方和 SS。 各要因の説明する変動 |
| df | 自由度。 K-1(要因)、 N-K(残差) |
| F | F統計量 = MS要因 / MS残差 |
| PR(>F) | F値以上の値が出る確率(p値) |
ANOVA は実は「カテゴリ変数(ダミー変数)だけの線形回帰」と数学的に等価。 statsmodels では同じ ols で記述できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import statsmodels.formula.api as smf import statsmodels.api as sm # ── この抜粋だけで動くように、都市規模つきの表を用意する ── df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() # 最新年度の 47 都道府県 # 「都市規模」は SSDSE に無い列なので、総人口を 3 等分して作る df['都市規模'] = pd.qcut(df['A1101'], 3, labels=['小', '中', '大']) df['C5401'] = pd.to_numeric(df['C5401'], errors='coerce') # 標準地価(住宅地) df = df.dropna(subset=['C5401']) # ANOVA としての記述 # 数式に C(...) と書くとカテゴリ扱いになるが、このページでは前の例で # 変数 C を作っているため C() が上書きされてしまう。 # 都市規模は category 型なので、そのまま書けばカテゴリとして扱われる。 m_anova = smf.ols('C5401 ~ 都市規模', data=df).fit() # C5401=標準地価(住宅地) print(sm.stats.anova_lm(m_anova, typ=2)) # 同じモデルを「ダミー回帰」として見る print(m_anova.summary()) |
p値・F値は完全に一致する。 ANOVA の「カテゴリの主効果」が、 回帰の「ダミー係数の合同検定」に対応している。
A. はい。 2 群 ANOVA は 2 標本 t検定(等分散仮定)と完全に等価です(F = t²、 df の関係も対応)。 ただし慣例的に 2 群なら t検定を使います。
A. あります。 ANOVA は「全体的な差」、 post-hoc は「ペアごとの差」を見るため、 補正の保守性で起こり得ます。 効果量と CI で全体像を補完しましょう。
A. ANCOVA(共分散分析)を使います。 statsmodels では ols('y ~ C(group) + covariate', data=df) のように共変量を加えるだけです。
A. Mauchly 検定で確認、 違反していたら Greenhouse–Geisser や Huynh–Feldt 補正を使います。 または混合効果モデルに切り替える選択肢もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd from scipy import stats # ── この抜粋だけで動くように、3 群のデータを用意する ── # 47 都道府県を総人口で 3 等分し、それぞれの高齢化率を 1 群とする _a = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _a = _a[_a['SSDSE-B-2026'] == 2023].copy() _a['aging'] = _a['A1303'] / _a['A1101'] * 100 _a['規模'] = pd.qcut(_a['A1101'], 3, labels=['小', '中', '大']) group_a = _a.loc[_a['規模'] == '小', 'aging'].values group_b = _a.loc[_a['規模'] == '中', 'aging'].values group_c = _a.loc[_a['規模'] == '大', 'aging'].values # 3群を直接渡す F, p = stats.f_oneway(group_a, group_b, group_c) print(f"F={F:.3f}, p={p:.4f}") # Welch 版(等分散仮定なし)は alexandergovern を使う |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd import statsmodels.api as sm from statsmodels.formula.api import ols from statsmodels.stats.multicomp import pairwise_tukeyhsd # ── この抜粋だけで動くように、結果変数 y と群 region を持つ表を用意する ── df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df['y'] = df['A1303'] / df['A1101'] * 100 # 高齢化率(%) を結果変数に df['region'] = pd.qcut(df['A1101'], 3, labels=['小', '中', '大']) # 人口 3 区分を群に # 数式に C(...) と書くとカテゴリ扱いになるが、このページでは前の例で # 変数 C を作っているため C() が上書きされてしまう。 # region は category 型なので、そのまま書けばカテゴリとして扱われる。 model = ols('y ~ region', data=df).fit() print(sm.stats.anova_lm(model, typ=2)) print(pairwise_tukeyhsd(df['y'], df['region'], alpha=0.05)) |
1 2 3 4 5 | import pingouin as pg # ANOVA + η² + 等分散検定が一発 print(pg.anova(data=df, dv='y', between='region', detailed=True)) print(pg.welch_anova(data=df, dv='y', between='region')) print(pg.pairwise_tukey(data=df, dv='y', between='region')) |
scikit-learn の SelectKBest(score_func=f_classif) は内部でカテゴリ y に対する ANOVA F検定を行い、 F値が大きい特徴量を選びます。 分類問題の前処理で頻出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd from sklearn.feature_selection import SelectKBest, f_classif # ── この抜粋だけで動くように、特徴量 X と目的 y を用意する ── # ANOVA の F 値は「群ごとに平均が違う特徴量」を選ぶのに使える _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023] _num = _d.select_dtypes(include='number').dropna(axis=1) # 値が 1 種類しかない列は分散が 0 で F 値を計算できないので、先に落とす _num = _num.loc[:, _num.nunique() > 1] X = _num.values y = (_d['A1101'] > _d['A1101'].median()).astype(int).values # 人口が中央値超か selector = SelectKBest(score_func=f_classif, k=10) X_new = selector.fit_transform(X, y) print(f'選ばれた特徴量: {X_new.shape[1]} 本 / 全 {X.shape[1]} 本') # F 値が大きい順に上位 10 本を見る _top = pd.Series(selector.scores_, index=_num.columns).sort_values(ascending=False) print(_top.head(10).round(2).to_string()) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd from scipy import stats # ── この抜粋だけで動くように、3 群のデータを用意する ── _k = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _k = _k[_k['SSDSE-B-2026'] == 2023].copy() _k['aging'] = _k['A1303'] / _k['A1101'] * 100 _k['規模'] = pd.qcut(_k['A1101'], 3, labels=['小', '中', '大']) a = _k.loc[_k['規模'] == '小', 'aging'].values b = _k.loc[_k['規模'] == '中', 'aging'].values c = _k.loc[_k['規模'] == '大', 'aging'].values H, p = stats.kruskal(a, b, c) # 正規性を仮定しない print(f'Kruskal-Wallis H = {H:.3f}, p = {p:.4f}') # 事後検定は Dunn or Mann-Whitney + Bonferroni |
| 落とし穴 | 対処 |
|---|---|
| ペアワイズ t検定の連発 | 必ず ANOVA + post-hoc を使う。 |
| 有意で終わる | どの群間に差があるかを post-hoc で示す。 η² と CI を併記。 |
| 交互作用を見ない | 二元配置では交互作用プロットを必ず描く。 |
| 不均衡データに Type I SS | 不均衡なら Type II/III SS を使う。 |
| 反復測定を独立ANOVAで処理 | 必ず subject を within-subject 因子に。 |
| 前提条件をスキップ | 残差プロット・Levene検定を行う。 |
| 名義尺度を量的に扱う | C() で明示的にカテゴリ化。 |
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd from scipy import stats from statsmodels.stats.multicomp import pairwise_tukeyhsd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023年の47都道府県 df['都市規模'] = pd.qcut(df['A1101'], q=3, labels=['小規模県', '中規模県', '大規模県']) groups = [g['C5401'].values for _, g in df.groupby('都市規模')] F, p = stats.f_oneway(*groups) print(f'ANOVA: F={F:.2f}, p={p:.4f}') print(pairwise_tukeyhsd(df['C5401'], df['都市規模'])) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # ── この抜粋だけで動くように、都市規模つきの表を作り直す ── df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df['都市規模'] = pd.qcut(df['A1101'], 3, labels=['小', '中', '大']) df['C5401'] = pd.to_numeric(df['C5401'], errors='coerce') # 標準地価(住宅地) df['高齢化率'] = df['A1303'] / df['A1101'] * 100 # 高齢人口/総人口 df['高齢度'] = (df['高齢化率'] >= df['高齢化率'].median()).map({True:'高', False:'低'}) sns.pointplot(data=df, x='都市規模', y='C5401', hue='高齢度', dodge=True) plt.title('交互作用プロット') plt.show() |
1 2 3 | print(stats.shapiro(df['C5401'])) print(stats.levene(*groups)) print(stats.kruskal(*groups)) |
「ANOVA の結果、 有意でした。」
「都市規模 3 群(小規模県・中規模県・大規模県、 総人口 A1101 の3分位、 各 n=15〜16)の標準地価(住宅地、 C5401、 円/m²)を一元配置 ANOVA で比較した結果、 主効果が有意であった (F(2, 44) = 8.83, p < .001, η² = .29 大効果)。 Tukey HSD による post-hoc では、 大規模県群が小規模県群より有意に高く (Mdiff = 75,019 円/m², 95% CI [27,346, 122,692], p = .001)、 中規模県群よりも有意に高かった (Mdiff = 68,125 円/m², 95% CI [19,665, 116,586], p = .004)。 中規模県群と小規模県群の差は有意ではなかった (p = .937)。 なお正規性・等分散性の検定では違反が示されたため(Shapiro p < .001、 Levene p = .003)、 Kruskal–Wallis 検定でも確認した(H = 16.45, p < .001、 結論は同じ)。」
古典的 ANOVA は「各群の分散が等しい」と仮定しています。 群間でばらつきが大きく違うと、 F検定が歪んで p値が信用できなくなります。 必ず Levene検定 か Bartlett検定 で確認。 違反していたら Welch ANOVA(scipy.stats.f_oneway ではなく scipy.stats.alexandergovern や Welch 版)か Kruskal-Wallis(ノンパラ版)を使う。 違反を無視すると Type I エラー率が名目 5% から 10-20% まで膨れることがあります。
F検定は「どこかの群が違う」とだけ言うので、 どのペアが違うかは別途調べる必要があります。 必ず Tukey HSD、 Dunnett、 Bonferroni などの事後検定を実行し、 さらに 効果量(η², ω²)と95% 信頼区間も併記。 「F(2,57)=4.2, p=0.02」だけで終わらせず、 「群A対B:差=12.3、 95%CI [3.1, 21.5]、 d=0.65」のような完全報告が査読基準。
3群比較で全ペアを比べると 3 回検定するので、 Type I エラーが累積。 「3つ全部が独立に α=0.05」ならいずれか1つで偽陽性となる確率は約 14%。 Bonferroni(α/k で割る)か Tukey HSD(同時信頼区間)か FDR(Benjamini-Hochberg)で補正。 ANOVA の F検定が「家族別 α を守る」と思い込んで、 事後で補正しないのが一番危険なパターン。
サンプル数が大きいと Shapiro-Wilk 等の正規性検定はほぼ必ず棄却される(ささいな偏りも検出)。 一方サンプルが小さいと検出力が低くてどんな分布も「正規」と判定。 つまり正規性検定はサンプル数に振り回されるので使い物にならない場面が多い。 代わりに Q-Q プロット、 ヒストグラム、 歪度・尖度を目視で確認。 また各群 n > 30 なら中心極限定理で F検定はかなり頑健なので、 過度な心配は不要。
同じ被験者を複数回測る(被験者内要因がある)場合、 「すべての時点ペアの差の分散が等しい」という球面性(sphericity)の仮定が必要。 違反すると F検定の自由度が間違って大きくなり、 p値が過小評価される。 Mauchly 検定で確認、 違反ならGreenhouse-GeisserかHuynh-Feldtで自由度補正、 または混合効果モデル(lmer)に移行。
「軽症・中等症・重症」のように順序のあるカテゴリ間で y を比べる時、 普通の ANOVA だと「順序情報」を捨ててしまう。 順序付き群間の単調傾向を検出するならJonckheere-Terpstra 検定か線形傾向検定(直交対比)を使う。 「平均が単調に増えるか」という具体的仮説を検証できる。 順序を無視した普通のANOVA より検出力が高くなる。
関連概念を視覚的に整理した概念マップ。
中心の ANOVA から左の 効果量 へは「F 比だけでなく η²=SS_between/SS_total で実務的意味を測る」線、 上の t 検定 へは「2 群限定の特殊形、 t²=F (df1=1)」線、 右上の Welch ANOVA へは「等分散仮定が崩れた場合の代替」線、 右下の Kruskal-Wallis へは「正規性が破れた場合のノンパラ代替」線、 下の Tukey HSD へは「全体差有意の後にどの群ペアが異なるかを FWER 制御で特定」線でつながる。 ANOVA の本質は「総分散を群間分散と群内分散に分解し、 F 比で群間の系統的差を検出する分散分解の枠組み」であり、 SSDSE-B-2026(2023 年)の 47 都道府県を 8 地方区分に分けたとき、 年平均気温の地方差で総分散の η²=0.71 を説明できるという形で実値が読める。
ANOVA は「3 群以上の平均差検定」を起点に、 分散分解という枠組みで多くの隣接手法と連結する。
SSDSE-B-2026(2023 年)で「47 都道府県を 8 地方区分に分け、 年平均気温の地方差を検定」する場合、 一元配置 ANOVA で F 比と $\eta^2=0.71$ を得て、 Tukey HSD で具体的にどの地方ペアが異なるかを特定する一連の流れが標準パイプライン。
ANOVA の選択は「因子数」「測定の独立性」「正規性・等分散性」の 3 軸で判定する。
有意 → 必ず Tukey HSD などの事後検定でどの群ペアが違うかを特定。 全体差有意 = 全ペア差ありではない点が ANOVA 解釈の鉄則。
ANOVA を一言でいうと 「シグナル ÷ ノイズ」の比を作って、 その比が偶然では説明できないほど大きいかを見る手法です。 ここでいうシグナルは群平均どうしのばらつき(群間)、 ノイズは同じ群の中での個体差(群内)。 3 群以上の平均がすべて同じ(帰無仮説)なら、 群平均のばらつきも「たまたま群内のばらつきから生じた程度」に収まるはずで、 このとき $F = \text{MS}_\text{between}/\text{MS}_\text{within}$ は理論上 1 前後になります。 逆に本当に群差があると分子だけが膨らみ、 F は 1 を大きく超えます。
なぜ「平均の差」を見たいのに「分散」を計算するのか? ポイントは、 群平均の散らばりを 1 つのスカラー(群間平方和)に要約すると、 群がいくつあっても検定を 1 回で済ませられる点にあります。 これが「t 検定を 3 群以上へ拡張したもの」と言われる理由です。 3 群を t 検定で総当たり(A-B, A-C, B-C)すると 3 回、 4 群なら 6 回と検定が増え、 多重比較により偽陽性が累積します(3 回なら「どれか 1 つで誤検出」の確率が約 14%)。 ANOVA はまず「全体としてどこかに差があるか」を 1 回の F 検定でまとめて判定し、 第一種の過誤が膨らむのを防ぎます。
2 群だけのときは、 t 検定と ANOVA は完全に等価で $F = t^2$(分子の自由度 $df_1 = 1$)という関係が成り立ちます。 つまり ANOVA は t 検定の一般化であり、 t 検定は ANOVA の特殊ケース。 検定統計量が「差の大きさ ÷ ばらつき」という同じ設計思想でできていることが、 $F = t^2$ という等式にそのまま表れています。 F の分母($\text{MS}_\text{within}$)が「共通の物差し(ものさし)」の役割を果たし、 群内ばらつきが大きいほど同じ平均差でも F は小さく=差を検出しにくくなります。
ANOVA は「回せば必ず p 値が出る」手法ですが、 前提が崩れた p 値は数字が出るだけで意味を持ちません。 実務で踏みやすい罠を、 判定の順序で整理します。
| 落とし穴 | 何が起きるか | 対処 |
|---|---|---|
| ① 正規性を確認しない | 残差が大きく歪むと p 値がずれる(特に小標本・強い歪み) | 残差の正規性を Shapiro–Wilk・QQ プロットで確認。 大幅違反は Kruskal–Wallis |
| ② 等分散性を仮定したまま | 群でばらつきが違うと第一種の過誤が名目 5% から 10〜20% に膨張 | Levene / Bartlett 検定で確認。 違反時は Welch ANOVA(等分散を仮定しない版) |
| ③ 独立性の破れ | 同一個体の反復・空間/時間の相関で有効サンプルが水増しされ p が過小 | サンプリング設計を見直す。 反復測定 ANOVA や混合効果モデルへ |
| ④ 有意で満足して終える | F の有意は「どこかに差」だけ。 どの群かは分からない | 事後検定(Tukey HSD 等)でペアを特定。 多重比較補正込みで |
| ⑤ 効果量を書かない | 大標本では小さな差でも p<0.05 になり「実質的な大きさ」が伝わらない | 効果量 η²(または偏り補正の ω²)を必ず併記。 η² の目安 0.01/0.06/0.14=小/中/大 |
| ⑥ 外れ値・不均衡データ | 1 点の外れ値が群平均を動かす/群サイズが極端に違うと SS の分解がタイプ依存に | 箱ひげ図で外れ値を可視化。 不均衡時は Type II/III 平方和を選び、 頑健手法も検討 |
| ⑦ 一元と二元を混同 | 2 因子あるのに一元で回すと交互作用を見落とす(交絡) | 因子が 2 つ以上なら二元配置 ANOVA で主効果+交互作用を分離 |
💡 前提チェックの結論が「古典 ANOVA・Welch・Kruskal–Wallis のどれでも同じ」なら結論は頑健です。 逆に手法で結論が割れるときこそ、 どの前提が崩れているかを見極める必要があります。
一元配置 ANOVA を起点に、 因子の数・測定の構造・仮定の崩れ方に応じて手法が枝分かれします。 「何が起きたらどこへ進むか」を一望できるように整理します。
| 発展手法 | いつ使うか | 要点 |
|---|---|---|
| 一元配置 ANOVA | 因子が 1 つ(例: 地方区分だけ) | 3 群以上の平均差を 1 回で検定。 本ページの基本形 |
| 二元配置 ANOVA | 因子が 2 つ(例: 地方 × 都市規模) | 2 つの主効果に加え交互作用(片方の効果が他方の水準で変わるか)を分離 |
| 多元配置 ANOVA | 因子が 3 つ以上 | 高次の交互作用まで扱えるが解釈が急に難しくなる。 交互作用プロット必須 |
| 反復測定 ANOVA | 同一個体を繰り返し測定(例: 同じ県の複数年) | 個体差を分離。 球面性を Mauchly 検定で確認し、 崩れたら Greenhouse–Geisser 補正 |
| 混合効果モデル | 独立性が崩れる・欠測やネスト構造がある | 固定効果+変量効果。 反復測定 ANOVA の一般化で、 不均衡・欠測に強い |
| Welch ANOVA | 等分散が崩れている(Levene で有意) | 分母を群ごとの分散で重み付け。 等分散を仮定せずに平均差を検定 |
| Kruskal–Wallis | 正規性が大きく崩れる・順序尺度 | 一元配置 ANOVA のノンパラ版(順位に基づく)。 反復測定版は Friedman 検定 |
| 事後検定(Tukey HSD) | F が有意でどのペアか特定したい | 全ペア比較を FWER 制御下で。 対照群との比較は Dunnett、 汎用は Bonferroni |
| 効果量 η² / ω² | 常に(p 値と併記) | η²=SS_between/SS_total。 ω² は η² の上振れを補正した推定量。 Cohen's f も併用可 |
| 線形回帰への一般化 | 連続共変量も入れたい(ANCOVA) | ANOVA=カテゴリ変数だけの回帰。 共変量を足せば ANCOVA、 さらに一般化で GLM |
関連ページ: t 検定(2 群版・$F=t^2$)/ 各種 t 検定/ F 検定・仮説検定/ p 値・自由度/ 効果量/ 多重比較・多重検定/ Kruskal–Wallis・ノンパラメトリック法/ 正規性・Shapiro–Wilk 検定。 (Welch ANOVA・Tukey HSD・二元配置/反復測定 ANOVA・混合効果モデルは本ページ内で解説)
SSDSE-B-2026(cp932・skiprows=[1]・2023 年・47 都道府県)の合計特殊出生率 A4103を、 都道府県を 8 地方ブロック(北海道/東北/関東/中部/近畿/中国/四国/九州沖縄)に分けて一元配置 ANOVA で比較した実測結果です(数値はすべて実データから計算)。
| 地方 | 県数 | 出生率 平均 | 標準偏差 |
|---|---|---|---|
| 北海道 | 1 | 1.06 | — |
| 東北 | 6 | 1.165 | 0.067 |
| 関東 | 7 | 1.151 | 0.084 |
| 中部 | 9 | 1.321 | 0.066 |
| 近畿 | 7 | 1.257 | 0.092 |
| 中国 | 5 | 1.390 | 0.063 |
| 四国 | 4 | 1.343 | 0.046 |
| 九州沖縄 | 8 | 1.455 | 0.098 |
💬 読み方: F(7,39)=13.34 で p は 10⁻⁸ 台と非常に小さく、 「少なくとも 1 組の地方間で出生率が違う」と言えます。 ただし F の有意は全体の差にすぎないので、 Tukey HSD で「九州沖縄 vs 関東」「九州沖縄 vs 東北」といった具体的にどのペアかを特定します。 効果量 η²=0.71 は非常に大きく、 統計的にも実質的にも顕著な地方差です。 なお北海道は 1 県のみ(n=1)で群内分散が定義できないため、 平均差の寄与は評価できても分散の議論からは外れる点に注意(不均衡データの一例)。 等分散(Levene p=0.93)・ノンパラ(Kruskal–Wallis)の双方でも結論が揃うため、 前提の崩れによる誤りの心配は小さい実測例です。
論文・記事に登場する用語のリンクで該当箇所へジャンプ: