← 授業トップへ戻る


本日の目標

  1. 対応のあるデータと対応のないデータの違いを説明できる
  2. 対応のあるt検定を Rで実行し,解釈できる
  3. ワイド形式・ロング形式を変換できる(pivot_longer() / pivot_wider())
  4. 発展:対応を取るとなぜ検出力が上がるのかを,式で説明できる

準備:パッケージの読み込み

この回のコードは,すべて tidyverse を読み込んだ状態で動きます。 RStudioを起動したら,まずこれを実行してください。

library(tidyverse)

毎回必要です。読み込まないまま filter() や ggplot() を使うと, 「関数が見つかりません」というエラーになります。


1. 対応のある vs. 対応のないデータ

対応のないデータ(独立2標本)→ 第10回で学習済み

  • A群と B群が異なる人で構成される
  • 例:男性グループ vs. 女性グループの試験点数

対応のあるデータ(繰り返し測定)

  • 同じ人から2回以上測定した(または対になったペアがある)
  • 例:同じ人の事前と事後の点数
  • 例:双子の左右の腕の測定値

対応あり vs. なしの判断

対応あり 対応なし
同じ人を前後で測定 別の人を比較
マッチドペア(年齢・性別を一致させたペア) 無作為に2群に割り当て

2. 例:介入前後の睡眠時間

# 架空データ:睡眠改善プログラム前後の睡眠時間(時間)
set.seed(42)
n <- 15
sleep_data <- tibble(
  id     = 1:n,
  before = round(rnorm(n, mean = 6.0, sd = 0.8), 1),
  after  = round(before + rnorm(n, mean = 0.8, sd = 0.5), 1)
)
sleep_data

set.seed(42) は何をしているか。 rnorm() は毎回ちがう乱数を返します。そのままでは実行するたびに数値が変わり, 手元の結果と資料の結果が一致しません。 set.seed() で乱数の出発点を固定しておくと,誰がいつ実行しても同じ数値になります。 カッコの中の数字(ここでは42)は何でもよく,「同じ数字なら同じ結果」という意味だけを持ちます。

上のコードを set.seed(42) の行から実行すれば,この資料とまったく同じ表が出ます。 違う数値が出た人は,set.seed() の行を実行し忘れています。 第12回の習熟度確認テストでも使うので,ここで覚えてください。

# 基本統計量
sleep_data |>
  summarise(
    mean_before = mean(before),
    sd_before   = sd(before),
    mean_after  = mean(after),
    sd_after    = sd(after),
    mean_diff   = mean(after - before)
  )

3. データ構造の変換

ワイド形式(Wide)

現在の sleep_data のような形式:各個人が1行,時点が別の列

sleep_data

ロング形式(Long)

分析・グラフ作成でよく使う形式:測定値と時点が別の列

# pivot_longer でワイド → ロングに変換
sleep_long <- sleep_data |>
  pivot_longer(
    cols      = c(before, after),   # 変換する列
    names_to  = "time",             # 時点を入れる新しい列名
    values_to = "sleep"             # 値を入れる新しい列名
  ) |>
  mutate(time = factor(time, levels = c("before", "after"),
                       labels = c("プログラム前", "プログラム後")))
sleep_long
# 可視化(対応あり:同じ人をつなぐ線を引く)
ggplot(sleep_long, aes(x = time, y = sleep, group = id)) +
  geom_line(alpha = 0.4, color = "gray60") +
  geom_point(aes(color = time), size = 3, alpha = 0.8) +
  stat_summary(aes(group = 1), fun = mean, geom = "line",
               linewidth = 1.5, color = "steelblue", linetype = "dashed") +
  scale_color_brewer(palette = "Dark2") +
  labs(title = "睡眠改善プログラム前後の睡眠時間",
       x = "時点", y = "睡眠時間(時間)", color = "時点") +
  theme_bw() +
  theme(legend.position = "none")


4. 対応のあるt検定の実行

# 方法1:paired = TRUE を指定
t.test(sleep_data$after, sleep_data$before, paired = TRUE)
## 
##  Paired t-test
## 
## data:  sleep_data$after and sleep_data$before
## t = 3.6211, df = 14, p-value = 0.00278
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
##  0.2554933 0.9978400
## sample estimates:
## mean difference 
##       0.6266667
# 方法2:差分ベクトルを作って1標本t検定として実行(同じ結果)
diff_sleep <- sleep_data$after - sleep_data$before
t.test(diff_sleep, mu = 0)
## 
##  One Sample t-test
## 
## data:  diff_sleep
## t = 3.6211, df = 14, p-value = 0.00278
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
##  0.2554933 0.9978400
## sample estimates:
## mean of x 
## 0.6266667

結果の読み方

    Paired t-test

data:  sleep_data$after and sleep_data$before
t = 3.6211, df = 14, p-value = 0.00278
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
 0.2554933 0.9978400
sample estimates:
mean difference
      0.6266667
項目 意味
t t統計量(差の平均 ÷ 差の標準誤差)
df 自由度(\(n - 1 = 14\)。\(2n\) ではない)
p-value p値(< 0.05 で有意)
95% CI 差の95%信頼区間(0を含まなければ有意)
mean difference 差の平均(= after の平均 − before の平均)

第10回の出力と見比べてください。 独立2標本では各群の平均が2つ出ましたが, 対応ありでは mean difference が1つだけです。 分析しているのが「2つの群」ではなく「15個の差」だからです(導出①)。

導出①:対応のある\(t\)検定は,新しい検定ではない

上の2つのコードが同じ結果を返したことに注目してください。paired = TRUE を指定した検定と,差を取ってから1標本の検定をした結果はまったく同じです。偶然ではありません。対応のある\(t\)検定は,定義からしてそういうものだからです。

各個体 \(i\) について,2回の測定の差を作ります。

\[d_i = x_i - y_i \quad (i = 1, \dots, n)\]

この \(d_1, \dots, d_n\) を,ただの \(n\) 個のデータとして扱います。「変化がなかった」という帰無仮説は,この差の母平均が0だという主張

\[H_0 : \mu_d = 0\]

に翻訳されます。あとは第10回とまったく同じで,標本平均をその標準誤差で割るだけです。

\[t = \frac{\bar{d}}{SE_{\bar{d}}} = \frac{\bar{d}}{s_d / \sqrt{n}}\]

数値で確かめる:この睡眠データでは \(\bar{d} = 0.6267\),\(s_d = 0.6703\),\(n = 15\) です。\(SE = 0.6703/\sqrt{15} = 0.1731\) なので \(t = 0.6267/0.1731 = 3.6211\),自由度は \(n-1 = 14\),\(p = 0.0028\) となります。上の t.test(..., paired = TRUE) の出力と一致します。

2つの測定値の対を,1つの差に畳んでしまう。これが「対応を取る」という操作の中身です。\(n\) 組の対から \(n\) 個の差が作られるので,分析に使うデータの個数は \(2n\) ではなく \(n\) になります。

使った数学:引き算と,第10回の1標本\(t\)検定。新しい道具は何も要りません。

前提も「差」について考えます。 必要なのは,前後それぞれの値が正規分布することではなく,差 \(d_i\) が正規分布することです。 差を1標本として扱っているので,前提も1標本t検定のものがそのまま当てはまります。


5. 効果量:Cohenの d(対応あり)

対応ありの場合,効果量は差の平均を,差の標準偏差で割って求めます。

\[d_z = \frac{\bar{d}}{s_d}\]

d_paired <- mean(diff_sleep) / sd(diff_sleep)
round(d_paired, 2)
## [1] 0.93

\(d_z = 0.93\) ですから,第10回の目安(0.2=小/0.5=中/0.8=大)では大きい効果です。

第10回の d とは分母が違います。 独立2標本では2群のばらつき(プールした標準偏差)で割りましたが, 対応ありでは差のばらつき \(s_d\) で割ります。 差のばらつきは \(-2\mathrm{Cov}\) のぶん小さい(導出②)ので, 同じデータでも対応ありの \(d\) のほうが大きく出ます。 実際このデータを独立2標本流に計算すると \(d = 0.64\) で,\(d_z = 0.93\) とはかなり違います。

どちらも「Cohenの d」と呼ばれるため混同されがちです。 区別するため,対応ありのほうを \(d_z\) と書くことがあります。 論文で読むときは,分母が何かを確かめてください。


6. 独立t検定との比較

同じデータを誤って「対応なし」で分析すると:

# 対応なし(誤った分析)
t.test(sleep_data$after, sleep_data$before, paired = FALSE)
## 
##  Welch Two Sample t-test
## 
## data:  sleep_data$after and sleep_data$before
## t = 1.75, df = 25.587, p-value = 0.09211
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -0.1099958  1.3633291
## sample estimates:
## mean of x mean of y 
##  7.000000  6.373333

paired = FALSE にすると,第10回と同じWelch法の独立2標本t検定になります (出力の1行目が Welch Two Sample t-test に変わっていることを確認してください)。

同じデータなのに,結論が変わりました。

分析の仕方 \(t\) \(df\) \(p\) 判定
対応あり(正しい) 3.62 14 0.0028 有意
対応なし(誤り) 1.75 25.59 0.0921 有意でない

対応のある分析の方が一般的に検出力が高い(個人差を除去できるため)。 データ構造を正しく理解して,適切な分析を選ぶことが重要。

導出②:なぜ対応を取ると有意になりやすいのか ―― \(-2\mathrm{Cov}\) の効き目

上の2つの結果を見比べてください。まったく同じデータなのに,対応のある検定では \(p = 0.0028\),対応なしでは \(p = 0.092\) です。片方は有意,もう片方は有意になりません。何が起きたのでしょうか。

鍵は差の分散です。2つの確率変数の差の分散は,一般に次の式で書けます。

\[\mathrm{Var}[X - Y] = \mathrm{Var}[X] + \mathrm{Var}[Y] - 2\,\mathrm{Cov}[X, Y]\]

第10回では2群が独立だったので \(\mathrm{Cov} = 0\) となり,この第3項が消えていました。だから分散が単純な足し算になったのです。

ところが同じ人を2回測ったデータでは,よく眠る人は前も後もよく眠ります。2つの測定は正に相関し,\(\mathrm{Cov}[X, Y] > 0\) になります。すると第3項が引き算として効いて,差の分散は足し算より小さくなります。

数値で確かめる:この睡眠データでは \(\mathrm{Var}[\text{前}] = 0.6664\),\(\mathrm{Var}[\text{後}] = 1.2571\),\(\mathrm{Cov} = 0.7371\)(相関 \(r = 0.805\))です。

\[0.6664 + 1.2571 - 2 \times 0.7371 = 0.4492\]

実際に var(after - before) を計算しても \(0.4492\) で,ぴったり一致します。もし2つが独立だったなら \(0.6664 + 1.2571 = 1.9235\) で,4倍以上の大きさでした。

標準誤差で比べると,違いは歴然です。

分析の仕方 分母の \(SE\) \(t\) \(p\)
対応あり(正しい) 0.1731 3.62 0.0028
対応なし(誤り) 0.3581 1.75 0.092

分母が \(0.483\) 倍に縮むので,\(t\) はそのぶん大きくなります。

個人差を「除去する」とは,この \(-2\mathrm{Cov}\) のことです。 対応があるのに独立2標本の検定を使うと,せっかくの共分散の情報を捨てることになります。分母が無駄に大きいまま検定するので,本当にある効果を見逃しやすくなります。

使った数学:分散の加法性の一般形(共分散の項まで含めたもの)。第10回で「2群が独立なら」と断っていたのは,この項を落とすためでした。

導出③:自由度は減る。それでも対応を取るほうが得なのはなぜか

対応を取ると,代償もあります。自由度が減るのです。

分析の仕方 使うデータの個数 自由度
独立2標本(§6の出力・Welch法) \(n_1 + n_2 = 30\) \(25.59\)
対応あり \(n = 15\)(差の個数) \(n - 1 = 14\)

等分散を仮定するStudent法なら,自由度はちょうど \(n_1 + n_2 - 2 = 28\) です。 §6では第10回の方針どおりWelch法を使ったので,\(25.59\) という小数になっています。

自由度が小さいほど\(t\)分布の裾は重くなり,有意と判定されるための臨界値は大きくなります。つまり不利です。

それでも対応を取るほうが得なのは,分母の縮小がこの不利を上回るからです。臨界値は \(2.0571\)(自由度25.59)から \(2.1448\)(自由度14)へ \(4.3\%\) 大きくなるだけですが,分母のほうは \(0.483\) 倍まで縮みます。\(t\) が倍以上になる効果のほうが,はるかに大きいのです。

ただし,これは相関が正のときの話です。式に戻って考えます。

\[\mathrm{Var}[X - Y] = \mathrm{Var}[X] + \mathrm{Var}[Y] - 2\,\mathrm{Cov}[X, Y]\]

  • \(\mathrm{Cov} > 0\)(正の相関):第3項が引き算として効き,分散は縮む。対応を取るほうが得
  • \(\mathrm{Cov} = 0\)(無相関):第3項が消え,分散は独立のときと同じ。自由度が減るぶん損
  • \(\mathrm{Cov} < 0\)(負の相関):第3項が足し算になり,分散はかえって膨らむ。はっきり損

前後の測定が正に相関するのは,多くの場合そうなりやすいというだけで,保証された性質ではありません。

対応を取るかどうかは,データの取り方が決めます。 同じ個体を2回測ったなら対応あり,別々の個体どうしなら対応なしです。相関の強さを見てから有利なほうを選ぶ,という性質のものではありません。検定の方法は,データを集めた設計によって決まります。

使った数学:自由度と\(t\)分布の臨界値,そして導出②の分散の式。


7. pivot_wider:ロング → ワイドの変換

# ロング → ワイドに戻す
sleep_long |>
  pivot_wider(
    id_cols    = id,
    names_from  = time,
    values_from = sleep
  )

列名が before / after に戻らないことに注意。 §3で factor(..., labels = c("プログラム前", "プログラム後")) とラベルを付け替えたので, names_from = time はそのラベルを列名にします。 日本語の列名は df$プログラム前 のようには書けず, df$\プログラム前`` とバッククォートで囲む必要があります。 あとで分析に使う列は,英数字の名前のままにしておくほうが楽です。


演習

演習1:データ構造の判断

以下のデータはそれぞれ「対応あり」か「対応なし」か判断してください。

  1. 同じ学生が「春学期」と「秋学期」に受けたテストの点数
  2. 男子学生20人と女子学生20人の体重を比較
  3. 双子(一卵性)の知能指数(IQ)の比較

演習2:ワイド→ロング変換

以下のデータを pivot_longer() でロング形式に変換してください。

# 架空データ:3名の授業前後の不安度スコア
anxiety_data <- tibble(
  id     = c("A", "B", "C"),
  pre    = c(7, 5, 8),
  post   = c(4, 3, 6)
)
anxiety_data
anxiety_long <- anxiety_data |>
  pivot_longer(
    cols      = c(pre, post),
    names_to  = "___",
    values_to = "___"
  )

演習3:対応のあるt検定

演習2のデータ(anxiety_data)を使って, 授業前後で不安度スコアが有意に変化したか検定してください。

t.test(anxiety_data$post, anxiety_data$pre, paired = ___)

演習4:結果の記述

演習3の結果を1〜2文でまとめてください。


本日のまとめ

  • 対応のあるデータ(反復測定・ペア)には paired t検定 を使う
  • t.test(..., paired = TRUE) で実行
  • pivot_longer() でワイド→ロング形式の変換が簡単にできる
  • データ構造を正しく理解することが分析の第一歩

宿題・予習

  • 演習3〜4を仕上げておく
  • 次回は 習熟度確認テスト(第2回) です
  • 第8〜11回の推測統計(相関・カイ二乗・t検定)を総復習しておく

← 授業トップへ戻る