データの生成
n_subj <- 100
n_ingroup <- 25
n_outgroup <- 25
items <- data.frame(
#アイテムのIDを1,2,3,...,50とする
item_id = seq_len(n_ingroup + n_outgroup),
#1〜25をingroup, 26〜50をoutgroupとする
category = rep(c("ingroup","outgroup"), c(n_ingroup, n_outgroup)),
#平均0、標準偏差omega_0の正規分布から生成した50個の値を、アイテムごとのランダム切片とする
O_0i = rnorm(n = n_ingroup + n_outgroup, mean = 0, sd = omega_0)
)
#固定効果をコーディング
items$X_i <- recode(items$category, "ingroup" = -0.5, "outgroup" = +0.5)
items[c(1:3,48:50),] %>% kable()
|
item_id
|
category
|
O_0i
|
X_i
|
1
|
1
|
ingroup
|
42.129
|
-0.5
|
2
|
2
|
ingroup
|
-30.798
|
-0.5
|
3
|
3
|
ingroup
|
5.336
|
-0.5
|
48
|
48
|
outgroup
|
36.004
|
0.5
|
49
|
49
|
outgroup
|
-60.000
|
0.5
|
50
|
50
|
outgroup
|
106.865
|
0.5
|
- 被験者の一覧表も生成する
- 先述の通りランダム切片ランダム傾斜に相関を想定する
- 相関のあるデータの生成をやってくれる
faux::rnorm_multi
関数を使う
- 標準インストールされている
MASS
パッケージを使うやり方も書かれているがわかりにくいのでこちらがおすすめ
subjects <- faux::rnorm_multi(
n = n_subj,
mu = 0, #ランダム切片・傾斜の平均はいずれも0
sd = c(tau_0, tau_1), # ランダム切片・傾斜の標準偏差(これによりサンプルも2個組で生成される)
r = rho, #相関係数
varnames = c("T_0s","T_1s") #上記の通り2個組のサンプルが生成されるので、それぞれT_0s(切片)、T_1s(傾斜)と名付ける
)
subjects$subj_id <- seq_len(n_subj) #被験者IDを追加
head(subjects) %>% kable()
T_0s
|
T_1s
|
subj_id
|
-28.60
|
-35.66
|
1
|
37.05
|
54.66
|
2
|
-7.57
|
22.81
|
3
|
131.64
|
15.08
|
4
|
-109.21
|
-31.36
|
5
|
-109.46
|
-57.66
|
6
|
- アイテム・被験者のデータをかけ合わせて全試行のデータフレームを作成
trials <- crossing(subjects, items) %>% #被験者・アイテムの表をかけ合わせて
mutate(e_si = rnorm(nrow(.), mean=0, sd=sigma)) %>% #残差の行を追加(平均0、標準偏差sigmaの正規分布)
select(subj_id, item_id, category, X_i, everything()) #列の順番を整える
trials[order(trials$subj_id),][c(1:3,4998:5000),] %>% kable()
subj_id
|
item_id
|
category
|
X_i
|
T_0s
|
T_1s
|
O_0i
|
e_si
|
1
|
1
|
ingroup
|
-0.5
|
-28.6
|
-35.66
|
42.129
|
25.65
|
1
|
2
|
ingroup
|
-0.5
|
-28.6
|
-35.66
|
-30.798
|
105.80
|
1
|
3
|
ingroup
|
-0.5
|
-28.6
|
-35.66
|
5.336
|
408.16
|
100
|
48
|
outgroup
|
0.5
|
-108.8
|
-22.45
|
36.004
|
-165.84
|
100
|
49
|
outgroup
|
0.5
|
-108.8
|
-22.45
|
-60.000
|
-145.05
|
100
|
50
|
outgroup
|
0.5
|
-108.8
|
-22.45
|
106.865
|
-28.09
|
- これで従属変数(\(RT_{si}\))は自動的に決まる(冒頭の式を参照)
dat_sim <- trials %>% #先ほどのtrialsデータフレームをもとに
mutate(RT = beta_0 + T_0s + O_0i + (beta_1 + T_1s) * X_i + e_si) %>% #モデルに基づいてRTを計算し
select(subj_id, item_id, category, X_i, RT) #必要な行だけを抜き出す
dat_sim[order(dat_sim$subj_id),][c(1:3,4998:5000),] %>% kable()
subj_id
|
item_id
|
category
|
X_i
|
RT
|
1
|
1
|
ingroup
|
-0.5
|
832.0
|
1
|
2
|
ingroup
|
-0.5
|
839.2
|
1
|
3
|
ingroup
|
-0.5
|
1177.7
|
100
|
48
|
outgroup
|
0.5
|
575.2
|
100
|
49
|
outgroup
|
0.5
|
500.0
|
100
|
50
|
outgroup
|
0.5
|
783.8
|
- ここまで見てきたデータ生成過程は、線形混合モデルの仮定そのもの
- 従属変数の分布は、線形モデルによって与えられる
- 我々が観察できるのは、そこから採った一定数のサンプル
- 普段の線形混合モデルによる分析は、そのサンプルから、見えないモデルを推定している
- 今回はモデルを初めに与えているが、以下では、そこから採ったサンプルから、元のモデルが適切に推定できるかを問う
- ここまでのデータ生成過程が実験1回分なので、繰り返せるように1つの関数としてまとめておく
my_sim_data <- function(
#パラメータの値は変更できるように、関数の引数(デフォルト値付き)としておく
n_subj = 100, n_ingroup = 25, n_outgroup = 25,
beta_0 = 800, beta_1 = 50,
omega_0 = 80, tau_0 = 100, tau_1 = 40, rho = 0.2,
sigma = 200
) {
items <- data.frame(
item_id = seq_len(n_ingroup + n_outgroup),
category = rep(c("ingroup","outgroup"), c(n_ingroup, n_outgroup)),
X_i = rep(c(-0.5,+0.5), c(n_ingroup, n_outgroup)),
O_0i = rnorm(n = n_ingroup + n_outgroup, mean = 0, sd = omega_0)
)
subjects <- data.frame(
subj_id = seq_len(n_subj),
faux::rnorm_multi(n = n_subj, mu = 0, sd = c(tau_0, tau_1), r = rho, varnames = c("T_0s","T_1s"))
)
crossing(subjects, items) %>%
mutate(e_si = rnorm(nrow(.), mean=0, sd=sigma),
RT = beta_0 + T_0s + O_0i + (beta_1 + T_1s) * X_i + e_si) %>%
select(subj_id, item_id, category, X_i, RT)
}
生成したデータの分析
mod_sim <- lmer(RT ~ 1 + X_i + (1|item_id) + (1+X_i|subj_id), data = dat_sim)
- 見慣れた出力
- ちゃんと設定したパラメータに近い値が推定されている
summary(mod_sim)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: RT ~ 1 + X_i + (1 | item_id) + (1 + X_i | subj_id)
## Data: dat_sim
##
## REML criterion at convergence: 67441
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.610 -0.656 -0.010 0.660 3.601
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subj_id (Intercept) 9688 98.4
## X_i 1311 36.2 -0.20
## item_id (Intercept) 4235 65.1
## Residual 38977 197.4
## Number of obs: 5000, groups: subj_id, 100; item_id, 50
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 793.1 13.8 124.1 57.64 <2e-16 ***
## X_i 64.8 19.6 51.1 3.31 0.0017 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## X_i -0.027
broom.mixed::tidy(mod_sim) %>% kable()
effect
|
group
|
term
|
estimate
|
std.error
|
statistic
|
df
|
p.value
|
fixed
|
NA
|
(Intercept)
|
793.1486
|
13.76
|
57.64
|
124.13
|
0.0000
|
fixed
|
NA
|
X_i
|
64.7771
|
19.57
|
3.31
|
51.11
|
0.0017
|
ran_pars
|
subj_id
|
sd__(Intercept)
|
98.4269
|
NA
|
NA
|
NA
|
NA
|
ran_pars
|
subj_id
|
cor__(Intercept).X_i
|
-0.2025
|
NA
|
NA
|
NA
|
NA
|
ran_pars
|
subj_id
|
sd__X_i
|
36.2142
|
NA
|
NA
|
NA
|
NA
|
ran_pars
|
item_id
|
sd__(Intercept)
|
65.0752
|
NA
|
NA
|
NA
|
NA
|
ran_pars
|
Residual
|
sd__Observation
|
197.4256
|
NA
|
NA
|
NA
|
NA
|
- 1回実験を行い、分析して結果を出力するまでを関数化
single_run <- function(...) {
#データを生成(引数は...によりsingle_runからmy_sim_dataに渡される)
dat_sim <- my_sim_data(...)
#モデルを推定
mod_sim <- lmer(RT ~ 1 + X_i + (1|item_id) + (1+X_i|subj_id), data = dat_sim)
#モデルを返す
broom.mixed::tidy(mod_sim)
}
purrr
関数で実験を繰り返す
boundary (singular) fit
という警告が出ることがあるが無視してよい
failed to converge
は、数回ならよいがあまり何度も出るなら、設定しているパラメータ数に対してデータ量が少なすぎるので増やしてみる
- 安定した結果を得るには1000回必要だそうだが時間かかるので注意
n_runs <- 100 #繰り返す回数
sims <- purrr::map_df(1:n_runs, ~ single_run(n_subj=100)) #被験者数・アイテム数などを変えたい場合はここの引数をいじる
write_csv(sims, "sims.csv")
sims <- read.csv("sims.csv") %>%
filter(effect == "fixed") %>%
select(term, estimate, std.error, p.value)
head(sims) %>% kable()
term
|
estimate
|
std.error
|
p.value
|
(Intercept)
|
804.348
|
15.58
|
0.0000
|
X_i
|
7.279
|
25.82
|
0.7792
|
(Intercept)
|
792.402
|
16.35
|
0.0000
|
X_i
|
48.595
|
26.26
|
0.0701
|
(Intercept)
|
836.912
|
16.87
|
0.0000
|
X_i
|
36.124
|
25.60
|
0.1644
|
- このうち、当該の効果が有意になっていた割合が検定力!
alpha <- 0.05 #有意水準
sims %>%
group_by(term) %>% #効果ごとに結果をまとめる
summarize(
mean_estimate = mean(estimate),
mean_se = mean(std.error),
power = mean(p.value < alpha), #p値がalpha未満なら1, そうでないなら0と評価されるので、平均をとれば割合となる
.groups = "drop"
) %>%
kable()
term
|
mean_estimate
|
mean_se
|
power
|
(Intercept)
|
800.93
|
15.41
|
1.0
|
X_i
|
48.42
|
23.67
|
0.5
|