「t test」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「t test」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「t test の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
平均の差を比べるための道具です。
2つのグループに意味のある差があるか調べます。
部活の練習前と後で成績が変わったか調べます。
t検定の種類と使い分けについて読みましょう。
t検定は、 2つの平均値(または1つの平均値とある定値)に統計的な差があるかを判定する仮説検定。 標本サイズが小さい時の標準ツール。
t分布は標準正規分布より「裾が重い」。 n が小さいほど顕著。 n が大きく(df > 30)なると正規分布とほぼ同じ。
| 種類 | 仮説 | 例 |
|---|---|---|
| 1標本t検定 | μ = μ₀ | 「平均は5kgか?」 |
| 対応のあるt検定 | μ_前 = μ_後 | 「ダイエット前後の体重」 |
| 独立2標本t検定 | μ₁ = μ₂ | 「A群とB群の差」 |
🍰 まずはやさしく
データ分析でよく使われる手法です。
平均値の差を正しく判定するために使います。
都道府県ごとのデータの違いを調べます。
定義から実装までの流れを順番に読みましょう。
論文中に 「t検定」として登場する用語。
t検定 とは:2群の平均に差があるか、または平均がある値と異なるかをt分布を使って検定。n小〜中で有効。
本ページでは「t test」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「t test」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
差の大きさをものさしで測るようなものです。
平均の差がばらつきに比べて大きいか調べます。
テストの点数の差が偶然かどうかを考えます。
ばらつきを使って差を評価する方法を読みましょう。
2 つのグループの平均が「10」と「12」だったら差は 2。 これが「意味のある差」かどうかは、 群内のばらつきとの相対関係で決まる。 ばらつきが ±0.5 程度なら 2 は巨大な差、 ばらつきが ±50 なら 2 は誤差の範囲。 t 検定は「平均の差をばらつき (標準誤差) で割って、 ばらつきの何倍離れているかを測る」シンプルな考え方だ。
| 場面 | 平均差 | SE | t = 差/SE | 解釈 |
|---|---|---|---|---|
| A: 安定環境 | 2.0 | 0.5 | 4.0 | 差は明確 |
| B: 普通 | 2.0 | 2.0 | 1.0 | 判定保留 |
| C: ノイジー | 2.0 | 10.0 | 0.2 | 差は誤差 |
→ |t| が大きいほど「差はばらつきよりも遥かに大きい」ことを示し、 偶然で説明できない (= 有意) という結論につながる。 t 分布の表 (両側 α=0.05) では df=30 で t≥2.04 が有意ラインの目安。
🍰 まずはやさしく
計算するための数式のことです。
客観的な数値で差があるかを判定します。
スマホの利用時間の平均を計算して比べます。
t統計量の式と使い分けについて読みましょう。
$$ t = \frac{\bar{x} - \mu_0}{s/\sqrt{n}} \sim t(n-1) $$
$$ t = \frac{\bar{x}_1 - \bar{x}_2}{s_p \sqrt{1/n_1 + 1/n_2}} $$
s_p はプール標準偏差。 自由度 df = n₁ + n₂ − 2。
$$ t = \frac{\bar{x}_1 - \bar{x}_2}{\sqrt{s_1^2/n_1 + s_2^2/n_2}} $$
分散が違うグループ間の比較に適切。 sklearn では scipy.stats.ttest_ind の equal_var=False。
仮定が崩れる場合 → ノンパラメトリック(Wilcoxon、 Mann-Whitney U)を使用。
「t 検定」と一言で言ってもバリエーションがある。 SciPy で使える主要な 4 種類を整理する。
| 種類 | SciPy API | 用途 | 自由度 | 前提 |
|---|---|---|---|---|
| 1 標本 t 検定 | ttest_1samp | 標本平均と仮説値 μ₀ の差 | n − 1 | 正規性 |
| 2 標本 (Student) t | ttest_ind (equal_var=True) | 2 群の平均比較 (等分散) | n₁ + n₂ − 2 | 正規性・等分散・独立 |
| Welch t 検定 | ttest_ind (equal_var=False) | 2 群の平均比較 (不等分散) | Welch-Satterthwaite | 正規性・独立 |
| 対応のある t 検定 | ttest_rel | 前後比較・ペア比較 | n − 1 | 差の正規性 |
いずれも基本形は t = (推定したい差) / (差の標準誤差 SE)。 数式を言葉で読み解くと、 「平均の差が、 偶然のばらつきの何倍離れているか」を測る指標。
t = (x̄ − μ₀) / (s/√n) — 母平均 μ₀ からのズレを測るt = (x̄₁ − x̄₂) / (s_p · √(1/n₁ + 1/n₂)) — プール分散 s_p を共通分散と仮定t = (x̄₁ − x̄₂) / √(s₁²/n₁ + s₂²/n₂) — 別々の分散を使う、 自由度は Welch-Satterthwaitet = d̄ / (s_d/√n) — d = x − y の差を 1 標本 t で扱うWelch 自由度は df = (s₁²/n₁ + s₂²/n₂)² / [(s₁²/n₁)²/(n₁−1) + (s₂²/n₂)²/(n₂−1)]。 一般に非整数。 等分散かつ n₁=n₂ なら Student の自由度と一致する。
2 つのグループの平均・標準偏差・標本サイズをスライダーで動かすと、 分布の重なり・t 統計量・自由度・p 値がリアルタイムに変わります。 平均差が大きい/ばらつきが小さい/n が大きいほど |t| が大きく p 値が小さくなる関係を体感してください。 初期値は本ページの実例(SSDSE-B-2026 の東日本 vs 西日本の年平均気温)です。
この計算機は t 分布の CDF を正則不完全ベータ関数 I_x(df/2, 1/2)(x=df/(df+t²))で厳密に評価し、 p 値・棄却域を求めています。 SciPy の ttest_ind / ttest_rel と一致します。 初期値では Welch で t≈4.1・p≈0.0002・d≈1.2 となり、 上の SSDSE 実例と整合します。
スライダーで数字が動くとき、 裏では次の考え方が働いています。 まとめて押さえておきましょう。
William Sealy Gosset(1876-1937)は Guinness ビール工場の品質管理担当。 少サンプルでビール品質を判定する手法として t分布を発見(1908)。 会社の機密保護のため「Student」のペンネームで発表。
$$ d = \frac{\bar{x}_1 - \bar{x}_2}{s_p} $$
d=0.5、 α=0.05、 検出力 0.8 で各群 n=64 必要。 d=0.2 では各群 n=394 必要。
SSDSE-B-2026(2023 年)の年平均気温 B4101 で、 東日本(24 都道県)と西日本(23 府県)の平均が有意に違うかをWelch のt検定で確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d2023 = df[df['SSDSE-B-2026'] == 2023] # 2023 年の 47 都道府県 east_pref = ['北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県', '栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県','新潟県','富山県', '石川県','福井県','山梨県','長野県','岐阜県','静岡県','愛知県','三重県'] temp = d2023.set_index('Prefecture')['B4101'] # 年平均気温(℃) east = temp[temp.index.isin(east_pref)] west = temp[~temp.index.isin(east_pref)] print(f'東: n={len(east)}, mean={east.mean():.1f}, sd={east.std():.1f}') print(f'西: n={len(west)}, mean={west.mean():.1f}, sd={west.std():.1f}') |
典型結果:東 n=24, mean=15.8, sd=2.0 / 西 n=23, mean=17.9, sd=1.5 — 平均差 約2.1℃。
1 2 3 4 5 6 7 8 9 10 11 | from scipy import stats t, p = stats.ttest_ind(west, east, equal_var=False) # Welch print(f't = {t:.3f}, p = {p:.4f}') # 典型値: t ≈ 4.13, p ≈ 0.0002 → 統計的に有意 # 効果量 Cohen's d import numpy as np pooled_sd = np.sqrt(((len(east)-1)*east.var() + (len(west)-1)*west.var()) / (len(east)+len(west)-2)) d = (west.mean() - east.mean()) / pooled_sd print(f"Cohen's d = {d:.2f}") # 典型値: d ≈ 1.20 → 大きな効果量 |
1 2 3 4 5 6 7 8 | from scipy.stats import t as t_dist diff = west.mean() - east.mean() se = np.sqrt(west.var()/len(west) + east.var()/len(east)) # Welch SE df_welch = (west.var()/len(west) + east.var()/len(east))**2 / \ ((west.var()/len(west))**2/(len(west)-1) + (east.var()/len(east))**2/(len(east)-1)) ci = t_dist.interval(0.95, df=df_welch, loc=diff, scale=se) print(f'平均差 = {diff:.1f}, 95% CI = [{ci[0]:.1f}, {ci[1]:.1f}]') # 典型値: 平均差 = 2.1, 95% CI = [1.1, 3.1] → 0 を含まないので有意 |
「西日本の年平均気温が東日本より約 2.1℃ 高い(95% CI [1.1, 3.1])」と区間で報告するのが現代的。 単に p=0.0002 だけでは情報不足。
このコードでやること: SSDSE-B-2026 の 47 県を「3 大都市圏 (東京・大阪・名古屋圏 9 都府県)」と「それ以外の 38 県」に分け、 4 種類の t 検定で平均人口の差を検証する。
📥 入力データ (SSDSE-B-2026 抜粋):
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 | import pandas as pd import numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) y2023 = df[df['SSDSE-B-2026'] == 2023] # 2023 年の 47 都道府県 pop = y2023.set_index('Prefecture')['A1101'] / 1e4 # 総人口(万人) metro = ['東京都','神奈川県','埼玉県','千葉県','大阪府','京都府','兵庫県','奈良県','愛知県'] g1 = pop[pop.index.isin(metro)].values # 都市圏 9 都府県 g2 = pop[~pop.index.isin(metro)].values # 地方 38 県 print(f'都市圏 n={len(g1)} mean={g1.mean():.1f} std={g1.std(ddof=1):.1f}') print(f'地方 n={len(g2)} mean={g2.mean():.1f} std={g2.std(ddof=1):.1f}') print() # ① 1 標本 t — 都市圏の平均が「全国平均 264.6」と等しいか t1, p1 = stats.ttest_1samp(g1, popmean=264.6) print(f'① 1-samp t={t1:>7.3f} p={p1:.4f} df={len(g1)-1}') # ② Student t (等分散) t2, p2 = stats.ttest_ind(g1, g2, equal_var=True) print(f'② Student t={t2:>7.3f} p={p2:.4f} df={len(g1)+len(g2)-2}') # ③ Welch t (不等分散) res3 = stats.ttest_ind(g1, g2, equal_var=False) print(f'③ Welch t={res3.statistic:>7.3f} p={res3.pvalue:.4f} df={res3.df:.2f}') # ④ 対応あり t — 人口上位 9 県と下位 9 県で擬似ペアを作る pop_sorted = pop.sort_values(ascending=False).values top9 = pop_sorted[:9] bot9 = pop_sorted[-9:] t4, p4 = stats.ttest_rel(top9, bot9) print(f'④ Paired t={t4:>7.3f} p={p4:.4f} df={len(top9)-1}') |
📤 実行例:
💬 結果の読み方: ① 都市圏 9 県の平均 692.7 万人は全国平均 264.6 から有意にズレている (p=0.009)。 ② Student t は p<0.001 で大都市圏 vs 地方の平均差が極めて大きいことを示す。 ただし都市圏分散 (377.9²) と地方分散 (106.2²) は約 13 倍違い、 Welch を使うべき場面。 ③ Welch t では p=0.003、 自由度 8.3 と地方の n=38 から大きく縮む (これが Welch-Satterthwaite の効果)。 ④ 対応あり t は人口上位 9 県と下位 9 県をペア化した擬似ペア検定で、 p=0.0001。 同じデータでも検定の選び方で p 値の桁が変わる典型例。
サンプルサイズが大きいと、 実質的に意味のない小さな差でも p<0.05 になる。 そこで「差の大きさそのもの」を測る効果量 (effect size) を併記するのが現代統計の標準。 t 検定では Cohen's d = (x̄₁ − x̄₂) / s_pooled を使う。
数式を言葉で読み解くと、 「平均の差を共通標準偏差で割った値」。 単位がなくなり、 「標準偏差の何倍離れているか」を示す。 Cohen の経験則では d=0.2 が小、 0.5 が中、 0.8 が大とされる。 d=1.0 を超えれば「ほとんど分布が重ならない」レベル。
このコードでやること: SSDSE 都市圏 vs 地方の人口差について、 Cohen's d (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 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) y2023 = df[df['SSDSE-B-2026'] == 2023] pop = y2023.set_index('Prefecture')['A1101'] / 1e4 # 総人口(万人) metro = ['東京都','神奈川県','埼玉県','千葉県','大阪府','京都府','兵庫県','奈良県','愛知県'] g1 = pop[pop.index.isin(metro)].values g2 = pop[~pop.index.isin(metro)].values n1, n2 = len(g1), len(g2) m1, m2 = g1.mean(), g2.mean() s1, s2 = g1.std(ddof=1), g2.std(ddof=1) # ① 古典 Cohen's d (プール標準偏差、 n で割る) s_pooled_cohen = np.sqrt(((n1-1)*s1**2 + (n2-1)*s2**2) / (n1+n2)) d_cohen = (m1 - m2) / s_pooled_cohen # ② Hedges' g (Cohen を不偏補正) s_pooled_hedges = np.sqrt(((n1-1)*s1**2 + (n2-1)*s2**2) / (n1+n2-2)) d_hedges_raw = (m1 - m2) / s_pooled_hedges J = 1 - 3 / (4*(n1+n2) - 9) # 補正因子 g_hedges = J * d_hedges_raw # ③ Glass の Δ (対照群の標準偏差だけ使う、 介入研究で有用) glass_delta = (m1 - m2) / s2 print(f"Cohen's d (古典) = {d_cohen:.3f}") print(f"Hedges' g (不偏補正) = {g_hedges:.3f} (J = {J:.4f})") print(f"Glass's Δ = {glass_delta:.3f}") print() print('Cohen の経験則: 0.2 = 小、 0.5 = 中、 0.8 = 大、 ≥1.0 = 非常に大') |
📤 実行例:
💬 結果の読み方: Cohen's d = 2.91 は「非常に大」を遥かに超える巨大効果。 都市圏と地方は「標準偏差の約 3 倍離れた」分布。 ほぼ重ならない。 Hedges' g は n が中程度のとき Cohen の上振れバイアスを補正したもの (n→∞ で一致)。 Glass's Δ = 4.99 は地方分散だけで割るため、 「都市圏が地方比較で何 σ 外れか」を示し、 介入研究 (新薬の効果、 教育介入) で対照群分散で測る場面に向く。
検定の前に「いくらのサンプルが必要か」を計算するのが 事前検出力分析 (a priori power analysis)。 検出力 (power = 1 − β) は「真の差があるときに有意と判定する確率」。 通常 80% を目標にする。
このコードでやること: 効果量 d=0.5 (中程度) を α=0.05 で検出するのに必要なサンプルサイズを 2 群 t 検定で求める。 また SSDSE データの実 d=2.91 で n=9 の小サンプルでも検出力が十分かを確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from statsmodels.stats.power import TTestIndPower analysis = TTestIndPower() # ① d=0.5, α=0.05, power=0.80 のとき必要な n n_needed = analysis.solve_power(effect_size=0.5, alpha=0.05, power=0.80, alternative='two-sided') print(f'd=0.5 を power=0.80 で検出するのに必要な n (各群): {n_needed:.1f}') # ② d=0.2, 0.5, 0.8, 1.0, 2.0 で必要な n を表に for d in [0.2, 0.5, 0.8, 1.0, 2.0]: n = analysis.solve_power(effect_size=d, alpha=0.05, power=0.80, alternative='two-sided') print(f' d={d:>4} → n per group = {n:>7.1f}') # ③ SSDSE 実 d=2.91 の場合の n=9 検出力 power_actual = analysis.solve_power(effect_size=2.91, nobs1=9, alpha=0.05, alternative='two-sided', ratio=38/9) print(f'\nSSDSE 実 d=2.91, n1=9, n2=38 → power = {power_actual:.4f}') |
📤 実行例:
💬 結果の読み方: 中効果 d=0.5 を 80% の確率で検出するには各群 64 名必要。 小効果 d=0.2 だと 394 名必要 — 大規模調査が必要な理由。 SSDSE 都市 vs 地方は実 d=2.91 と巨大効果のため、 n=9 でも検出力ほぼ 100%。 逆に言うと「サンプルが多すぎると小さな差でも有意になる」が、 効果量と併記すれば「有意だが効果は小」と正しく結論できる。 これが効果量を併記する最大の理由。
合成データで H₀: μ=50 を t 検定する。
1 2 3 4 5 | from scipy import stats import numpy as np x = np.array([52, 55, 48, 53, 42]) t, p = stats.ttest_1samp(x, popmean=50) print(f"t: {t:.3f}, p: {p:.3f}") |
💬 手計算 (Step 2) t=0 と Python 出力が完全一致。
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 | # ── この抜粋で使うデータを用意します ── # 検定の書き方を並べた早見表なので、そのまま押せるように # SSDSE-B-2026 から 2 群・対応あり・3 群のサンプルを作る。 import numpy as np import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年の 47 都道府県 temp = df['B4101'].astype(float).values # 年平均気温 group1 = g1 = temp[:24] # 北海道〜静岡 group2 = g2 = temp[24:] # 愛知〜沖縄 group3 = g3 = df['A4103'].astype(float).values # 合計特殊出生率 data = temp # 1 標本検定用 mu0 = float(temp.mean()) # 対応のあるデータ(同じ 47 県の 2012 年と 2023 年) _all = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) before = _all[_all['SSDSE-B-2026'] == 2012]['A1101'].astype(float).values after = _all[_all['SSDSE-B-2026'] == 2023]['A1101'].astype(float).values cond1, cond2, cond3 = before, after, (before + after) / 2 x = df['A1101'].astype(float).values y = df['A4101'].astype(float).values observed = np.array([[20, 15], [12, 18]]) # クロス集計の例 from scipy import stats # 1標本t検定 t, p = stats.ttest_1samp(data, popmean=50) # 独立2標本(等分散) t, p = stats.ttest_ind(group1, group2) # Welch t検定(不等分散) t, p = stats.ttest_ind(group1, group2, equal_var=False) # 対応のあるt検定 t, p = stats.ttest_rel(before, after) # 効果量(Cohen's d) d = (np.mean(group1) - np.mean(group2)) / np.sqrt((np.var(group1, ddof=1) + np.var(group2, ddof=1))/2) print(f't={t:.3f}, p={p:.4f}, Cohen\'s d={d:.3f}') |
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 | # ── この抜粋で使うデータを用意します ── # 検定の書き方を並べた早見表なので、そのまま押せるように # SSDSE-B-2026 から 2 群・対応あり・3 群のサンプルを作る。 import numpy as np import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年の 47 都道府県 temp = df['B4101'].astype(float).values # 年平均気温 group1 = g1 = temp[:24] # 北海道〜静岡 group2 = g2 = temp[24:] # 愛知〜沖縄 group3 = g3 = df['A4103'].astype(float).values # 合計特殊出生率 data = temp # 1 標本検定用 mu0 = float(temp.mean()) # 対応のあるデータ(同じ 47 県の 2012 年と 2023 年) _all = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) before = _all[_all['SSDSE-B-2026'] == 2012]['A1101'].astype(float).values after = _all[_all['SSDSE-B-2026'] == 2023]['A1101'].astype(float).values cond1, cond2, cond3 = before, after, (before + after) / 2 x = df['A1101'].astype(float).values y = df['A4101'].astype(float).values observed = np.array([[20, 15], [12, 18]]) # クロス集計の例 from scipy import stats grp1, grp2 = group1, group2 pre, post = before, after # 1標本(母平均 = μ0 の検定) t, p = stats.ttest_1samp(data, popmean=250) # 独立2標本(等分散仮定) t, p = stats.ttest_ind(grp1, grp2, equal_var=True) # Welch(等分散仮定しない、 推奨デフォルト) t, p = stats.ttest_ind(grp1, grp2, equal_var=False) # 対応のあるt検定 t, p = stats.ttest_rel(pre, post) |
1 2 3 4 5 6 7 | from statsmodels.stats.weightstats import ttest_ind, DescrStatsW t, p, df_resid = ttest_ind(west, east, usevar='unequal') print(f't = {t:.3f}, p = {p:.4f}, df = {df_resid:.1f}') # 信頼区間付き cm = DescrStatsW(west).get_compare(DescrStatsW(east)) print(cm.summary(usevar='unequal')) # 表形式で完結 |
1 2 3 4 5 | import pingouin as pg result = pg.ttest(west, east, correction='auto') print(result) # T dof alternative p-val CI95% cohen-d BF10 power # 一表で必要な情報がすべて出る |
1 2 3 4 | from scipy.stats import mannwhitneyu # 正規性が成立しない時の代替 u, p = mannwhitneyu(west, east, alternative='two-sided') print(f'U = {u}, p = {p:.4f}') |
1 2 3 4 5 6 7 8 9 10 11 12 | import pymc as pm with pm.Model() as model: mu1 = pm.Normal('mu1', mu=250, sigma=100) mu2 = pm.Normal('mu2', mu=250, sigma=100) sd1 = pm.HalfNormal('sd1', sigma=50) sd2 = pm.HalfNormal('sd2', sigma=50) nu = pm.Exponential('nu', 1/30) # 自由度(裾の重さ) pm.StudentT('y1', mu=mu1, sigma=sd1, nu=nu, observed=west) pm.StudentT('y2', mu=mu2, sigma=sd2, nu=nu, observed=east) diff = pm.Deterministic('diff', mu1 - mu2) trace = pm.sample(2000, tune=1000) # diff の事後分布から「差 > 0 の確率」を直接計算可能 |
現代的な統計報告は「p 値 + 効果量 + 平均差の 95% 信頼区間」を 3 点セットで書く。 信頼区間は「真の平均差がこの範囲に 95% の信頼度で存在する」を示し、 ゼロを跨がなければ有意。
このコードでやること: SSDSE 都市圏 vs 地方の平均差について、 Welch t に対応する 95% 信頼区間を SciPy の confidence_interval() で取得し、 p 値・d・CI を同時に出力する。
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 | import pandas as pd import numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) y2023 = df[df['SSDSE-B-2026'] == 2023] pop = y2023.set_index('Prefecture')['A1101'] / 1e4 # 総人口(万人) metro = ['東京都','神奈川県','埼玉県','千葉県','大阪府','京都府','兵庫県','奈良県','愛知県'] g1 = pop[pop.index.isin(metro)].values g2 = pop[~pop.index.isin(metro)].values # Welch t を新 API で res = stats.ttest_ind(g1, g2, equal_var=False) ci = res.confidence_interval(confidence_level=0.95) # 効果量 Cohen's d (古典) n1, n2 = len(g1), len(g2) s_pool = np.sqrt(((n1-1)*g1.var(ddof=1) + (n2-1)*g2.var(ddof=1)) / (n1+n2)) d = (g1.mean() - g2.mean()) / s_pool print(f'平均差 (g1 - g2) = {g1.mean() - g2.mean():.2f} 万人') print(f'95% CI for diff = [{ci.low:.2f}, {ci.high:.2f}]') print(f't = {res.statistic:.3f}, df = {res.df:.2f}, p = {res.pvalue:.4f}') print(f"Cohen's d = {d:.3f}") print() print('=== 報告用文章 (APA 7 風) ===') print(f"都市圏 (M = {g1.mean():.1f}, SD = {g1.std(ddof=1):.1f}) は") print(f"地方 (M = {g2.mean():.1f}, SD = {g2.std(ddof=1):.1f}) より") print(f"有意に人口が多かった (Welch t({res.df:.1f}) = {res.statistic:.2f}, p = {res.pvalue:.3f},") print(f"d = {d:.2f}, 95% CI [{ci.low:.0f}, {ci.high:.0f}] 万人)。") |
📤 実行例:
💬 結果の読み方: 信頼区間 [238, 821] 万人はゼロを含まず、 「真の平均差は約 238 〜 821 万人の間にある」と読める。 p<0.01 と d=2.91 と CI [238,821] を 3 点セットで報告するのが現代の標準。 APA 7 (心理学)、 JAMA (医学)、 経済学トップジャーナルもこの形式を採用している。 SciPy 1.10 以降は res.confidence_interval() で簡潔に取得可能。
t 検定は「2 群の平均差が、 標本誤差だけで説明できる範囲か」を判断する手続き。 ここでは 3 つの概念図で「t 統計量の意味」「分布の形」「有意性 vs 効果量」を視覚的に整理する。
equal_var=False)が常に安全。 Welch は等分散時もほぼ同じ結果で、 不等分散時はより正確。 R では t.test() のデフォルトが Welch、 Python でも 2023年以降 Welch をデフォルトとする論調が主流。 等分散検定(F検定・Levene)の事後判断は多重検定問題を招くため推奨されない。ttest_rel) を使う。 独立t検定を使うと、 個体内の相関を無視するため検出力が大幅に下がる(または逆に偽陽性に)。 「前後の血圧」「兄弟ペア」「同一県の年次比較」などは対応データ。 データ構造を最初に明確化することが大切。α=0.05 で k 個の独立な検定をすると、 少なくとも 1 個で偽陽性が出る確率は 1 − (1−α)^k。 k=10 で 40%、 k=20 で 64%。 これを補正しないと「20 個調べたら 1 個ぐらいは有意になる」という不当な発見を量産してしまう。
数式を言葉で読み解くと、 「補正法は『どんな誤りをどれだけ抑えるか』のトレードオフ調整」。 Bonferroni は最も保守的、 Benjamini-Hochberg は実用的、 Holm は両者の中間。
| 手法 | 補正対象 | 仕組み | 特徴 |
|---|---|---|---|
| Bonferroni | FWER | α / k で各 p を判定 | 最も保守的、 過検出ゼロが目標 |
| Holm | FWER | p 昇順に α/(k − i + 1) | Bonferroni より少し強い検出力 |
| Benjamini-Hochberg | FDR | p 昇順、 q = i·α/k で打ち切り | 遺伝子発現・大規模スクリーニング向け |
このコードでやること: SSDSE 47 県の年平均気温 B4101 を 8 地方ブロックに分け(北海道は n=1 のため除外し 7 ブロック)、 全 21 ペアの t 検定を実行し、 Bonferroni / Holm / BH 補正で有意ペアを比較する。
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 | import pandas as pd import numpy as np from itertools import combinations from scipy import stats from statsmodels.stats.multitest import multipletests df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) y2023 = df[df['SSDSE-B-2026'] == 2023][['Prefecture', 'B4101']].copy() # 年平均気温 region_map = {'北海道':'北海道', '青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北', '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東', '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部', '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿', '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国', '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国', '福岡県':'九州沖縄','佐賀県':'九州沖縄','長崎県':'九州沖縄','熊本県':'九州沖縄','大分県':'九州沖縄','宮崎県':'九州沖縄','鹿児島県':'九州沖縄','沖縄県':'九州沖縄', } y2023['region'] = y2023['Prefecture'].map(region_map) groups = {r: g['B4101'].values for r, g in y2023.groupby('region') if len(g) >= 2} pairs, raw_p = [], [] for a, b in combinations(groups, 2): _, p = stats.ttest_ind(groups[a], groups[b], equal_var=False) pairs.append(f'{a} vs {b}') raw_p.append(p) bonf = multipletests(raw_p, alpha=0.05, method='bonferroni')[1] holm = multipletests(raw_p, alpha=0.05, method='holm')[1] bh = multipletests(raw_p, alpha=0.05, method='fdr_bh')[1] print(f'比較数 k = {len(pairs)}, raw <0.05 = {sum(p<0.05 for p in raw_p)}') print(f'Bonferroni <0.05 = {sum(p<0.05 for p in bonf)}') print(f'Holm <0.05 = {sum(p<0.05 for p in holm)}') print(f'BH (FDR) <0.05 = {sum(p<0.05 for p in bh)}') |
📤 実行例:
💬 結果の読み方: 補正前は 11 ペア有意だが、 Bonferroni では 6 ペア、 Holm でも 6 ペア、 BH (FDR) で 10 ペア。 FWER を抑える Bonferroni / Holm は保守的で有意ペアが減り、 FDR を抑える BH は緩めで多く残る。 「探索的に有望ペアを絞る」段階なら BH、 「論文の主結論として確定的に言いたい」なら Bonferroni / Holm を使う。 補正なしで「11 ペアで有意差」と書くと査読で必ず指摘される。
stats.shapiro) や QQ プロットで確認。 違反時は Mann-Whitney U 検定。equal_var=False で。t検定 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 推測統計 › 検定 › t検定
中心に t検定 を置き、 そこから F検定・χ²検定・正規分布・p値・ノンパラ検定・重回帰 など 計 15 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「t検定」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「t検定」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは t検定 の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → 検定 → t検定 という入れ子の位置を示します。 「検定には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。
t 検定は「2 群の平均比較」の標準ツール。 SSDSE-B-2026 で東日本 (24 都道県) vs 西日本 (23 府県) の年平均気温 B4101 の平均差を Welch's t で比較するワークフローが典型。 各群 n が 20 台と小標本のため、 正規性が崩れていれば Mann-Whitney U に切り替え、 等分散が疑わしければ Welch's t を使う。
「t 統計量 + 自由度 + p 値 + Cohen's d + 95% CI」のセットが現代的な標準報告形式。 SSDSE 分析でも p 値単体は避け、 効果量と信頼区間を併記する。
t 検定は「平均の差」を比較する標準ツールだが、 群の数・対応の有無・正規性・等分散性で適切な検定が変わる。 以下に典型シナリオと推奨検定を示す。
| シナリオ | 重視する観点 | 推奨検定 |
|---|---|---|
| 2 群独立・正規・等分散 | 古典的 t 検定の前提下 | Student の t 検定 (equal_var=True) |
| 2 群独立・等分散不明 / 違う | SSDSE-B-2026 の東/西比較で典型 | Welch の t 検定 (equal_var=False) |
| 対応あり (前後比較・ペア) | 同一個体内の差を見る | 対応のある t 検定 (paired t) |
| 正規性が大きく崩れる / 外れ値 | 分布仮定なしのノンパラ | Mann-Whitney U / Wilcoxon 符号付順位 |
| 3 群以上の平均比較 | 多群同時・多重比較補正 | ANOVA + Tukey HSD / Kruskal-Wallis |
| 分布仮定を一切置きたくない | 分布フリー・再標本化 | 並べ替え検定 / ブートストラップ検定 |
本ページはここまで t 検定を「検定の道具」として解説してきた。 この節では視点を変え、 (1) t 検定は最小の回帰分析である、 (2) SSDSE-B のような都道府県×年のパネルデータに t 検定を当てるときの構造的な罠、 という 2 つの独自角度から掘り下げる。 数値はすべて SSDSE-B-2026 (年平均気温 B4101) から実際に計算した実測値である。
独立 2 標本 t 検定は、 説明変数がダミー変数 1 個だけの単回帰とまったく同じものだ。 「西日本なら 1、 東日本なら 0」というダミー変数 g を作って 気温 = β₀ + β₁·g + ε を最小二乗法で当てはめると、 切片 β₀ は東日本の平均、 係数 β₁ は「平均差そのもの」になり、 β₁ の t 値は Student の t 検定の t 値と完全に一致する。
SSDSE-B-2026 (2023 年、 B4101 年平均気温) での実測値:
| 方法 | 推定量 | t 値 | p 値 (両側) |
|---|---|---|---|
Student t 検定 (ttest_ind, equal_var=True) | 平均差 2.116℃ | 4.106 | 0.000168 |
OLS 回帰 気温 ~ 西日本ダミー の係数 β₁ | β₁ = 2.116℃ | 4.106 | 0.000168 |
| Welch t 検定 (equal_var=False) | 平均差 2.116℃ | 4.130 | 0.000165 |
| OLS + 不均一分散頑健 (HC2) 標準誤差 | β₁ = 2.116℃ | 4.130 | —※ |
※ 2 群ダミーの場合、 HC2 頑健標準誤差は Welch の標準誤差と代数的に一致するため t 値も 4.130 で一致する。 ただし statsmodels の頑健推論は既定で正規分布 (z) を参照するため、 表示される p 値は Welch の t 分布参照とは異なる。 「t 値の一致」が本質。
この見方の利点は 3 つ。 ① 対応のない t・対応のある t・ANOVA・ANCOVA が「一般線形モデルの特殊ケース」として 1 本の軸に載る (対応のある t は「差 d を切片のみで回帰」したもの)。 ② 「等分散仮定を外す」=「頑健標準誤差を使う」という回帰側の常套手段と対応がつき、 Welch が特別な検定ではなく標準誤差の見積もり方の違いだと分かる。 ③ 共変量 (緯度・標高など) を足したくなったら、 検定を乗り換えるのではなく同じ回帰式に列を 1 本足すだけでよい。
SSDSE-B-2026 は 47 都道府県 × 2012–2023 年の 12 年分 = 564 行ある。 「2023 年だけだと n=47 で心もとないから、 全年をプールして n=564 で検定しよう」— これは疑似反復 (pseudo-replication) と呼ばれる典型的な誤りで、 実測するとその危うさがよく分かる。
| データの使い方 | n (東 / 西) | Welch t | p 値 |
|---|---|---|---|
| 2023 年のみ (正しい単位: 県) | 24 / 23 | 4.130 | 1.7×10⁻⁴ |
| 2012–2023 年を全プール (疑似反復) | 288 / 276 | 16.465 | 1.2×10⁻⁴⁹ |
p 値が 45 桁も小さくなったが、 情報が 12 倍に増えたわけではない。 同じ県の気温は年をまたいでほぼ同じ値を繰り返すからだ。 実際、 47 都道府県の B4101 について 2012 年の値と 2023 年の値の相関を取ると r = 0.989。 つまり 564 行の実体は「ほぼ同じ 47 個の値の 12 回コピー」に近く、 t 検定の前提である観測値の独立性が根本から崩れている。 独立でないデータを独立とみなすと SE が過小評価され、 p 値は見かけ上いくらでも小さくできてしまう。
「t 検定=ダミー変数回帰」と理解すると、 発展の道筋が 1 本の階段になる。