analyse_07_sample_picture_word_roi.R 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. library(lme4)
  2. library(dplyr)
  3. library(tidyr)
  4. library(purrr)
  5. library(readr)
  6. library(ggplot2)
  7. library(ggtext)
  8. library(cowplot)
  9. library(patchwork)
  10. library(parallel)
  11. ggplot2::theme_set(ggplot2::theme_classic())
  12. cong_cols <- c("#E69F00", "#009E73")
  13. n_cores <- 14
  14. library(RColorBrewer)
  15. eff_cols <- c("#000000", brewer.pal(3, "Set1")[1:2], brewer.pal(5, "Set1")[5])
  16. cong_cols <- c("#E69F00", "#009E73")
  17. cong_cols_alpha_0.4_white <- c("#F5D899", "#99D8C7")
  18. roi <- c("TP7", "CP5", "P5", "P7", "P9", "PO3", "PO7", "O1")
  19. time_lims <- c(-250, 650)
  20. # function to normalise between 0 and 1
  21. norm01 <- function(x, ...) (x-min(x, ...))/(max(x, ...)-min(x, ...))
  22. # get the stimuli's percentage of name agreement values
  23. stim <- read_csv("boss.csv", col_types = cols(perc_name_agree_denom_fq_inputs = col_number())) %>%
  24. select(filename, perc_name_agree_denom_fq_inputs) %>%
  25. rename(perc_name_agree = perc_name_agree_denom_fq_inputs)
  26. # import the max electrode data from the preprocessing, and set up the variables for the model
  27. get_sample_data <- function(ch_i = NA) {
  28. list.files("sample_data", pattern=".+\\.csv$", full.names=TRUE) %>%
  29. map_dfr(function(f) {
  30. x <- read_csv(
  31. f,
  32. col_types=cols(
  33. subj_id = col_character(),
  34. stim_grp = col_integer(),
  35. resp_grp = col_integer(),
  36. item_nr = col_integer(),
  37. ch_name = col_character(),
  38. time = col_double(),
  39. uV = col_double(),
  40. condition = col_character(),
  41. image = col_character(),
  42. string = col_character(),
  43. .default = col_double()
  44. ),
  45. progress = FALSE
  46. ) |>
  47. select(-stim_grp, -resp_grp, -item_nr) |>
  48. filter(time > time_lims[[1]] & time < time_lims[[2]])
  49. if (all(!is.na(ch_i))) x <- filter(x, ch_name %in% ch_i)
  50. x
  51. }) %>%
  52. left_join(stim, by=c("image" = "filename")) %>%
  53. mutate(
  54. prop_agree = perc_name_agree/100,
  55. pred_norm = norm01(prop_agree, na.rm=TRUE),
  56. pred_norm_max = pred_norm - 1
  57. ) %>%
  58. select(-perc_name_agree)
  59. }
  60. unique_ids <- list.files("sample_data", pattern=".+\\.csv$", full.names=TRUE) %>%
  61. first() %>%
  62. read_csv() |>
  63. filter(time > time_lims[[1]] & time < time_lims[[2]]) |>
  64. select(ch_name, time) %>%
  65. as.list() %>%
  66. lapply(unique)
  67. target_time_ids <- unique_ids$time
  68. # get models for each timepoint, for each electrode, and extract fixed effects
  69. d_i_list <- get_sample_data(roi) |>
  70. mutate(
  71. cong_dev = as.numeric(scale(ifelse(condition=="A2", 0, 1), center=TRUE, scale=FALSE)),
  72. cong_dum_incong = as.numeric(condition=="A1"), # 0 is at incongruent
  73. cong_dum_cong = as.numeric(condition=="A2") # 0 is at congruent
  74. ) |>
  75. group_split(time)
  76. gc()
  77. # # sanity check
  78. # sc_pl <- d_i_list |>
  79. # reduce(bind_rows) |>
  80. # mutate(pred_bin = factor(case_when(
  81. # pred_norm <.33 ~ "low",
  82. # pred_norm <.66 ~ "medium",
  83. # pred_norm < Inf ~ "high"
  84. # ), levels = c("low", "medium", "high"))) |>
  85. # group_by(time, condition, pred_bin, ch_name) |>
  86. # summarise(mean_uV = mean(uV)) |>
  87. # ungroup() |>
  88. # pivot_wider(names_from=condition, values_from=mean_uV) |>
  89. # mutate(cong_minus_incong = A1-A2) |>
  90. # ggplot(aes(time, cong_minus_incong, colour=ch_name)) +
  91. # geom_line() +
  92. # facet_wrap(vars(pred_bin))
  93. message("Fitting models in parallel to OT channels in picture-word experiment")
  94. # d_i_list <- get_sample_data(roi) %>%
  95. # mutate(cong_dev = as.numeric(scale(ifelse(condition=="A2", 0, 1), center=TRUE, scale=FALSE))) %>%
  96. # filter(time <= 655) %>%
  97. # arrange(time) %>%
  98. # split(time)
  99. #
  100. # gc()
  101. cl <- makeCluster(n_cores)
  102. cl_packages <- clusterEvalQ(cl, {
  103. library(dplyr)
  104. library(lme4)
  105. })
  106. fe_res <- parLapply(cl, d_i_list, function(d_i) {
  107. # m <- lme4::lmer(
  108. # uV ~ cong_dev * pred_norm +
  109. # (cong_dev * pred_norm | subj_id) +
  110. # (cong_dev * pred_norm | ch_name:subj_id) +
  111. # (cong_dev | image) +
  112. # (1 | string),
  113. # REML=FALSE,
  114. # control = lmerControl(optimizer="bobyqa"),
  115. # data=d_i
  116. # )
  117. m <- lme4::lmer(
  118. uV ~ cong_dev * pred_norm +
  119. (1 | subj_id) +
  120. (1 | ch_name:subj_id) +
  121. (1 | image) +
  122. (1 | string),
  123. REML=FALSE,
  124. control = lmerControl(optimizer="bobyqa"),
  125. data=d_i
  126. )
  127. m %>%
  128. summary() %>%
  129. with(coefficients) %>%
  130. as_tibble(rownames = "fe")
  131. }) %>%
  132. reduce(bind_rows) %>%
  133. mutate(time = rep(target_time_ids, each=4))
  134. stopCluster(cl)
  135. fe_res_tidy <- fe_res %>%
  136. mutate(
  137. fe_lab = factor(recode(
  138. fe,
  139. `(Intercept)` = "Intercept",
  140. cong_dev = "Congruency",
  141. pred_norm = "Predictability",
  142. `cong_dev:pred_norm` = "Congruency * Predictability",
  143. ), levels = c("Intercept", "Congruency", "Predictability", "Congruency * Predictability")),
  144. fe_lab_newline = fe_lab,
  145. bound_lower = Estimate - 1.96 * `Std. Error`,
  146. bound_upper = Estimate + 1.96 * `Std. Error`
  147. )
  148. levels(fe_res_tidy$fe_lab) <- c("Intercept"="Intercept", "Congruency"="Congruency", "Predictability"="Predictability", "Congruency * Predictability"="Congruency × Predictability")
  149. levels(fe_res_tidy$fe_lab_newline) <- c("Intercept"="Intercept", "Congruency"="Congruency", "Predictability"="Predictability", "Congruency * Predictability"="Congruency<br>× Predictability")
  150. overlay_dat <- fe_res_tidy %>%
  151. filter(fe_lab=="Intercept") %>%
  152. select(-fe_lab, -fe_lab_newline) %>%
  153. expand_grid(fe_lab = unique(fe_res_tidy$fe_lab)) %>%
  154. mutate(
  155. Estimate = ifelse(fe_lab=="Intercept", NA, Estimate),
  156. fe_lab_newline = fe_lab
  157. )
  158. levels(overlay_dat$fe_lab) <- c("Intercept"="Intercept", "Congruency"="Congruency", "Predictability"="Predictability", "Congruency * Predictability"="Congruency × Predictability")
  159. levels(overlay_dat$fe_lab_newline) <- c("Intercept"="Intercept", "Congruency"="Congruency", "Predictability"="Predictability", "Congruency * Predictability"="Congruency<br>× Predictability")
  160. ylims <- round(c(min(fe_res_tidy$bound_lower), max(fe_res_tidy$bound_upper)))
  161. pl_roi <- fe_res_tidy %>%
  162. ggplot(aes(time, Estimate, ymin=bound_lower, ymax=bound_upper, group=fe_lab)) +
  163. geom_line(colour="black", data=overlay_dat, alpha=0.6) +
  164. geom_ribbon(alpha=0.5, linewidth=0.1, fill="dodgerblue") +
  165. geom_line() +
  166. geom_vline(xintercept=0, linetype="dashed") +
  167. geom_hline(yintercept=0, linetype="dashed") +
  168. facet_wrap(vars(fe_lab), nrow=2) +
  169. labs(x = "Time (ms)", y = "Fixed Effect Estimate (µV)", tag="a") +
  170. # scale_colour_manual(values = eff_cols) +
  171. # scale_fill_manual(values = eff_cols) +
  172. scale_x_continuous(expand = c(0, 0)) +
  173. scale_y_continuous(limits=ylims) +
  174. theme(
  175. legend.position = "none",
  176. strip.background = element_blank(),
  177. legend.background = element_blank(),
  178. axis.line.x = element_blank(),
  179. panel.spacing.x = unit(12, "pt"),
  180. strip.text.x = ggtext::element_markdown()
  181. ) +
  182. coord_cartesian(xlim=c(-250, time_lims[2]))
  183. pl_roi_1row <- fe_res_tidy %>%
  184. ggplot(aes(time, Estimate, ymin=bound_lower, ymax=bound_upper, group=fe_lab)) +
  185. geom_line(colour="darkgrey", data=overlay_dat) +
  186. geom_ribbon(alpha=0.5, linewidth=0.1, fill="dodgerblue") +
  187. geom_line() +
  188. geom_vline(xintercept=0, linetype="dashed") +
  189. geom_hline(yintercept=0, linetype="dashed") +
  190. facet_wrap(vars(fe_lab_newline), nrow=1) +
  191. labs(x = "Time (ms)", y = "Effect Estimate (µV)", tag="a") +
  192. # scale_colour_manual(values = eff_cols) +
  193. # scale_fill_manual(values = eff_cols) +
  194. scale_x_continuous(expand = c(0, 0)) +
  195. scale_y_continuous(limits=ylims) +
  196. theme(
  197. legend.position = "none",
  198. strip.background = element_blank(),
  199. legend.background = element_blank(),
  200. axis.line.x = element_blank(),
  201. panel.spacing.x = unit(12, "pt"),
  202. strip.text.x = ggtext::element_markdown(),
  203. strip.clip = "off"
  204. ) +
  205. coord_cartesian(xlim=c(-250, time_lims[2]))
  206. preds <- expand_grid(
  207. prop_agree = seq(0.1, 1, 0.1),
  208. condition = factor(c("Picture-Congruent", "Picture-Incongruent"), levels=c("Picture-Congruent", "Picture-Incongruent"))
  209. ) %>%
  210. rowwise() %>%
  211. mutate(pred_norm = ifelse(prop_agree==1, 1, norm01(c(0.07, prop_agree, 1))[2])) %>%
  212. expand_grid(time = target_time_ids) %>%
  213. ungroup() %>%
  214. mutate(
  215. perc_name_agree = prop_agree*100,
  216. cong_dev = as.numeric(scale(ifelse(condition=="Picture-Incongruent", 0, 1), center=TRUE, scale=FALSE))
  217. ) %>%
  218. left_join(
  219. fe_res %>%
  220. select(time, fe, Estimate) %>%
  221. pivot_wider(names_from=fe, values_from=Estimate, names_prefix="fe_"),
  222. by = "time"
  223. ) %>%
  224. mutate(
  225. pred_uV = `fe_(Intercept)` +
  226. cong_dev * fe_cong_dev +
  227. pred_norm * fe_pred_norm +
  228. cong_dev * pred_norm * `fe_cong_dev:pred_norm`
  229. )
  230. pl_preds <- preds %>%
  231. ggplot(aes(time, pred_uV, colour=prop_agree)) +
  232. geom_line(aes(group = as.factor(prop_agree))) +
  233. geom_vline(xintercept=0, linetype="dashed") +
  234. geom_hline(yintercept=0, linetype="dashed") +
  235. scale_colour_continuous(
  236. type="viridis", breaks=sort(unique(preds$prop_agree)),
  237. labels=sprintf("%s%%", sort(unique(preds$prop_agree))*100),
  238. # guide=guide_colourbar(barheight = 7.6, barwidth = 1)
  239. guide = guide_legend(override.aes = list(linewidth=1), reverse = TRUE)
  240. ) +
  241. scale_x_continuous(expand=c(0, 0)) +
  242. scale_y_continuous(limits=ylims) +
  243. facet_wrap(vars(condition), nrow=1) +
  244. labs(
  245. x = "Time (ms)",
  246. y = "Predicted Amplitude (µV)",
  247. colour = "Predictability",
  248. tag = "b"
  249. ) +
  250. theme(
  251. legend.position = "right",
  252. legend.key.height = unit(11, "pt"),
  253. legend.text.align = 1,
  254. strip.background = element_blank(),
  255. plot.background = element_blank(),
  256. axis.line.x = element_blank(),
  257. panel.spacing.x = unit(12, "pt")
  258. ) +
  259. coord_cartesian(xlim=c(-250, time_lims[2]))
  260. pl <- plot_grid(pl_roi, pl_preds, ncol=1, rel_heights = c(1, 0.8), align="v", axis="l")
  261. ggsave(file.path("figs", "sample_level", "picture_word", "roi.pdf"), pl, width=6.5, height=6, device="pdf")
  262. ggsave(file.path("figs", "sample_level", "picture_word", "roi.png"), pl, width=6.5, height=6, device="png", type="cairo")
  263. pl_preds_bypred <- preds %>%
  264. mutate(
  265. perc_lab = factor(
  266. sprintf("%s%%", perc_name_agree),
  267. levels = sprintf("%s%%", sort(unique(perc_name_agree)))
  268. ),
  269. cong_lab = ifelse(condition=="Picture-Congruent", "Congruent", "Incongruent")
  270. ) %>%
  271. ggplot(aes(time, pred_uV, colour=cong_lab)) +
  272. geom_vline(xintercept=0, linetype="dashed") +
  273. geom_hline(yintercept=0, linetype="dashed") +
  274. geom_line() +
  275. scale_colour_manual(
  276. values = cong_cols,
  277. guide=guide_legend(nrow=1, override.aes = list(linewidth=1))
  278. ) +
  279. scale_x_continuous(expand=c(0, 0)) +
  280. scale_y_continuous(limits=ylims) +
  281. facet_wrap(vars(perc_lab), nrow=3) +
  282. labs(
  283. x = "Time (ms)",
  284. y = "Predicted Amplitude (µV)",
  285. colour = "Picture-Word Congruency"
  286. ) +
  287. theme(
  288. legend.position = c(0.925, -0.025),
  289. legend.justification = c(1, 0),
  290. legend.margin = margin(),
  291. legend.key.height = unit(12, "pt"),
  292. strip.background = element_blank(),
  293. plot.background = element_blank(),
  294. axis.line.x = element_blank(),
  295. panel.spacing.x = unit(12, "pt")
  296. ) +
  297. coord_cartesian(xlim=c(-250, time_lims[2]))
  298. pl_plus_C <- plot_grid(
  299. pl_roi_1row + theme(strip.text = element_text(margin=margin(t=2.5, b=2.5, unit="pt"))),
  300. pl_preds +
  301. theme(
  302. strip.text = element_text(margin=margin(t=2.5, b=2.5, unit="pt")),
  303. legend.key.height = unit(9.5, "pt")
  304. ),
  305. pl_preds_bypred +
  306. labs(tag="c") +
  307. theme(strip.text = element_text(margin=margin(t=2.5, b=2.5, unit="pt"))),
  308. ncol=1, rel_heights = c(0.85, 1.2, 1.5), align="v", axis="l"
  309. )
  310. ggsave(file.path("figs", "sample_level", "picture_word", "roi_3panels.pdf"), pl_plus_C, width=6.5, height=7, device="pdf")
  311. ggsave(file.path("figs", "sample_level", "picture_word", "roi_3panels.png"), pl_plus_C, width=6.5, height=7, device="png", type="cairo")
  312. # simple effects models ---------------------------------------------------
  313. cl <- makeCluster(n_cores)
  314. cl_packages <- clusterEvalQ(cl, {
  315. library(dplyr)
  316. library(lme4)
  317. })
  318. fe_res_cong <- parLapply(cl, d_i_list, function(d_i) {
  319. m <- lme4::lmer(
  320. uV ~ cong_dum_cong * pred_norm +
  321. (1 | subj_id) +
  322. (1 | ch_name:subj_id) +
  323. (1 | image) +
  324. (1 | string),
  325. REML=FALSE,
  326. control = lmerControl(optimizer="bobyqa"),
  327. data=d_i
  328. )
  329. m %>%
  330. summary() %>%
  331. with(coefficients) %>%
  332. as_tibble(rownames = "fe")
  333. }) %>%
  334. reduce(bind_rows) %>%
  335. mutate(time = rep(target_time_ids, each=4))
  336. fe_res_incong <- parLapply(cl, d_i_list, function(d_i) {
  337. m <- lme4::lmer(
  338. uV ~ cong_dum_incong * pred_norm +
  339. (1 | subj_id) +
  340. (1 | ch_name:subj_id) +
  341. (1 | image) +
  342. (1 | string),
  343. REML=FALSE,
  344. control = lmerControl(optimizer="bobyqa"),
  345. data=d_i
  346. )
  347. m %>%
  348. summary() %>%
  349. with(coefficients) %>%
  350. as_tibble(rownames = "fe")
  351. }) %>%
  352. reduce(bind_rows) %>%
  353. mutate(time = rep(target_time_ids, each=4))
  354. stopCluster(cl)
  355. fe_res_tidy_se <- bind_rows(
  356. fe_res_cong %>%
  357. mutate(
  358. cong_level = "Congruent",
  359. fe_lab = factor(recode(
  360. fe,
  361. `(Intercept)` = "Intercept",
  362. cong_dum_cong = "Congruency",
  363. pred_norm = "Predictability",
  364. `cong_dum_cong:pred_norm` = "Congruency * Predictability"
  365. ), levels = c("Intercept", "Congruency", "Predictability", "Congruency * Predictability")),
  366. fe_lab_newline = factor(fe_lab, labels = c("Intercept", "Congruency", "Predictability", "Congruency\n* Predictability")),
  367. bound_lower = Estimate - 1.96 * `Std. Error`,
  368. bound_upper = Estimate + 1.96 * `Std. Error`
  369. ),
  370. fe_res_incong %>%
  371. mutate(
  372. cong_level = "Incongruent",
  373. fe_lab = factor(recode(
  374. fe,
  375. `(Intercept)` = "Intercept",
  376. cong_dum_incong = "Congruency",
  377. pred_norm = "Predictability",
  378. `cong_dum_incong:pred_norm` = "Congruency * Predictability"
  379. ), levels = c("Intercept", "Congruency", "Predictability", "Congruency * Predictability")),
  380. fe_lab_newline = factor(fe_lab, labels = c("Intercept", "Congruency", "Predictability", "Congruency\n* Predictability")),
  381. bound_lower = Estimate - 1.96 * `Std. Error`,
  382. bound_upper = Estimate + 1.96 * `Std. Error`
  383. )
  384. )
  385. fe_res_tidy_se_pred <- filter(fe_res_tidy_se, fe_lab=="Predictability")
  386. fe_se_pl_alpha <- 0.4
  387. fe_se_pl <- fe_res_tidy_se_pred %>%
  388. ggplot(aes(time, Estimate, ymin=bound_lower, ymax=bound_upper, colour=cong_level, fill=cong_level)) +
  389. geom_ribbon(alpha=fe_se_pl_alpha, linewidth=0.25, data=filter(fe_res_tidy_se_pred, cong_level=="Congruent")) +
  390. geom_line(data=filter(fe_res_tidy_se_pred, cong_level=="Congruent")) +
  391. geom_ribbon(alpha=fe_se_pl_alpha, linewidth=0.25, data=filter(fe_res_tidy_se_pred, cong_level=="Incongruent")) +
  392. geom_line(data=filter(fe_res_tidy_se_pred, cong_level=="Incongruent")) +
  393. geom_vline(xintercept=0, linetype="dashed") +
  394. geom_hline(yintercept=0, linetype="dashed") +
  395. scale_colour_manual(values = cong_cols) +
  396. scale_fill_manual(values = cong_cols) +
  397. scale_x_continuous(expand=c(0, 0), breaks = seq(-200, time_lims[2], 100), limits=c(-250, time_lims[2])) +
  398. scale_y_continuous(breaks=scales::breaks_pretty(n=6)) +
  399. guides(
  400. colour = guide_legend(nrow=1),
  401. fill = guide_legend(nrow=1, override.aes = list(alpha=1, fill=cong_cols_alpha_0.4_white))
  402. ) +
  403. labs(
  404. x = "Time (ms)",
  405. y = "Effect of Predictability (µV)",
  406. colour = "Picture-Word Congruency",
  407. fill = "Picture-Word Congruency"
  408. ) +
  409. theme(
  410. legend.key.height = unit(12, "pt"),
  411. legend.position = "top",
  412. axis.line.x = element_blank()
  413. )
  414. ggsave(file.path("figs", "sample_level", "picture_word", "roi_simple_effs.pdf"), fe_se_pl, width=4.75, height=2.75, device="pdf")
  415. ggsave(file.path("figs", "sample_level", "picture_word", "roi_simple_effs.png"), fe_se_pl, width=4.75, height=2.75, device="png", type="cairo")