highspeed-analysis-oddball.Rmd 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. ## Decoding on slow trials
  2. ### Initialization
  3. We load the data and relevant functions:
  4. ```{r, warning=FALSE, message=FALSE, echo=TRUE}
  5. # find the path to the root of this project:
  6. if (!requireNamespace("here")) install.packages("here")
  7. if ( basename(here::here()) == "highspeed" ) {
  8. path_root = here::here("highspeed-analysis")
  9. } else {
  10. path_root = here::here()
  11. }
  12. # source all relevant functions from the setup R script:
  13. source(file.path(path_root, "code", "highspeed-analysis-setup.R"))
  14. # create a list of participants to exclude based on behavioral performance:
  15. sub_exclude <- c("sub-24", "sub-31", "sub-37", "sub-40")
  16. ```
  17. ### Decoding accuracy
  18. #### Mean decoding accuracy
  19. We calculate the mean decoding accuracy:
  20. ```{r, results="hold", echo=TRUE}
  21. calc_class_stim_acc <- function(data, mask_label) {
  22. # calculate the mean decoding accuracy for each participant:
  23. dt_odd_peak = data %>%
  24. # select data for the oddball peak decoding
  25. # leave out the redundant "other" class predictions:
  26. filter(test_set == "test-odd_peak" & class != "other" & mask == mask_label) %>%
  27. # filter out excluded participants:
  28. filter(!(id %in% sub_exclude)) %>%
  29. # only check classifier of stimulus presented on a given trial:
  30. filter(class == stim) %>%
  31. # calculate the decoding accuracy by comparing true and predicted label:
  32. mutate(acc = 0) %>%
  33. transform(acc = ifelse(pred_label == stim, 1, acc)) %>%
  34. setDT(.) %>%
  35. # calculate average decoding accuracy for every participant and classification:
  36. .[, by = .(classification, id), .(
  37. mean_accuracy = mean(acc) * 100,
  38. num_trials = .N
  39. )] %>%
  40. verify(all(num_trials <= 480)) %>%
  41. # verify if the number of participants matches:
  42. verify(.[classification == "ovr", .(num_subs = .N)]$num_subs == 36)
  43. return(dt_odd_peak)
  44. }
  45. ```
  46. ```{r, results="hold", echo=TRUE}
  47. calc_max_prob_acc <- function(data, mask_label) {
  48. dt_odd_peak <- data %>%
  49. # select data for the oddball peak decoding
  50. # leave out the redundant "other" class predictions:
  51. filter(test_set == "test-odd_peak" & class != "other" & mask == mask_label) %>%
  52. # filter out excluded participants:
  53. filter(!(id %in% sub_exclude)) %>%
  54. # calculate the decoding accuracy by comparing true and predicted label:
  55. setDT(.) %>%
  56. # calculate average decoding accuracy for every participant and classification:
  57. .[, by = .(classification, id, trial, stim), .(
  58. max_prob_label = class[which.max(probability)],
  59. num_classes = .N
  60. )] %>%
  61. verify(num_classes == 5) %>%
  62. .[, "acc" := ifelse(stim == max_prob_label, 1, 0)] %>%
  63. .[, by = .(classification, id), .(
  64. mean_accuracy = mean(acc) * 100,
  65. num_trials = .N
  66. )] %>%
  67. verify(num_trials <= 480)
  68. return(dt_odd_peak)
  69. }
  70. ```
  71. We print the results for the decoding accuracy:
  72. ```{r, results="hold", warning=FALSE, message=FALSE, echo=TRUE}
  73. decoding_accuracy <- function(accuracy, alt_label){
  74. # print average decoding accuracy:
  75. print(sprintf("decoding accuracy: m = %0.2f %%", mean(accuracy)))
  76. # print sd decoding accuracy:
  77. print(sprintf("decoding accuracy: sd = %0.2f %%", sd(accuracy)))
  78. # print range of decoding accuracy:
  79. print(sprintf("decoding accuracy: range = %s %%",
  80. paste(round(range(accuracy),0), collapse = "-")))
  81. # define decoding accuracy baseline (should equal 20%):
  82. acc_baseline = 100/5
  83. print(sprintf("chance baseline = %d %%", acc_baseline))
  84. # perform effect size (cohen`s d)
  85. cohens_d = (mean(accuracy) - acc_baseline) / sd(accuracy)
  86. print(sprintf("effect size (cohen's d) = %0.2f", cohens_d))
  87. # perform shapiro-wilk test to test for normality of the data
  88. print(shapiro.test(accuracy))
  89. # perform one-sided one-sample t-test against baseline:
  90. results = t.test(accuracy, mu = acc_baseline, alternative = alt_label)
  91. print(results)
  92. # perform one-sample wilcoxon test in case of non-normality of the data:
  93. print(wilcox.test(accuracy, mu = acc_baseline, alternative = alt_label))
  94. }
  95. ```
  96. ```{r, results="hold"}
  97. #dt_odd_peak <- calc_class_stim_acc(data = dt_pred, mask_label = "cv")
  98. #dt_odd_peak_hpc <- calc_class_stim_acc(data = dt_pred, mask_label = "cv_hpc")
  99. dt_odd_peak <- calc_max_prob_acc(data = dt_pred, mask_label = "cv")
  100. dt_odd_peak_hpc <- calc_max_prob_acc(data = dt_pred, mask_label = "cv_hpc")
  101. decoding_accuracy(
  102. accuracy = subset(dt_odd_peak, classification == "ovr")$mean_acc,
  103. alt_label = "greater")
  104. decoding_accuracy(
  105. accuracy = subset(dt_odd_peak_hpc, classification == "ovr")$mean_accuracy,
  106. alt_label = "two.sided")
  107. ```
  108. We plot the mean decoding accuracy in occipito-temporal and hippocampal data:
  109. ```{r, echo=TRUE, class.source=NULL, fig.width=1, fig.height=3}
  110. plot_odd_peak <- function(data) {
  111. plot.odd = ggplot(data = data, aes(
  112. x = "mean_acc", y = as.numeric(mean_accuracy))) +
  113. geom_bar(stat = "summary", fun = "mean", fill = "lightgray") +
  114. geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5,
  115. color = "black", fill = "lightgray", alpha = 0.5,
  116. inherit.aes = TRUE, binwidth = 2) +
  117. geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
  118. ylab("Accuracy (%)") + xlab("Condition") +
  119. geom_hline(aes(yintercept = 20), linetype = "dashed", color = "black") +
  120. scale_y_continuous(labels = label_fill(seq(0, 100, 20), mod = 1), breaks = seq(0, 100, 20)) +
  121. coord_capped_cart(left = "both", expand = TRUE, ylim = c(0, 100)) +
  122. theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
  123. theme(axis.title.x = element_blank(), axis.text.x = element_blank()) +
  124. annotate("text", x = 1, y = 15, label = "Chance", color = "black",
  125. size = rel(2.5), family = "Helvetica", fontface = "plain") +
  126. theme(panel.border = element_blank(), axis.line = element_line()) +
  127. theme(axis.line = element_line(colour = "black"),
  128. panel.grid.major = element_blank(),
  129. panel.grid.minor = element_blank(),
  130. panel.border = element_blank(),
  131. panel.background = element_blank())
  132. return(plot.odd)
  133. }
  134. fig_odd_peak <- plot_odd_peak(subset(dt_odd_peak, classification == "ovr"))
  135. fig_odd_peak_hpc <- plot_odd_peak(subset(dt_odd_peak_hpc, classification == "ovr"))
  136. fig_odd_peak; fig_odd_peak_hpc
  137. ```
  138. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  139. ggsave(filename = "highspeed_plot_decoding_average_decoding.pdf",
  140. plot = fig_odd_peak, device = cairo_pdf, path = path_figures, scale = 1,
  141. dpi = "retina", height = 5, width = 2)
  142. ```
  143. ### Fold-wise decoding accuracy:
  144. We calculate the mean decoding accuracy for each of the eight folds of the cross-validation procedure:
  145. ```{r, results="hold", echo=TRUE}
  146. dt_odd_peak_run = dt_pred %>%
  147. # select data for the oddball peak decoding
  148. # leave out the redundant "other" class predictions:
  149. filter(test_set == "test-odd_peak" & class != "other" & mask == "cv") %>%
  150. # filter out excluded participants:
  151. filter(!(id %in% sub_exclude)) %>%
  152. setDT(.) %>%
  153. # calculate average decoding accuracy for every participant and classification:
  154. .[, by = .(classification, id, run_study, trial, stim), .(
  155. max_prob_label = class[which.max(probability)],
  156. num_classes = .N
  157. )] %>%
  158. # check if there are five classifier predictions per trial:
  159. verify(num_classes == 5) %>%
  160. # determine accuracy if label with highest probability matches stimulus:
  161. .[, "accuracy" := ifelse(stim == max_prob_label, 1, 0)]
  162. # print results table:
  163. rmarkdown::paged_table(dt_odd_peak_run)
  164. ```
  165. ```{r, results="hold", echo=TRUE}
  166. # calculate average decoding accuracy for every participant and classification:
  167. dt_odd_peak_run_mean <- dt_odd_peak_run %>%
  168. .[, by = .(classification, id, run_study), .(
  169. mean_accuracy = mean(accuracy) * 100,
  170. num_trials = length(unique(trial))
  171. )] %>%
  172. # verify if the number of participants matches for every classification and run:
  173. verify(.[, by = .(classification, run_study),
  174. .(num_subs = .N)]$num_subs == 36) %>%
  175. setorder(., classification, id, run_study)
  176. # print the table:
  177. rmarkdown::paged_table(dt_odd_peak_run_mean)
  178. ```
  179. ```{r, echo=FALSE, class.source=NULL}
  180. fig_odd_run = ggplot(data = subset(dt_odd_peak_run_mean, classification == "ovr"), aes(
  181. x = run_study, y = as.numeric(mean_accuracy))) +
  182. geom_bar(stat = "summary", fun = "mean", fill = "lightgray", color = "black") +
  183. geom_dotplot(aes(color = "black", fill = id),
  184. binaxis = "y", stackdir = "center", stackratio = 0.5, alpha = 0.5,
  185. inherit.aes = TRUE, binwidth = 2) +
  186. geom_line(aes(group = id, color = id)) +
  187. geom_line(aes(group = 1), stat = "summary", fun = "mean", color = "black") +
  188. geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
  189. ylab("Accuracy (%)") + xlab("Task run") +
  190. geom_hline(aes(yintercept = 20), linetype = "dashed", color = "black") +
  191. scale_y_continuous(labels = label_fill(seq(0, 100, 20), mod = 1), breaks = seq(0, 100, 20)) +
  192. coord_capped_cart(left = "both", expand = TRUE, ylim = c(0, 100)) +
  193. annotate("text", x = 8, y = 16, label = "Chance", color = "black",
  194. size = rel(2.5), family = "Helvetica", fontface = "plain") +
  195. theme(legend.position = "none") +
  196. theme(panel.border = element_blank()) +
  197. theme(axis.text = element_text(color = "black")) +
  198. theme(axis.ticks = element_line(color = "black")) +
  199. theme(axis.line.y = element_line(colour = "black"),
  200. axis.ticks.x = element_blank(),
  201. panel.grid.major = element_blank(),
  202. panel.grid.minor = element_blank(),
  203. panel.border = element_blank(),
  204. panel.background = element_blank())
  205. fig_odd_run
  206. ```
  207. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  208. ggsave(filename = "highspeed_plot_decoding_average_decoding_run.pdf",
  209. plot = fig_odd_run, device = cairo_pdf, path = path_figures, scale = 1,
  210. dpi = "retina", height = 3, width = 5)
  211. ```
  212. ```{r, results="hold"}
  213. lme_odd_peak_run <- lmerTest::lmer(
  214. mean_accuracy ~ run_study + (1 | id), control = lcctrl,
  215. data = subset(dt_odd_peak_run_mean, classification == "ovr")
  216. )
  217. summary(lme_odd_peak_run)
  218. anova(lme_odd_peak_run)
  219. emmeans_results = emmeans(lme_odd_peak_run, list(pairwise ~ run_study))
  220. emmeans_results
  221. ```
  222. ## Multivariate decoding time courses
  223. We calculate the multivariate decoding time courses:
  224. ```{r, echo=TRUE}
  225. dt_odd_long_sub = dt_pred %>%
  226. # select data for the oddball long decoding
  227. # leave out the redundant "other" class predictions:
  228. filter(test_set == "test-odd_long" & class != "other" & mask == "cv") %>%
  229. # filter out excluded participants:
  230. filter(!(id %in% sub_exclude)) %>% setDT(.) %>%
  231. # average across stimuli and runs for each participant
  232. # create an index for the true stimulus presented on each trial:
  233. .[, by = .(classification, id, seq_tr, class, stim), .(
  234. num = .N,
  235. mean_prob = mean(probability * 100),
  236. current_stim = as.numeric(class == unique(stim))
  237. )]
  238. dt_odd_long_mean = dt_odd_long_sub %>%
  239. # average across participants:
  240. .[, by = .(classification, seq_tr, class, stim, current_stim), .(
  241. mean_prob = mean(mean_prob),
  242. num_subs = .N,
  243. sem_upper = (mean(mean_prob) + (sd(mean_prob)/sqrt(.N))),
  244. sem_lower = (mean(mean_prob) - (sd(mean_prob)/sqrt(.N)))
  245. )] %>%
  246. # create an additional variable that expresses time in seconds:
  247. mutate(time = (seq_tr - 1) * 1.25) %>%
  248. # check if the number of participants is correct:
  249. verify(all(num_subs == 36))
  250. ```
  251. ```{r, echo=FALSE, class.source=NULL}
  252. plot.odd.long = ggplot(data = subset(dt_odd_long_mean, classification == "ovr"), aes(
  253. x = as.factor(seq_tr), y = as.numeric(mean_prob))) +
  254. facet_wrap(~ as.factor(stim)) +
  255. geom_line(data = subset(dt_odd_long_sub, classification == "ovr"), aes(
  256. group = as.factor(interaction(id, class)), color = fct_rev(as.factor(current_stim))), alpha = 0.0) +
  257. geom_line(data = subset(dt_odd_long_sub, classification == "ovr" & current_stim == 0), aes(
  258. group = as.factor(interaction(id, class)), color = fct_rev(as.factor(current_stim))), alpha = 0.3) +
  259. geom_line(data = subset(dt_odd_long_sub, classification == "ovr" & current_stim == 1), aes(
  260. group = as.factor(interaction(id, class)), color = fct_rev(as.factor(current_stim))), alpha = 0.3) +
  261. geom_ribbon(
  262. data = subset(dt_odd_long_mean, classification == "ovr"),
  263. aes(ymin = sem_lower, ymax = sem_upper, fill = fct_rev(as.factor(current_stim)),
  264. group = as.factor(class)), alpha = 0.0, color = NA) +
  265. geom_line(
  266. data = subset(dt_odd_long_mean, classification == "ovr", alpha = 0),
  267. aes(color = fct_rev(as.factor(current_stim)), group = as.factor(class))) +
  268. geom_ribbon(
  269. data = subset(dt_odd_long_mean, classification == "ovr" & current_stim == 1),
  270. aes(ymin = sem_lower, ymax = sem_upper, fill = fct_rev(as.factor(current_stim)),
  271. group = as.factor(class)), alpha = 0.3, color = NA) +
  272. geom_line(
  273. data = subset(dt_odd_long_mean, classification == "ovr" & current_stim == 1),
  274. aes(color = fct_rev(as.factor(current_stim)), group = as.factor(class))) +
  275. ylab("Probability (%)") + xlab("Time from stimulus onset (TRs)") +
  276. scale_fill_manual(name = "Classified class", values = c("black", "lightgray"), labels = c("true", "other")) +
  277. scale_color_manual(name = "Classified class", values = c("black", "lightgray"), labels = c("true", "other")) +
  278. coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 80)) +
  279. theme(legend.position = c(1, 0), legend.justification = c(1, 0)) +
  280. #theme(legend.position = c(0.95, 0.8), legend.justification = c(1, 0)) +
  281. #theme(legend.title = element_text(size = 8), legend.text = element_text(size = 8)) +
  282. #theme(legend.key.size = unit(0.7, "line")) +
  283. scale_x_discrete(labels = label_fill(seq(0, 7, 1), mod = 1), breaks = seq(0, 7, 1)) +
  284. guides(color = guide_legend(ncol = 2), fill = guide_legend(ncol = 2)) +
  285. theme(strip.text.x = element_text(margin = margin(b = 2, t = 2))) +
  286. theme(panel.border = element_blank(), axis.line = element_line()) +
  287. theme(axis.line = element_line(colour = "black"),
  288. panel.grid.major = element_blank(),
  289. panel.grid.minor = element_blank(),
  290. panel.border = element_blank(),
  291. panel.background = element_blank())
  292. #theme(strip.text.x = element_blank())
  293. plot.odd.long
  294. ```
  295. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  296. ggsave(filename = "highspeed_plot_decoding_single_trial_activation.pdf",
  297. plot = plot.odd.long, device = cairo_pdf, path = path_figures, scale = 1,
  298. dpi = "retina", width = 3.5, height = 3)
  299. ```
  300. We compare the mean classification probability of the current stimulus
  301. versus the mean of all other stimuli for each TR:
  302. ```{r}
  303. dt_odd_long_mean_stat = dt_pred %>%
  304. # select data for the oddball long decoding
  305. # leave out the redundant "other" class predictions:
  306. filter(test_set == "test-odd_long" & class != "other" & mask == "cv") %>%
  307. setDT(.) %>%
  308. # filter out excluded participants:
  309. filter(!(id %in% sub_exclude)) %>% setDT(.) %>%
  310. # average across stimuli and runs for each participant
  311. # create a new variable that indicated whether stimulus is current or not:
  312. .[, by = .(classification, id, seq_tr, class, stim), .(
  313. num_stim = .N,
  314. mean_prob = mean(probability * 100),
  315. stim_type = ifelse(class == unique(stim), "current", "other")
  316. )] %>%
  317. verify(num_stim <= 96) %>%
  318. # average across stimulus type:
  319. .[, by = .(classification, id, seq_tr, stim, stim_type), .(
  320. num_class = .N,
  321. mean_prob = mean(mean_prob)
  322. )] %>%
  323. verify(num_class %in% c(1, 4)) %>%
  324. select(., -num_class) %>%
  325. # turn into wide format:
  326. spread(key = stim_type, value = mean_prob) %>% setDT(.) %>%
  327. # calculate a paired t-test at every TR for each unique stimulus to
  328. # compare probability of the current vs. the mean other class probabilities:
  329. .[, by = .(classification, seq_tr, stim), {
  330. results = t.test(current, other, paired = TRUE, alternative = "two.sided")
  331. list(
  332. tvalue = results$statistic,
  333. df = results$parameter,
  334. pvalue = results$p.value,
  335. cohens_d = (mean(current) - mean(other)) / sd(current - other),
  336. num_subs = .N,
  337. mean_current = mean(current),
  338. mean_other = mean(other),
  339. pvalue_round = round_pvalues(results$p.value)
  340. )
  341. }] %>%
  342. # ajdust the p-value for multiple comparisons using Bonferroni correction:
  343. .[, by = .(classification), ":=" (
  344. num_comp = .N,
  345. pvalue_adjust = p.adjust(pvalue, method = "bonferroni", n = .N)
  346. )] %>%
  347. # check if correction was done for 5 (stimuli) by 7 (TRs) = 35 comparisons:
  348. verify(all(num_comp == 5 * 7)) %>%
  349. # verify if number of participants matches:
  350. verify(all(num_subs == 36)) %>%
  351. # check if degrees-of-freedom = n - 1
  352. verify(all(df == num_subs - 1)) %>%
  353. # round the bonferroni-adjusted p-values for clarity:
  354. mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
  355. # sort the datatable according to classification, stimulus and TR:
  356. setorder(., classification, stim, seq_tr) %>%
  357. # only look at the fourth TR where probabilities are expected to peak
  358. filter(seq_tr == 4 & classification == "ovr")
  359. # print the data table:
  360. dt_odd_long_mean_stat
  361. ```
  362. ```{r, echo=FALSE, class.source = NULL}
  363. plot_grid(fig_odd_peak, plot.odd.long, labels = c('a', 'b'), ncol = 2,
  364. rel_widths = c(1.5, 5), hjust = c(0, 0), label_fontface = "bold")
  365. ```
  366. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  367. ggsave(filename = "highspeed_plot_decoding_oddball.pdf",
  368. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  369. dpi = "retina", width = 6.5, height = 3.5)
  370. ggsave(filename = "wittkuhn_schuck_figure_s2.pdf",
  371. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  372. dpi = "retina", width = 6.5, height = 3.5)
  373. ```
  374. ## Response functions
  375. ### Define response functions
  376. We define the sine wave response function:
  377. ```{r}
  378. sine_truncated <- function(params, time) {
  379. if (!is.list(params)) {
  380. params = as.list(params)
  381. names(params) = c('frequency', 'amplitude', 'shift', 'baseline')
  382. }
  383. y = rep(0, 13)
  384. y = params$amp/2 * sin(2*pi*params$freq*time - 2*pi*params$freq*params$shift - 0.5*pi) + params$baseline + params$amp/2
  385. # flatten response function after one cycle:
  386. y[time < (params$shift)] = params$baseline
  387. y[time > (params$shift + 1/params$freq)] = params$baseline
  388. return(y)
  389. }
  390. ```
  391. We define a function to evaluate during optimization:
  392. ```{r}
  393. sine_truncated_eval = function(params, time, data) {
  394. y = sine_truncated(params, time)
  395. SSE = sum((data - y)^2)
  396. return(SSE)
  397. }
  398. ```
  399. ### Mean parameters
  400. We calculate the multivariate decoding time courses for each stimulus class:
  401. ```{r}
  402. # calculate mean probability for every stimulus for every TR:
  403. dt_odd_long_mean_class = dt_pred %>%
  404. # filter for oddball long predictions and only select ovr classifier:
  405. filter(test_set == "test-odd_long" & class != "other" &
  406. classification == "ovr" & mask == "cv") %>%
  407. # filter out excluded participants:
  408. filter(!(id %in% sub_exclude)) %>% setDT(.) %>%
  409. # select only predictions for the class currently shown on any given trial:
  410. filter(class == stim) %>% setDT(.) %>%
  411. # average the probability across trials
  412. .[, by = .(id, classification, stim, seq_tr), .(
  413. mean_probability = mean(probability, na.rm = TRUE)
  414. )]
  415. ```
  416. We fit the truncated sine wave response function to data from every participant:
  417. ```{r, results="hold"}
  418. # set optimization parameters:
  419. opts = list("algorithm" = "NLOPT_LN_COBYLA", "xtol_rel" = 1.0e-8, "maxeval" = 1.0e+5)
  420. default_params = c(0.2, 0.6, 0, 0.1)
  421. lower_bounds = c(0.01, 0.1, 0, 0)
  422. upper_bounds = c(0.5, 1, 5, 0.3)
  423. time = 0:6
  424. dt_odd_long_fit = dt_odd_long_mean_class %>%
  425. # fit the truncated sine wave to probabilities of every decoding timecourse:
  426. .[, by = .(id, classification, stim), {
  427. res <- nloptr(
  428. x0 = default_params, eval_f = sine_truncated_eval, time = time,
  429. data = mean_probability, lb = lower_bounds, ub = upper_bounds, opts = opts)
  430. list(
  431. num_trs = .N,
  432. frequency = res$solution[1],
  433. wavelength = 1/res$solution[1],
  434. amplitude = res$solution[2],
  435. shift = res$solution[3],
  436. baseline = res$solution[4],
  437. params = list(res$solution)
  438. )}] %>%
  439. verify(all(num_trs == length(time)))
  440. # print the mean model parameters:
  441. summary(dt_odd_long_fit[, c("frequency", "wavelength", "amplitude", "shift", "baseline")])
  442. # calculate the mean parameters across all participants to be used later:
  443. mean_parameters = dt_odd_long_fit %>%
  444. # average the fitted parameters across participants:
  445. .[, by = .(classification), .(
  446. mean_freq = mean(frequency),
  447. mean_wavelength = mean(wavelength),
  448. mean_amplitude = mean(amplitude),
  449. mean_shift = mean(shift),
  450. mean_baseline = mean(baseline)
  451. )]
  452. ```
  453. We fit the sine response function for every participant using individual parameters:
  454. ```{r}
  455. dt_odd_long_fit_single = dt_odd_long_fit %>%
  456. # calculate a sine-wave respone function timecourse for every participant:
  457. .[, by = .(id, classification, stim), .(
  458. csine = sine_truncated(params = unlist(params), seq(0, 6, 0.1))
  459. )] %>%
  460. # add a counter that is used for plotting below:
  461. .[, by = .(classification, id, stim), ":=" (
  462. t = seq(0, 6, 0.1))]
  463. ```
  464. We plot the single sine wave fits for three example participants (supplement):
  465. ```{r, echo=FALSE}
  466. # set seed to reproduce random id sampling:
  467. set.seed(18)
  468. num_sub_select = 3
  469. # select random example participants:
  470. select_id = sample(x = unique(dt_odd_long_fit_single$id), size = num_sub_select)
  471. fig_s1 = ggplot(data = subset(dt_odd_long_fit_single, id %in% select_id),
  472. aes(x = t, y = csine * 100)) +
  473. geom_point(aes(color = "Model"), alpha = 0.5) +
  474. geom_line(data = subset(dt_odd_long_mean_class, id %in% select_id),
  475. aes(x = (seq_tr - 1), y = mean_probability * 100, color = "Data")) +
  476. geom_point(data = subset(dt_odd_long_mean_class, id %in% select_id),
  477. aes(x = (seq_tr - 1), y = mean_probability * 100, color = "Data")) +
  478. scale_colour_manual(name = "", values = c("gray", "black")) +
  479. facet_grid(vars(as.factor(id)), vars(as.factor(stim))) +
  480. coord_capped_cart(left = "both", bottom = "both") +
  481. xlab("Time from stimulus onset (in TRs)") + ylab("Probability (%)") +
  482. scale_x_continuous(labels = label_fill(seq(1, 7, 1), mod = 1), breaks = seq(0, 6, 1)) +
  483. theme(legend.position = "top", legend.direction = "horizontal",
  484. legend.justification = "center", legend.margin = margin(0, 0 ,0, 0),
  485. legend.box.margin = margin(t = 0, r = 0, b = -10, l = 0)) +
  486. theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
  487. theme(axis.title.x = element_blank()) +
  488. theme(axis.line = element_line(colour = "black"),
  489. panel.grid.major = element_blank(),
  490. panel.grid.minor = element_blank(),
  491. panel.border = element_blank(),
  492. panel.background = element_blank())
  493. fig_s1
  494. ```
  495. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  496. ggsave(filename = "highsspeed_plot_decoding_oddball_single_sine_fits.pdf",
  497. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  498. dpi = "retina", width = 6, height = 4)
  499. ```
  500. We calculate the mean shape of the sine wave response function across participants:
  501. ```{r, echo=TRUE}
  502. dt_odd_long_fit_mean = dt_odd_long_fit %>%
  503. # average the fitting parameters across participants:
  504. .[, by = .(classification, stim), .(
  505. num_subs = .N,
  506. mean_freq = mean(frequency),
  507. mean_amplitude = mean(amplitude),
  508. mean_shift = mean(shift),
  509. mean_baseline = mean(baseline)
  510. )] %>% verify(all(num_subs == 36)) %>%
  511. .[, by = .(classification, stim), .(
  512. csine = sine_truncated(params = c(mean_freq, mean_amplitude, mean_shift, mean_baseline),
  513. seq(0, 6, 0.1)))] %>%
  514. .[, by = .(classification, stim), ":=" (
  515. t = seq(0, 6, 0.1))]
  516. ```
  517. ```{r, echo=TRUE}
  518. fig_s2 = ggplot(data = dt_odd_long_mean_class) +
  519. geom_line(data = dt_odd_long_mean_class,
  520. aes(x = (seq_tr - 1), y = mean_probability * 100, group = as.factor(id),
  521. color = "Data"), alpha = 0.3) +
  522. geom_line(data = dt_odd_long_fit_mean, aes(x = t, y = csine * 100, color = "Model"),
  523. size = 1) +
  524. facet_wrap(~ as.factor(stim), nrow = 1) +
  525. coord_capped_cart(left = "both", bottom = "both", ylim = c(0, 80)) +
  526. xlab("Time from stimulus onset (in TRs)") + ylab("Probability (%)") +
  527. scale_colour_manual(name = "", values = c("gray", "black"), guide = FALSE) +
  528. scale_x_continuous(labels = label_fill(seq(1, 7, 1), mod = 1), breaks = seq(0, 6, 1)) +
  529. #theme(plot.margin = unit(c(t = 1, r = 1, b = 1, l = 1), "pt")) +
  530. theme(legend.position = "bottom", legend.direction = "horizontal",
  531. legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
  532. legend.box.margin = margin(t = -10, r = 0, b = 0, l = 0)) +
  533. theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
  534. theme(axis.line = element_line(colour = "black"),
  535. panel.grid.major = element_blank(),
  536. panel.grid.minor = element_blank(),
  537. panel.border = element_blank(),
  538. panel.background = element_blank())
  539. fig_s2
  540. ```
  541. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  542. ggsave(filename = "highsspeed_plot_decoding_oddball_mean_sine_fits.pdf",
  543. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  544. dpi = "retina", width = 5, height = 3)
  545. ```
  546. ```{r}
  547. plot_grid(fig_s1, fig_s2, nrow = 2, ncol = 1, hjust = c(0, 0),
  548. rel_heights = c(4, 2), labels = c("a", "b"), label_fontface = "bold")
  549. ```
  550. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  551. ggsave(filename = "highsspeed_plot_decoding_oddball_supplement.pdf",
  552. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  553. dpi = "retina", width = 7, height = 7)
  554. ggsave(filename = "wittkuhn_schuck_figure_s4.pdf",
  555. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  556. dpi = "retina", width = 7, height = 7)
  557. ```
  558. We evaluate the response function for two time-shifted events:
  559. ```{r}
  560. # horizontal phase shift (in TRs) that will be added to the second sine wave:
  561. add_shift = 0.5
  562. time = seq(0, 8, 0.1)
  563. dt_odd_long_fit_shift = dt_odd_long_fit %>%
  564. # average the fitted parameters across participants:
  565. .[, by = .(classification), .(
  566. mean_freq = mean(frequency),
  567. mean_amplitude = mean(amplitude),
  568. mean_shift = mean(shift),
  569. mean_baseline = mean(baseline)
  570. )] %>%
  571. # add variable that adds shift (in TRs) for the second sine wave:
  572. mutate(mean_shift_shifted = mean_shift + add_shift) %>%
  573. mutate(amp = mean_amplitude * sin(add_shift * mean_freq * pi)) %>%
  574. mutate(freq = mean_freq / (add_shift * mean_freq + 1)) %>% setDT(.) %>%
  575. # calculate first and second response filter time-courses
  576. .[, by = .(classification), .(
  577. first = sine_truncated(params = c(mean_freq, mean_amplitude, mean_shift, mean_baseline), time),
  578. second = sine_truncated(params = c(mean_freq, mean_amplitude, mean_shift_shifted, mean_baseline), time),
  579. cosine = amp * sin(2 * pi * time * freq - 2 * pi * freq * mean_shift),
  580. t = time
  581. )] %>%
  582. # shift the cosine wave by 0.6 (only for illustrational purposes):
  583. mutate(cosine = cosine - 0.6) %>% setDT(.) %>%
  584. # signify the tails of the sine wave (that will otherwise be flattened):
  585. .[time < mean_parameters$mean_shift, ":=" (
  586. cosine_tails = cosine,
  587. cosine = NA)] %>%
  588. .[time > (mean_parameters$mean_shift + add_shift + 1/mean_parameters$mean_freq), ":=" (
  589. cosine_tails = cosine,
  590. cosine = -NA)] %>%
  591. # calculate the difference between the first and second wave
  592. # adjust by 0.2 for illustrational purposes only:
  593. mutate(difference = first - second - 0.2) %>%
  594. gather(key = "event", value = "probability", -classification, -t)
  595. ```
  596. We calculate the mean probability timecourses for the true stimulus class across participants:
  597. ```{r, ech0=TRUE}
  598. # calculate the mean decoding time course across all classes and participants:
  599. dt_odd_long_mean = dt_pred %>%
  600. # filter for oddball multivariate data and one-versus-rest classification only:
  601. filter(test_set == "test-odd_long" & class != "other" &
  602. classification == "ovr" & mask == "cv") %>%
  603. # filter out excluded participants:
  604. filter(!(id %in% sub_exclude)) %>%
  605. # filter for all trials where the predicted class matches the true stimulus:
  606. filter(class == stim) %>% setDT(.) %>%
  607. # calculate the mean decoding time courses for each participant:
  608. .[, by = .(id, classification, stim, seq_tr), .(
  609. mean_probability = mean(probability, na.rm = TRUE) * 100)] %>%
  610. # average mean decoding time courses across participants:
  611. .[, by = .(classification, stim, seq_tr), .(
  612. mean_probability = mean(mean_probability),
  613. num_subs = .N,
  614. sem_upper = mean(mean_probability) + (sd(mean_probability)/sqrt(.N)),
  615. sem_lower = mean(mean_probability) - (sd(mean_probability)/sqrt(.N))
  616. )] %>% verify(all(num_subs == 36))
  617. ```
  618. ```{r, echo=FALSE}
  619. fig_a = ggplot(data = dt_odd_long_mean, aes(
  620. x = (seq_tr - 1), y = mean_probability, group = stim)) +
  621. geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.1, color = NA) +
  622. geom_line(mapping = aes(color = "Data"), alpha = 1) +
  623. geom_point(mapping = aes(color = "Data"), alpha = 1, pch = 16) +
  624. geom_line(data = subset(dt_odd_long_fit_shift, event == "first"),
  625. aes(x = t, y = probability * 100, color = "Model"),
  626. size = 2, inherit.aes = FALSE) +
  627. coord_capped_cart(left = "both", bottom = "both", ylim = c(0, 100),
  628. xlim = c(0, 6), expand = TRUE) +
  629. xlab("Time (TRs)") + ylab("Probability (%)") +
  630. scale_colour_manual(name = "", values = c("gray", "black")) +
  631. scale_fill_manual(name = "", values = c("gray", "black")) +
  632. scale_x_continuous(labels = label_fill(seq(1,7,1), mod = 1), breaks = seq(0,6,1)) +
  633. scale_y_continuous(labels = label_fill(seq(0, 80, 20), mod = 1), breaks = seq(0, 80, 20)) +
  634. theme(legend.position = "top", legend.direction = "horizontal",
  635. legend.justification = "center", legend.margin = margin(0, 0 ,0, 0),
  636. legend.box.margin = margin(t = 0, r = 0, b = -60, l = 0)) +
  637. guides(color = guide_legend(ncol = 2)) +
  638. # add the shift parameter:
  639. geom_segment(aes(x = 0, xend = mean_parameters$mean_shift, yend = 65, y = 65),
  640. color = "darkgray", linetype = "dashed") +
  641. annotate("text", x = mean_parameters$mean_shift/2, y = 70,
  642. label = paste("italic(d)"), color = "darkgray", hjust = 0.5, parse = TRUE) +
  643. # add the wavelength parameter:
  644. geom_segment(aes(x = mean_parameters$mean_shift, xend = mean_parameters$mean_shift + 1/mean_parameters$mean_freq,
  645. yend = 70, y = 70), color = "darkgray", linetype = "dashed") +
  646. annotate("text", x = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq), y = 75,
  647. label = paste("italic(lambda)"), color = "darkgray", hjust = 0.5, parse = TRUE) +
  648. # add the amplitude parameter:
  649. geom_segment(aes(x = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq),
  650. xend = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq),
  651. y = mean_parameters$mean_baseline * 100,
  652. yend = (mean_parameters$mean_baseline + mean_parameters$mean_amplitude) * 100),
  653. color = "darkgray", linetype = "dashed") +
  654. annotate("text", x = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq) - 0.25,
  655. y = (mean_parameters$mean_baseline + mean_parameters$mean_amplitude / 2) * 100,
  656. label = paste("italic(A)"), color = "darkgray", parse = TRUE) +
  657. # add the baseline parameter:
  658. geom_segment(aes(x = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq) - 0.2,
  659. xend = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq) - 0.2,
  660. y = 0, yend = mean_parameters$mean_baseline * 100),
  661. color = "darkgray", linetype = "dashed") +
  662. annotate("text", x = mean_parameters$mean_shift + 1/(2 * mean_parameters$mean_freq) - 0.25 - 0.2,
  663. y = mean_parameters$mean_baseline / 2 * 100,
  664. label = paste("italic(b)"), color = "darkgray", parse = TRUE) +
  665. annotate("text", x = 6, y = 0, label = "1 TR = 1.25 s",
  666. hjust = 1, size = rel(2)) +
  667. theme(axis.line = element_line(colour = "black"),
  668. panel.grid.major = element_blank(),
  669. panel.grid.minor = element_blank(),
  670. panel.border = element_blank(),
  671. panel.background = element_blank())
  672. fig_a
  673. ```
  674. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  675. ggsave(filename = "highspeed_plot_decoding_oddball_sine_illustration_fig1.pdf",
  676. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  677. dpi = "retina", width = 3, height = 3.1)
  678. ```
  679. ### Illustration of response model and difference dynamics
  680. ```{r, echo=TRUE}
  681. # get the average horizonzal shift of the first sine wave:
  682. t_first = mean(dt_odd_long_fit$shift)
  683. # calculate the shift of the second sine wave (shift + half a wavelength)
  684. t_second = mean(dt_odd_long_fit$shift) + add_shift + 1/mean(dt_odd_long_fit$freq)
  685. # calculate the time point of cross over between the two sine functions:
  686. t_crossover = 0.5/mean(dt_odd_long_fit$freq) + mean(dt_odd_long_fit$shift) + add_shift/2
  687. # create a vector of time steps:
  688. time = seq(0, 8, 0.1)
  689. max_prob = max(dt_odd_long_fit_shift$probability[dt_odd_long_fit_shift$event == "difference"], na.rm = TRUE)
  690. t_max_diff = dt_odd_long_fit_shift$t[dt_odd_long_fit_shift$probability == max_prob & !is.na(dt_odd_long_fit_shift$probability)]
  691. a = dt_odd_long_fit_shift$probability[dt_odd_long_fit_shift$t == t_max_diff & dt_odd_long_fit_shift$event == "first"]
  692. ```
  693. ```{r, echo=TRUE}
  694. # select colors used for plotting:
  695. colors = c("darkgray", "darkgray", "black", "dodgerblue", "red", "black")
  696. # plot sine wave difference illustration:
  697. fig_b = ggplot(data = dt_odd_long_fit_shift, aes(x = time, y = probability * 100, group = event, color = event)) +
  698. # create rectangles to indicate the forward and backward phase:
  699. annotate("rect", xmin = t_first, xmax = t_crossover, ymin = -80, ymax = 95, alpha = 0.05, fill = "dodgerblue") +
  700. annotate("rect", xmin = t_crossover, xmax = t_second, ymin = -80, ymax = 95, alpha = 0.05, fill = "red") +
  701. # plot the onset of the first event:
  702. geom_vline(xintercept = t_first, color = "gray", linetype = "dashed") +
  703. # plot the offset of the second event (d + lambda + s in manuscript text):
  704. geom_vline(xintercept = t_second, color = "gray", linetype = "dashed") +
  705. # plot the crossover (d + 0.5 * (lambda + s) in manuscript text)
  706. geom_vline(xintercept = t_crossover, color = "gray", linetype = "dashed") +
  707. # plot the horizontal line for the difference time course:
  708. geom_hline(yintercept = -20, color = "gray") +
  709. # plot the horizontal line for the cosine wave:
  710. geom_hline(yintercept = -60, color = "gray") +
  711. # plot the first and second event time courses:
  712. geom_line(aes(x = t, linetype = fct_rev(event)), na.rm = TRUE) +
  713. coord_capped_cart(left = "both", bottom = "both", ylim = c(-80, 85)) +
  714. xlab("Time (in TRs)") + ylab("Probability (a.u.)") +
  715. scale_colour_manual(name = "Serial event", values = colors) +
  716. scale_linetype_manual(values = c(rep("solid", 3), "dashed", "solid")) +
  717. scale_x_continuous(labels = c(label_fill(seq(1,7,1), mod = 1), rep("", 2)), breaks = seq(0,8,1)) +
  718. theme(axis.text.y = element_blank(), axis.ticks.y = element_line(colour = "white"),
  719. axis.line.y = element_line(colour = "white")) +
  720. theme(legend.position = "none") +
  721. annotate("label", x = 0.5, y = 45, label = "~1^'st'~event", color = "dodgerblue", parse = TRUE, size = 3) +
  722. annotate("label", x = 0.5, y = 30, label = "~2^'nd'~event", color = "red", parse = TRUE, size = 3) +
  723. annotate("label", x = 0.5, y = -7, label = "Difference", color = "black", size = 3) +
  724. annotate("label", x = 0.5, y = -35, label = "Difference\n(no flattening)", color = "darkgray", size = 3) +
  725. # add annotation for forward period:
  726. geom_segment(aes(x = t_first, xend = t_crossover, yend = 85, y = 85,
  727. colour = "segment"), color = "dodgerblue") +
  728. annotate("label", x = t_first + (t_crossover - t_first)/2, y = 85, label = "Forward period",
  729. color = "dodgerblue", fill = "white", hjust = 0.5) +
  730. # add annotations for backward period:
  731. geom_segment(aes(x = t_crossover, xend = t_second, y = 85, yend = 85,
  732. colour = "segment"), color = "red") +
  733. annotate("label", x = t_crossover + (t_second - t_crossover)/2, y = 85, label = "Backward period",
  734. color = "red", fill = "white", hjust = 0.5) +
  735. # add annotation for early forward phase
  736. geom_segment(aes(x = t_first, xend = t_first + (t_crossover - t_first)/2, yend = 70, y = 70,
  737. colour = "segment"), color = "gray") +
  738. annotate("label", x = (t_first + (t_crossover - t_first)/4),
  739. y = 70, label = "early",
  740. color = "gray", fill = "white", hjust = 0.5) +
  741. # add annotation for late forward phase
  742. geom_segment(aes(x = t_first + (t_crossover - t_first)/2, xend = t_crossover,
  743. yend = 68, y = 68, colour = "segment"), color = "gray") +
  744. annotate("label", x = (t_first + (t_crossover - t_first)/4 * 3),
  745. y = 68, label = "late",
  746. color = "gray", fill = "white", hjust = 0.5) +
  747. # add annotation for early backward phase
  748. geom_segment(aes(x = t_crossover, xend = t_crossover + (t_second - t_crossover)/2,
  749. yend = 70, y = 70, colour = "segment"), color = "gray") +
  750. annotate("label", x = (t_crossover + (t_second - t_crossover)/4),
  751. y = 70, label = "early", color = "gray", fill = "white", hjust = 0.5) +
  752. # add annotation for late backward phase
  753. geom_segment(aes(x = t_crossover + (t_second - t_crossover)/2, xend = t_second,
  754. yend = 68, y = 68, colour = "segment"), color = "gray") +
  755. annotate("label", x = (t_crossover + (t_second - t_crossover)/4 * 3),
  756. y = 68, label = "late", color = "gray", fill = "white", hjust = 0.5) +
  757. # add annotation for delta:
  758. geom_segment(aes(x = t_first + (t_crossover - t_first)/2,
  759. xend = t_first + (t_crossover - t_first)/2 + add_shift,
  760. y = 35 , yend = 35, colour = "segment"), color = "darkgray", linetype = "dotted") +
  761. annotate("text", x = t_first + (t_crossover - t_first)/2 - 0.5 + add_shift / 2, y = 36,
  762. label = "delta", color = "darkgray", hjust = 0.5, parse = TRUE) +
  763. # add annotation for TRs:
  764. annotate("text", x = 8, y = -80, label = "1 TR = 1.25 s",
  765. hjust = 1, size = rel(2)) +
  766. theme(axis.line.x = element_line(colour = "black"),
  767. panel.grid.major = element_blank(),
  768. panel.grid.minor = element_blank(),
  769. panel.border = element_blank(),
  770. panel.background = element_blank())
  771. fig_b
  772. ```
  773. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  774. ggsave(filename = "highspeed_plot_decoding_oddball_sine_illustration_figb.pdf",
  775. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  776. dpi = "retina", width = 5.5, height = 4.5)
  777. ```
  778. ```{r, echo = TRUE}
  779. ampfun = function(s, A) {A*cos(s*mean(dt_odd_long_fit$freq)*pi - 0.5*pi)}
  780. cs = seq(0, 0.5/mean(dt_odd_long_fit$freq), 0.01)
  781. ba = data.table(cs = cs, diff = ampfun(cs, 1))
  782. fig_c = ggplot(data = ba, aes(x = cs, y = diff)) +
  783. geom_line() +
  784. xlab("Time-shift (in TRs)") + ylab("Max. diff. in amplitude") +
  785. coord_capped_cart(left = "both", bottom = "both", xlim = c(0,3)) +
  786. scale_x_continuous(labels = c("0", rep("", 2), "Resp. peak"), breaks = seq(0,3)) +
  787. scale_y_continuous(labels = c("0", rep("", 3), "Max")) +
  788. #theme(plot.margin = unit(c(t = 10, r = 30, b = 1, l = 1), "pt")) +
  789. theme(axis.text.y = element_text(angle = 90, hjust = 0.5)) +
  790. theme(axis.line = element_line(colour = "black"),
  791. panel.grid.major = element_blank(),
  792. panel.grid.minor = element_blank(),
  793. panel.border = element_blank(),
  794. panel.background = element_blank())
  795. fig_c
  796. ```
  797. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  798. ggsave(filename = "highspeed_plot_decoding_oddball_sine_illustration_fig3.pdf",
  799. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  800. dpi = "retina", width = 3, height = 2.5)
  801. ```
  802. We plot a combination of the previous plots:
  803. ```{r, echo=TRUE, fig.width = 7, fig.height = 3}
  804. plot_grid(fig_a, fig_b, ncol = 2, hjust = c(0,0),
  805. labels = c("c", "d", "e"), rel_widths = c(2, 4),
  806. label_fontface = "bold", label_size = 14)
  807. ```
  808. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  809. ggsave(filename = "highspeed_plot_decoding_oddball_sine_illustration.pdf",
  810. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  811. dpi = "retina", width = 7, height = 3)
  812. ```
  813. ### Forward and backward period for different speeds
  814. Here, we calculate the expected forward and backward periods for two events
  815. separated by different speed levels (different values for delta).
  816. ```{r}
  817. calc_periods = function(speed, stim_dur = 0.1, num_stim = 5, tr = 1.25){
  818. # mean wavelength based on fitted models, in TRs, round to two digits:
  819. lambda = round(mean_parameters$mean_wavelength, 2)
  820. # mean phase-shift based on fitted models, in TRs, round to two digits:
  821. phase_shift = round(mean_parameters$mean_shift, 2)
  822. # delta, in sec (time shift depending on speed)
  823. delta_sec = stim_dur * (num_stim - 1) + (num_stim - 1) * speed
  824. # delta, in trs:
  825. delta_tr = delta_sec / tr
  826. # end of forward period, in TRs:
  827. fwd_end = 0.5 * (lambda + delta_tr) + phase_shift
  828. # end of the backward period, in TRs:
  829. bwd_end = lambda + phase_shift + delta_tr
  830. # forward period, in TRs:
  831. forward = list(seq(round(phase_shift) + 1, round(fwd_end - 0.5) + 1))
  832. # backward period, in TRs:
  833. backward = list(seq(round(fwd_end + 0.5) + 1, round(bwd_end) + 1))
  834. # entire relevant trial period, in rounded TRs:
  835. trial_period = list(seq(round(phase_shift) + 1, round(lambda + phase_shift + delta_tr) + 1))
  836. # save results as a data table and return results:
  837. dt = data.table(speed, lambda, delta_sec, delta_tr, fwd_end, bwd_end, forward, backward, trial_period)
  838. return(dt)
  839. }
  840. # get the relevant timeperiods for each sequence speed condition:
  841. speeds = c(0.032, 0.064, 0.128, 0.512, 2.048)
  842. dt_periods = do.call(rbind, lapply(speeds, calc_periods))
  843. dt_periods
  844. ```
  845. We save the periods of interest for different speeds which will be used for the analysis of sequence and repetition trials:
  846. ```{r}
  847. save(dt_periods, file = file.path(path_root, 'data', "tmp", "dt_periods.Rdata"))
  848. ```
  849. ### Probability differences at different speeds
  850. We plot the probability differences at different speeds:
  851. ```{r}
  852. time = seq(0, 12, 0.1)
  853. speeds = c(0.032, 0.064, 0.128, 0.512, 2.048)
  854. stim_dur = 0.1
  855. dt_odd_seq_sim = dt_odd_long_fit %>% setDT(.) %>%
  856. # average the fitted parameters across stimuli for each participant:
  857. .[, by = .(classification, id), .(
  858. mean_freq = mean(frequency),
  859. mean_amplitude = mean(amplitude),
  860. mean_shift = mean(shift),
  861. mean_baseline = mean(baseline)
  862. )] %>%
  863. # repeat rows once for each speed condition (5 repetitions in total):
  864. .[rep(seq(1, nrow(.)), length(speeds))] %>%
  865. # add the different speed conditions for every participant:
  866. .[, by = .(classification, id), ":=" (speed = speeds, num_events = 1)] %>%
  867. # repeat rows once for each hypothetical event (here, 2 events):
  868. .[rep(seq(1, nrow(.)), num_events)] %>%
  869. # add a counter for the number of events:
  870. .[, by = .(classification, id, speed), ":=" (event = 5)] %>%
  871. # calculate a speed-specific shift for each event:
  872. .[, by = .(classification, id, speed, event), {
  873. shift = (speed * (event - 1) + (event - 1) * stim_dur)/1.25
  874. amp = mean_amplitude * sin(shift * 0.5 * mean_freq * pi)
  875. freq = (1/(1/mean_freq + shift))
  876. c_d = amp * sin(2 * pi * time * freq - 2 * pi * freq * mean_shift)
  877. # flatten the tails of the response function
  878. cidx = time < mean_shift | time > (mean_shift + (1 / freq))
  879. c_d[cidx] = 0
  880. # save the response function:
  881. list(time = time, probability = c_d)
  882. }] %>%
  883. filter(classification == "ovr") %>% setDT(.)
  884. dt_odd_seq_sim_diff = dt_odd_seq_sim %>%
  885. .[, by = .(classification, speed, time), .(
  886. num_subs = .N,
  887. mean_difference = mean(probability),
  888. sem_upper = mean(probability) + (sd(probability)/sqrt(.N)),
  889. sem_lower = mean(probability) - (sd(probability)/sqrt(.N))
  890. )] %>% verify(all(num_subs == 36))
  891. ```
  892. ```{r}
  893. save(dt_odd_seq_sim_diff, file = file.path(
  894. path_root, "data", "tmp", "dt_odd_seq_sim_diff.Rdata"))
  895. save(dt_odd_seq_sim, file = file.path(
  896. path_root, "data", "tmp", "dt_odd_seq_sim.Rdata"))
  897. ```
  898. ```{r}
  899. fig_seq_sim_diff = ggplot(data = dt_odd_seq_sim_diff, mapping = aes(
  900. x = time, y = as.numeric(mean_difference), color = as.factor(speed * 1000),
  901. fill = as.factor(speed * 1000))) +
  902. geom_hline(aes(yintercept = 0), linetype = "solid", color = "gray") +
  903. geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.5, color = NA) +
  904. geom_line() +
  905. theme(legend.position = "top", legend.direction = "horizontal",
  906. legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
  907. legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
  908. xlab("Time from sequence onset (TRs)") + ylab("Probability difference") +
  909. scale_colour_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
  910. scale_fill_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
  911. coord_capped_cart(left = "both", bottom = "both", expand = TRUE,
  912. ylim = c(-0.4, 0.4), xlim = c(0, 12)) +
  913. scale_x_continuous(labels = label_fill(seq(1, 13, 1), mod = 4), breaks = seq(0, 12, 1)) +
  914. guides(color = guide_legend(nrow = 1)) +
  915. geom_segment(aes(x = 0, xend = 0, y = 0.01, yend = 0.4),
  916. arrow = arrow(length = unit(5, "pt")), color = "darkgray") +
  917. geom_segment(aes(x = 0, xend = 0, y = -0.01, yend = -0.4),
  918. arrow = arrow(length = unit(5, "pt")), color = "darkgray") +
  919. annotate(geom = "text", x = 0.4, y = 0.2, label = "Forward order",
  920. color = "darkgray", angle = 90, size = 3) +
  921. annotate(geom = "text", x = 0.4, y = -0.2, label = "Backward order",
  922. color = "darkgray", angle = 90, size = 3) +
  923. annotate("text", x = 12, y = -0.4, label = "1 TR = 1.25 s",
  924. hjust = 1, size = rel(2)) +
  925. theme(axis.line = element_line(colour = "black"),
  926. panel.grid.major = element_blank(),
  927. panel.grid.minor = element_blank(),
  928. panel.border = element_blank(),
  929. panel.background = element_blank())
  930. fig_seq_sim_diff
  931. ```
  932. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  933. ggsave(filename = "highspeed_plot_decoding_oddball_sequence_predictions.pdf",
  934. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  935. dpi = "retina", width = 5.5, height = 3)
  936. ```
  937. ```{r, echo=TRUE, fig.width=8, fig.height=3}
  938. upper = plot_grid(fig_odd_peak, plot.odd.long, fig_a, labels = c("a", "b", "c"), ncol = 3,
  939. rel_widths = c(0.8, 3.5, 2), label_fontface = "bold", hjust = c(0, 0))
  940. lower = plot_grid(fig_b, fig_seq_sim_diff, labels = c("d", "e"), nrow = 1,
  941. label_fontface = "bold", hjust = c(0, 0), rel_widths = c(0.45, 0.53))
  942. plot_grid(upper, lower, nrow = 2, ncol = 1, rel_heights = c(2.5, 3))
  943. ```
  944. ```{r, include=FALSE, eval=FALSE, echo=FALSE}
  945. ggsave(filename = "highspeed_plot_decoding_oddball_data.pdf",
  946. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  947. dpi = "retina", width = 10, height = 6.5)
  948. ggsave(filename = "wittkuhn_schuck_figure_2.pdf",
  949. plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
  950. dpi = "retina", width = 10, height = 6.5)
  951. ```