pivot_longer() /
pivot_wider())この回のコードは,すべて tidyverse
を読み込んだ状態で動きます。
RStudioを起動したら,まずこれを実行してください。
毎回必要です。読み込まないまま
filter()やggplot()を使うと, 「関数が見つかりません」というエラーになります。
| 対応あり | 対応なし |
|---|---|
| 同じ人を前後で測定 | 別の人を比較 |
| マッチドペア(年齢・性別を一致させたペア) | 無作為に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)
)分析・グラフ作成でよく使う形式:測定値と時点が別の列
# 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")##
## 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個の差」だからです(導出①)。
上の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検定のものがそのまま当てはまります。
対応ありの場合,効果量は差の平均を,差の標準偏差で割って求めます。
\[d_z = \frac{\bar{d}}{s_d}\]
## [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\) と書くことがあります。 論文で読むときは,分母が何かを確かめてください。
同じデータを誤って「対応なし」で分析すると:
##
## 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つの結果を見比べてください。まったく同じデータなのに,対応のある検定では \(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]\]
前後の測定が正に相関するのは,多くの場合そうなりやすいというだけで,保証された性質ではありません。
対応を取るかどうかは,データの取り方が決めます。 同じ個体を2回測ったなら対応あり,別々の個体どうしなら対応なしです。相関の強さを見てから有利なほうを選ぶ,という性質のものではありません。検定の方法は,データを集めた設計によって決まります。
使った数学:自由度と\(t\)分布の臨界値,そして導出②の分散の式。
列名が
before/afterに戻らないことに注意。 §3でfactor(..., labels = c("プログラム前", "プログラム後"))とラベルを付け替えたので,names_from = timeはそのラベルを列名にします。 日本語の列名はdf$プログラム前のようには書けず,df$\プログラム前`` とバッククォートで囲む必要があります。 あとで分析に使う列は,英数字の名前のままにしておくほうが楽です。
以下のデータはそれぞれ「対応あり」か「対応なし」か判断してください。
以下のデータを pivot_longer()
でロング形式に変換してください。
# 架空データ:3名の授業前後の不安度スコア
anxiety_data <- tibble(
id = c("A", "B", "C"),
pre = c(7, 5, 8),
post = c(4, 3, 6)
)
anxiety_data演習2のデータ(anxiety_data)を使って,
授業前後で不安度スコアが有意に変化したか検定してください。
演習3の結果を1〜2文でまとめてください。
t.test(..., paired = TRUE) で実行pivot_longer()
でワイド→ロング形式の変換が簡単にできる