2 Results
2.1 Overview
The following sections contain the main results reported in the paper.
The results are shown next to the R
code that produced them.
Specifically, all code is written in RMarkdown
which allows to combine analysis code with regular text.
The rendered RMarkdown
files you see here, were generated automatically based on data stored in the relevant data sets listed above using continuous integration in the associated GitLab repository.
This means that upon any change to the analysis code, the relevant data is retrieved from the relevant sub-directories and all analyses are re-generated.
Please see the following sections for details on the computational environment and the continuous integration pipeline.
2.1.0.1 Computational environment
Below we show the version information about R, the operating system (OS) and attached or loaded R packages using sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Debian GNU/Linux bullseye/sid
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.10.so
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## loaded via a namespace (and not attached):
## [1] compiler_4.0.3 magrittr_1.5 bookdown_0.21 htmltools_0.5.0
## [5] tools_4.0.3 rstudioapi_0.11 yaml_2.2.1 stringi_1.5.3
## [9] rmarkdown_2.5 knitr_1.30 stringr_1.4.0 digest_0.6.27
## [13] xfun_0.18 rlang_0.4.8 evaluate_0.14
2.1.0.2 Continuous integration
Below we show the continuous integration script that was used to automatically generate this project website. This is made possible by using thee great tools DataLad and bookdown.
stages:
- retrieve
- publish
datalad:
stage: retrieve
image:
name: registry.git.mpib-berlin.mpg.de/wittkuhn/highspeed/datalad:0.13.5
entrypoint: [""]
script:
# rclone configurations:
- rclone config create highspeed-bids seafile url https://keeper.mpdl.mpg.de/ user wittkuhn@mpib-berlin.mpg.de library highspeed-bids pass $CI_KEEPER_PASS
- rclone config create highspeed-analysis seafile url https://keeper.mpdl.mpg.de/ user wittkuhn@mpib-berlin.mpg.de library highspeed-analysis pass $CI_KEEPER_PASS
#- rclone config create highspeed-fmriprep seafile url https://keeper.mpdl.mpg.de/ user wittkuhn@mpib-berlin.mpg.de library highspeed-fmriprep pass $CI_KEEPER_PASS
- rclone config create highspeed-mriqc seafile url https://keeper.mpdl.mpg.de/ user wittkuhn@mpib-berlin.mpg.de library highspeed-mriqc pass $CI_KEEPER_PASS
- rclone config create highspeed-masks seafile url https://keeper.mpdl.mpg.de/ user wittkuhn@mpib-berlin.mpg.de library highspeed-masks pass $CI_KEEPER_PASS
- rclone config create highspeed-glm seafile url https://keeper.mpdl.mpg.de/ user wittkuhn@mpib-berlin.mpg.de library highspeed-glm pass $CI_KEEPER_PASS
# git configuration:
- git config --global user.name "GitLab CI"
- git config --global user.email "ci@git.mpib-berlin.mpg.de"
# remove and install all datasets:
- datalad remove --dataset . bibliography
- datalad clone --dataset . https://github.com/lnnrtwttkhn/bibliography.git
#- datalad remove --dataset . highspeed-analysis
- rm -rf highspeed-analysis
- datalad install --dataset . -r https://github.com/lnnrtwttkhn/highspeed-analysis.git
- datalad remove --dataset . highspeed-bids
- datalad clone --dataset . https://github.com/lnnrtwttkhn/highspeed-bids.git
- datalad remove --dataset . highspeed-fmriprep
- datalad clone --dataset . https://github.com/lnnrtwttkhn/highspeed-fmriprep.git
- datalad remove --dataset . highspeed-mriqc
- datalad clone --dataset . https://github.com/lnnrtwttkhn/highspeed-mriqc.git
- datalad remove --dataset . highspeed-masks
- datalad clone --dataset . https://github.com/lnnrtwttkhn/highspeed-masks.git
- datalad remove --dataset . highspeed-glm
- datalad clone --dataset . https://github.com/lnnrtwttkhn/highspeed-glm.git
- datalad remove --dataset . highspeed-decoding
- datalad clone --dataset . https://github.com/lnnrtwttkhn/highspeed-decoding.git
# add gin siblings for all datasets:
- datalad siblings add --dataset highspeed-bids -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-bids
- datalad siblings add --dataset highspeed-analysis -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-analysis
- datalad siblings add --dataset highspeed-analysis/data/bids -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-bids
- datalad siblings add --dataset highspeed-analysis/data/decoding -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-decoding
#- datalad siblings add --dataset highspeed-fmriprep -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-fmriprep
- datalad siblings add --dataset highspeed-mriqc -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-mriqc
- datalad siblings add --dataset highspeed-masks -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-masks
- datalad siblings add --dataset highspeed-glm -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-glm
- datalad siblings add --dataset highspeed-decoding -s gin --url https://gin.g-node.org/lnnrtwttkhn/highspeed-decoding
# enable keeper siblings for all datasets:
- datalad siblings --dataset highspeed-analysis enable --name keeper
- datalad siblings --dataset highspeed-bids enable --name keeper
- datalad siblings --dataset highspeed-analysis/data/bids enable --name keeper
#- datalad siblings --dataset highspeed-fmriprep enable --name keeper
- datalad siblings --dataset highspeed-mriqc enable --name keeper
- datalad siblings --dataset highspeed-masks enable --name keeper
- datalad siblings --dataset highspeed-glm enable --name keeper
# configure gin and keeper siblings for all datasets:
- datalad siblings --dataset highspeed-bids configure --name origin --publish-depends gin --publish-depends keeper
- datalad siblings --dataset highspeed-analysis configure --name origin --publish-depends gin
- datalad siblings --dataset highspeed-analysis/data/bids configure --name origin --publish-depends gin --publish-depends keeper
#- datalad siblings --dataset highspeed-fmriprep configure --name origin --publish-depends gin --publish-depends keeper
- datalad siblings --dataset highspeed-mriqc configure --name origin --publish-depends gin --publish-depends keeper
- datalad siblings --dataset highspeed-masks configure --name origin --publish-depends gin --publish-depends keeper
- datalad siblings --dataset highspeed-glm configure --name origin --publish-depends gin --publish-depends keeper
- datalad siblings --dataset highspeed-decoding configure --name origin --publish-depends gin
- datalad siblings --dataset highspeed-analysis/data/decoding configure --name origin --publish-depends gin
# get data
- datalad get highspeed-analysis/data/bids/participants.tsv
- datalad get highspeed-analysis/data/bids/sub-*/ses-*/func/*events.tsv || true
- datalad get highspeed-analysis/data/bids/sub-*/ses-*/func/*events.tsv
- datalad get highspeed-analysis/data/decoding/decoding/sub-*/data/*_decoding.csv || true
- datalad get highspeed-analysis/data/decoding/decoding/sub-*/data/*_decoding.csv
- datalad get highspeed-analysis/data/decoding/decoding/*/data/*thresholding.csv || true
- datalad get highspeed-analysis/data/decoding/decoding/*/data/*thresholding.csv
- datalad get highspeed-analysis/data/tmp/dt_pred_conc_slope.Rdata
artifacts:
paths:
- bibliography/code/bibliography.bib
- highspeed-bids/code
- highspeed-analysis/code
- highspeed-analysis/figures
- highspeed-analysis/sourcedata
- highspeed-analysis/data/bids/sub-*/ses-*/func/*events.tsv
- highspeed-analysis/data/decoding/decoding/sub-*/data/*_decoding.csv
- highspeed-analysis/data/decoding/decoding/sub-*/data/*_thresholding.csv
- highspeed-analysis/data/tmp
- highspeed-fmriprep/code
- highspeed-mriqc/code
- highspeed-glm/code
- highspeed-decoding/code
expire_in: 1 hour
only:
- master
pages:
stage: publish
image: registry.git.mpib-berlin.mpg.de/wittkuhn/highspeed/bookdown:latest
script:
- Rscript -e "options(bookdown.render.file_scope = FALSE); bookdown::render_book('index.Rmd', 'all', output_dir = 'public')"
artifacts:
paths:
- public
- highspeed-analysis/figures
- highspeed-analysis/sourcedata
only:
- master
2.2 Behavior
2.2.1 Initialization
2.2.1.1 Load data and files
We set the paths and source the basic setup script:
::opts_chunk$set(echo = TRUE)
knitr# find the path to the root of this project:
if (!requireNamespace("here")) install.packages("here")
if ( basename(here::here()) == "highspeed" ) {
here::here("highspeed-analysis")
path_root =else {
} here::here()
path_root =
}# source all relevant functions from the setup R script:
source(file.path(path_root, "code", "highspeed-analysis-setup.R"))
2.2.1.2 Signal-detection labeling
We assign labels from signal detection theory that will be used in one of the analyses below:
# denotes misses (key was not pressed and stimulus was upside-down):
$sdt_type[
dt_events$key_down == 0 & dt_events$stim_orient == 180] <- "miss"
dt_events# denotes hits (key was pressed and stimulus was upside-down):
$sdt_type[
dt_events$key_down == 1 & dt_events$stim_orient == 180] <- "hit"
dt_events# denotes correct rejection (key was not pressed and stimulus was upright):
$sdt_type[
dt_events$key_down == 0 & dt_events$stim_orient == 0] <- "correct rejection"
dt_events# denotes false alarms (key was pressed and stimulus was upright):
$sdt_type[
dt_events$key_down == 1 & dt_events$stim_orient == 0] <- "false alarm" dt_events
2.2.2 Stimulus timings
We calculate the differences between consecutive stimulus onsets:
%>%
dt_events # get duration of stimuli by calculating differences between consecutive onsets:
.[, duration_check := shift(onset, type = "lead") - onset,
.(subject, run_study)] %>%
by = # get the difference between the expected and actual stimulus duration:
.[, duration_diff := duration_check - duration, by = .(subject, run_study)] %>%
# for each condition and trial check participants' responses:
.[, by = .(subject, condition, trial), ":=" (
# for each trial check if a key has been pressed:
trial_key_down = ifelse(any(key_down == 1, na.rm = TRUE), 1, 0),
# for each trial check if the participant was accurate:
trial_accuracy = ifelse(any(accuracy == 1, na.rm = TRUE), 1, 0)
%>%
)] .[, trial_type := factor(trial_type, levels = rev(unique(trial_type)))]
dt_events %>%
timings_summary = filter(condition %in% c("sequence", "repetition") & trial_type == "interval") %>%
setDT(.) %>%
.[, by = .(subject, condition, trial_type), {
t.test(duration_diff, mu = 0.001, alternative = "two.sided")
results =list(
mean = mean(duration_diff, na.rm = TRUE),
sd = sd(duration_diff, na.rm = TRUE),
min = min(duration_diff, na.rm = TRUE),
max = max(duration_diff, na.rm = TRUE),
num = .N,
tvalue = results$statistic,
df = results$parameter,
pvalue = results$p.value,
pvalue_round = round_pvalues(results$p.value)
)%>%
}] .[, trial_type := factor(trial_type, levels = rev(unique(trial_type)))] %>%
setorder(., condition, trial_type)
::paged_table(timings_summary) rmarkdown
We plot the differences in expected versus actual timings of individual stimuli in the behavioral task:
ggplot(data = dt_events, aes(
y = as.numeric(duration_diff),
x = as.factor(trial_type),
fill = as.factor(trial_type)), na.rm = TRUE) +
facet_grid(vars(as.factor(trial_key_down)), vars(as.factor(condition))) +
geom_point(
aes(y = as.numeric(duration_diff), color = as.factor(trial_type)),
position = position_jitter(width = .15), size = .5, alpha = 1, na.rm = TRUE) +
geom_boxplot(width = .1, outlier.shape = NA, alpha = 0.5, na.rm = TRUE) +
scale_color_brewer(palette = "Spectral") +
scale_fill_brewer(palette = "Spectral") +
#coord_capped_flip(left = "both", bottom = "both", expand = TRUE) +
coord_flip() +
theme(legend.position = "none") +
xlab("Trial event (in serial order)") +
ylab("Difference between expected and actual timing (in s)") +
theme(strip.text = element_text(margin = margin(unit(c(t = 2, r = 2, b = 2, l = 2), "pt")))) +
theme(legend.position = "none") +
theme(panel.background = element_blank())
We check the timing of the inter-trial interval on oddball trials:
dt_events %>%
dt_odd_iti_mean = # filter for the stimulus intervals on oddball trials:
filter(condition == "oddball" & trial_type == "interval") %>%
setDT(.) %>%
# calculate the mean duration of the oddball intervals for each participant:
.[, by = .(subject), .(
mean_duration = mean(duration, na.rm = TRUE),
num_trials = .N
%>%
)] verify(num_trials == 600)
::paged_table(head(dt_odd_iti_mean)) rmarkdown
2.2.3 Behavioral performance
2.2.3.1 Mean accuracy
We calculate the mean behavioral accuracy across all trials of all three task conditions (slow, sequence, and repetition trials):
# behavioral chance level is at 50%:
50
chance_level = dt_events %>%
dt_acc = # filter out all events that are not related to a participants' response:
filter(!is.nan(accuracy)) %>%
# filter for only upside down stimuli on slow trials:
filter(!(condition == "oddball" & stim_orient == 0)) %>%
setDT(.) %>%
# check if the number of trials matches for every subject:
verify(.[(condition == "oddball"), by = .(subject), .(
num_trials = .N)]$num_trials == 120) %>%
verify(.[(condition == "sequence"), by = .(subject), .(
num_trials = .N)]$num_trials == 75) %>%
verify(.[(condition == "repetition"), by = .(subject), .(
num_trials = .N)]$num_trials == 45) %>%
# calculate the average accuracy for each participant and condition:
.[, by = .(subject, condition), .(
mean_accuracy = mean(accuracy, na.rm = TRUE) * 100,
num_trials = .N)] %>%
# check if the accuracy values are between 0 and 100:
assert(within_bounds(lower.bound = 0, upper.bound = 100), mean_accuracy) %>%
# create new variable that specifies excluded participants:
mutate(exclude = ifelse(mean_accuracy < chance_level, "yes", "no")) %>%
# create a short name for the conditions:
mutate(condition_short = substr(condition, start = 1, stop = 3)) %>%
# reorder the condition factor in descending order of accuracy:
transform(condition_short = fct_reorder(
.desc = TRUE))
condition_short, mean_accuracy, ::paged_table(dt_acc) rmarkdown
We create a list of participants that will be excluded because their performance is below the 50% chance level in either or both sequence and repetition trials:
# create a list with all excluded subject ids and print the list:
unique(dt_acc$subject[dt_acc$exclude == "yes"])
subjects_excluded =print(subjects_excluded)
## [1] "sub-24" "sub-31" "sub-37" "sub-40"
We calculate the mean behavioral accuracy across all three task conditions (slow, sequence, and repetition trials), excluding participants that performed below chance on either or both sequence and repetition trials:
dt_acc %>%
dt_acc_mean = # filter out all data of excluded participants:
filter(!(subject %in% unique(subject[exclude == "yes"]))) %>%
# check if the number of participants matches expectations:
verify(length(unique(subject)) == 36) %>%
setDT(.) %>%
# calculate mean behavioral accuracy across participants for each condition:
.[, by = .(condition), {
t.test(
ttest_results =mu = chance_level, alternative = "greater")
mean_accuracy, list(
pvalue = ttest_results$p.value,
pvalue_rounded = round_pvalues(ttest_results$p.value),
tvalue = round(ttest_results$statistic, digits = 2),
conf_lb = round(ttest_results$conf.int[1], digits = 2),
conf_ub = round(ttest_results$conf.int[2], digits = 2),
df = ttest_results$parameter,
num_subs = .N,
mean_accuracy = round(mean(mean_accuracy), digits = 2),
SD = round(sd(mean_accuracy), digits = 2),
cohens_d = round((mean(mean_accuracy) - chance_level) / sd(mean_accuracy), 2),
sem_upper = mean(mean_accuracy) + (sd(mean_accuracy)/sqrt(.N)),
sem_lower = mean(mean_accuracy) - (sd(mean_accuracy)/sqrt(.N))
%>%
)}] verify(num_subs == 36) %>%
# create a short name for the conditions:
mutate(condition_short = substr(condition, start = 1, stop = 3)) %>%
# reorder the condition factor in descending order of accuracy:
transform(condition_short = fct_reorder(condition_short, mean_accuracy, .desc = TRUE))
# show the table (https://rstudio.github.io/distill/tables.html):
::paged_table(dt_acc_mean) rmarkdown
2.2.3.2 Above-chance performance
We plot only data of above-chance performers:
2.2.3.3 Figure S1a
We plot data of all participants with below chance performers highlighted in red.
ggplot(data = dt_acc_mean,
fig_behav_all_outlier =mapping = aes(x = as.factor(condition_short), y = as.numeric(mean_accuracy),
group = as.factor(condition_short), fill = as.factor(condition_short))) +
geom_bar(aes(fill = as.factor(condition)), stat = "identity",
color = "black", fill = "white") +
geom_point(data = subset(dt_acc, exclude == "no"),
aes(color = as.factor(exclude)),
position = position_jitter(width = 0.2, height = 0, seed = 2),
alpha = 0.5, inherit.aes = TRUE, pch = 21,
color = "black", fill = "lightgray") +
geom_point(data = subset(dt_acc, exclude == "yes"),
aes(color = as.factor(exclude), shape = as.factor(subject)),
position = position_jitter(width = 0.05, height = 0, seed = 4),
alpha = 1, inherit.aes = TRUE, color = "red") +
geom_errorbar(aes(ymin = sem_lower, ymax = sem_upper), width = 0.0, color = "black") +
ylab("Accuracy (%)") + xlab("Condition") +
scale_color_manual(values = c("darkgray", "red"), name = "Outlier") +
geom_hline(aes(yintercept = 50), linetype = "dashed", color = "black") +
coord_capped_cart(left = "both", bottom = "none", expand = TRUE, ylim = c(0, 100)) +
theme(axis.ticks.x = element_line(color = "white"),
axis.line.x = element_line(color = "white")) +
guides(shape = FALSE, fill = FALSE) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
axis.ticks.x = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_behav_all_outlier
2.2.3.4 Source Data File Fig. S1a
%>%
dt_acc select(-num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s1a.csv"),
row.names = FALSE)
2.2.4 Slow trials
2.2.4.1 Mean accuracy (all trials)
We calculate the mean accuracy on slow trials (oddball task condition) across all trials in the final sample (only participants who performed above chance):
# we use the dataframe containing the accuracy data
dt_acc %>%
dt_acc_odd = # filter for oddball / slow trials only:
filter(condition == "oddball") %>%
# exclude participants with below chance performance::
filter(!(subject %in% subjects_excluded)) %>%
# verify that the number of participants (final sample) is correct:
verify(all(.N == 36))
We plot the mean behavioral accuracy on slow trials (oddball task condition) in the final sample:
ggplot(data = dt_acc_odd, aes(
fig_behav_odd =x = "mean_acc", y = as.numeric(mean_accuracy))) +
geom_bar(stat = "summary", fun = "mean", fill = "lightgray") +
#geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5,
# color = "black", fill = "lightgray", alpha = 0.5,
# inherit.aes = TRUE, binwidth = 0.5) +
geom_point(position = position_jitter(width = 0.2, height = 0, seed = 2),
alpha = 0.5, inherit.aes = TRUE, pch = 21,
color = "black", fill = "lightgray") +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
ylab("Accuracy (%)") + xlab("Condition") +
scale_color_manual(values = c("darkgray", "red"), name = "Outlier") +
geom_hline(aes(yintercept = 50), linetype = "dashed", color = "black") +
#coord_capped_cart(left = "both", bottom = "none", expand = TRUE, ylim = c(90, 100)) +
theme(plot.title = element_text(size = 12, face = "plain")) +
theme(axis.ticks.x = element_line(color = "white"),
axis.line.x = element_line(color = "white")) +
theme(axis.title.x = element_text(color = "white"),
axis.text.x = element_text(color = "white")) +
ggtitle("Slow") +
theme(plot.title = element_text(hjust = 0.5))
fig_behav_odd
2.2.4.2 Mean accuracy (per run)
We calculate the mean behavioral accuracy on slow trials (oddball task condition) for each of the eight task runs for each participant:
# calculate the mean accuracy per session and run for every participant:
dt_events %>%
dt_odd_behav_run_sub = # exclude participants performing below chance:
filter(!(subject %in% subjects_excluded)) %>%
# select only oddball condition and stimulus events:
filter(condition == "oddball" & trial_type == "stimulus") %>%
# filter for upside-down trials (oddballs) only:
filter(stim_orient == 180) %>%
setDT(.) %>%
# calculate mean accuracy per session and run:
.[, by = .(subject, session, run_study, run_session), .(
mean_accuracy = mean(accuracy))] %>%
# express accuracy in percent by multiplying with 100:
transform(mean_accuracy = mean_accuracy * 100) %>%
# check whether the mean accuracy is within the expected range of 0 to 100:
assert(within_bounds(lower.bound = 0, upper.bound = 100), mean_accuracy)
We calculate the mean behavioral accuracy on slow trials (oddball task condition) for each of the eight task runs across participants:
# calculate mean accuracy per session and run across participants:
dt_odd_behav_run_sub %>%
dt_odd_behav_run_mean = setDT(.) %>%
# average across participants:
.[, by = .(session, run_study, run_session), .(
mean_accuracy = mean(mean_accuracy),
num_subs = .N,
sem_upper = mean(mean_accuracy) + (sd(mean_accuracy)/sqrt(.N)),
sem_lower = mean(mean_accuracy) - (sd(mean_accuracy)/sqrt(.N))
%>%
)] verify(num_subs == 36) %>%
# z-score the accuracy values:
mutate(mean_accuracy_z = scale(mean_accuracy, scale = TRUE, center = TRUE))
We run a LME model to test the linear effect of task run on behavioral accuracy:
lmerTest::lmer(
lme_odd_behav_run =~ run_study + (1 + run_study | subject),
mean_accuracy data = dt_odd_behav_run_sub, na.action = na.omit, control = lcctrl)
summary(lme_odd_behav_run)
anova(lme_odd_behav_run)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_accuracy ~ run_study + (1 + run_study | subject)
## Data: dt_odd_behav_run_sub
## Control: lcctrl
##
## REML criterion at convergence: 1250.2
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -6.7158 0.1095 0.1285 0.1571 1.9026
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subject (Intercept) 0.12000 0.3464
## run_study 0.01038 0.1019 1.00
## Residual 3.97535 1.9938
## Number of obs: 288, groups: subject, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 99.53650 0.26529 135.52431 375.201 <2e-16 ***
## run_study -0.01969 0.05401 92.71468 -0.365 0.716
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## run_study -0.757
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## run_study 0.52843 0.52843 1 92.715 0.1329 0.7162
We run a second model to test run- and session-specific effects:
dt_odd_behav_run_sub %>%
dt <- transform(run_session = as.factor(paste0("run-0", run_session)),
session = as.factor(paste0("ses-0", session)))
lmerTest::lmer(
lme_odd_behav_run =~ session + run_session + (1 + session + run_session | subject),
mean_accuracy data = dt, na.action = na.omit, control = lcctrl)
summary(lme_odd_behav_run)
emmeans(lme_odd_behav_run, list(pairwise ~ run_session | session))
anova(lme_odd_behav_run)
rm(dt)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_accuracy ~ session + run_session + (1 + session + run_session |
## subject)
## Data: dt
## Control: lcctrl
##
## REML criterion at convergence: 1192.3
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -4.2095 -0.0252 0.1378 0.2041 2.8277
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subject (Intercept) 0.1958 0.4425
## sessionses-02 2.2398 1.4966 -0.73
## run_sessionrun-02 0.4736 0.6882 0.44 0.09
## run_sessionrun-03 0.8393 0.9161 0.70 -0.01 0.78
## run_sessionrun-04 4.2997 2.0736 0.90 -0.77 0.02 0.47
## Residual 2.4305 1.5590
## Number of obs: 288, groups: subject, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 99.544925 0.218252 85.583815 456.101 <2e-16 ***
## sessionses-02 0.049649 0.309796 35.649869 0.160 0.874
## run_sessionrun-02 -0.054933 0.284024 67.397962 -0.193 0.847
## run_sessionrun-03 -0.009178 0.301376 54.333815 -0.030 0.976
## run_sessionrun-04 -0.423351 0.432376 37.096544 -0.979 0.334
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) sss-02 rn_-02 rn_-03
## sessinss-02 -0.447
## rn_sssnr-02 -0.485 0.031
## rn_sssnr-03 -0.394 -0.006 0.555
## rn_sssnr-04 -0.115 -0.498 0.281 0.449
## $`emmeans of run_session | session`
## session = ses-01:
## run_session emmean SE df lower.CL upper.CL
## run-01 99.54 0.2183 35 99.10 99.99
## run-02 99.49 0.2611 35 98.96 100.02
## run-03 99.54 0.2943 35 98.94 100.13
## run-04 99.12 0.4614 35 98.18 100.06
##
## session = ses-02:
## run_session emmean SE df lower.CL upper.CL
## run-01 99.59 0.2883 35 99.01 100.18
## run-02 99.54 0.3302 35 98.87 100.21
## run-03 99.59 0.3478 35 98.88 100.29
## run-04 99.17 0.3389 35 98.48 99.86
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of run_session | session`
## session = ses-01:
## contrast estimate SE df t.ratio p.value
## (run-01) - (run-02) 0.05493 0.284 35 0.193 0.9974
## (run-01) - (run-03) 0.00918 0.301 35 0.030 1.0000
## (run-01) - (run-04) 0.42335 0.432 35 0.979 0.7621
## (run-02) - (run-03) -0.04575 0.277 35 -0.165 0.9984
## (run-02) - (run-04) 0.36842 0.446 35 0.827 0.8415
## (run-03) - (run-04) 0.41417 0.401 35 1.033 0.7314
##
## session = ses-02:
## contrast estimate SE df t.ratio p.value
## (run-01) - (run-02) 0.05493 0.284 35 0.193 0.9974
## (run-01) - (run-03) 0.00918 0.301 35 0.030 1.0000
## (run-01) - (run-04) 0.42335 0.432 35 0.979 0.7621
## (run-02) - (run-03) -0.04575 0.277 35 -0.165 0.9984
## (run-02) - (run-04) 0.36842 0.446 35 0.827 0.8415
## (run-03) - (run-04) 0.41417 0.401 35 1.033 0.7314
##
## Degrees-of-freedom method: kenward-roger
## P value adjustment: tukey method for comparing a family of 4 estimates
##
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## session 0.06243 0.06243 1 35.650 0.0257 0.8736
## run_session 2.90689 0.96896 3 50.201 0.3987 0.7545
2.2.4.3 Figure S1c
We plot the behavioral accuracy on slow trials (oddball task condition) across task runs (x-axis) for each study session (panels):
# change labels of the facet:
unique(paste0("Session ", dt_events$session))
facet_labels_new = as.character(unique(dt_events$session))
facet_labels_old =names(facet_labels_new) = facet_labels_old
# plot behavioral accuracy across runs:
ggplot(data = dt_odd_behav_run_mean, mapping = aes(
plot_odd_run =y = as.numeric(mean_accuracy), x = as.numeric(run_session))) +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.5, fill = "gray") +
geom_line(color = "black") +
facet_wrap(~ as.factor(session), labeller = as_labeller(facet_labels_new)) +
ylab("Accuracy (%)") + xlab("Run") +
ylim(c(90, 100)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(90,100)) +
theme(axis.ticks.x = element_text(color = "white"),
axis.line.x = element_line(color = "white")) +
theme(strip.text.x = element_text(margin = margin(b = 2, t = 2))) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
axis.ticks.x = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
plot_odd_run
2.2.4.4 Source Data File Fig. S1c
%>%
dt_odd_behav_run_mean select(-num_subs, -mean_accuracy_z) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s1c.csv"),
row.names = FALSE)
2.2.4.5 Misses vs. false alarms
We calculate the mean frequency of misses (missed response to upside-down images) and false alarms (incorrect response to upright images):
dt_events %>%
dt_odd_behav_sdt_sub = # exclude participants performing below chance:
filter(!(subject %in% subjects_excluded)) %>%
# select only oddball condition and stimulus events:
filter(condition == "oddball" & trial_type == "stimulus") %>%
setDT(.) %>%
# create new variable with number of upside-down / upright stimuli per run:
.[, by = .(subject, session, run_session, stim_orient), ":=" (
num_orient = .N
%>%
)] # get the number of signal detection trial types for each run:
.[, by = .(subject, session, run_session, sdt_type), .(
num_trials = .N,
freq = .N/unique(num_orient)
%>%
)] # add missing values:
complete(nesting(subject, session, run_session), nesting(sdt_type),
fill = list(num_trials = 0, freq = 0)) %>%
transform(freq = freq * 100) %>%
filter(sdt_type %in% c("false alarm", "miss"))
We run a LME model to test the effect of signal detection type (miss vs. false alarm), task run and session on the frequency of those events:
lmer(
lme_odd_behav_sdt =~ sdt_type + run_session * session + (1 + run_session + session | subject),
freq data = subset(dt_odd_behav_sdt_sub), na.action = na.omit, control = lcctrl)
summary(lme_odd_behav_sdt)
anova(lme_odd_behav_sdt)
emmeans(lme_odd_behav_sdt, list(pairwise ~ sdt_type))
emmeans_results = round_pvalues(summary(emmeans_results[[2]])$p.value)
emmeans_pvalues = emmeans_results
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: freq ~ sdt_type + run_session * session + (1 + run_session +
## session | subject)
## Data: subset(dt_odd_behav_sdt_sub)
## Control: lcctrl
##
## REML criterion at convergence: 2164.6
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.3848 -0.2865 -0.1767 -0.0525 7.7856
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subject (Intercept) 0.12419 0.3524
## run_session 0.09805 0.3131 0.37
## session 0.32394 0.5692 -0.94 -0.66
## Residual 2.21395 1.4879
## Number of obs: 576, groups: subject, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 0.10130 0.48776 457.56239 0.208 0.8356
## sdt_typemiss 0.25175 0.12399 501.00009 2.030 0.0429 *
## run_session 0.06991 0.18296 442.67822 0.382 0.7026
## session 0.05953 0.31819 366.45275 0.187 0.8517
## run_session:session -0.01733 0.11090 501.00010 -0.156 0.8759
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) sdt_ty rn_sss sessin
## sdt_typemss -0.127
## run_session -0.849 0.000
## session -0.925 0.000 0.736
## rn_sssn:sss 0.853 0.000 -0.909 -0.871
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## sdt_type 9.1265 9.1265 1 501.00 4.1223 0.04285 *
## run_session 0.3232 0.3232 1 442.68 0.1460 0.70258
## session 0.0775 0.0775 1 366.45 0.0350 0.85169
## run_session:session 0.0541 0.0541 1 501.00 0.0244 0.87587
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## $`emmeans of sdt_type`
## sdt_type emmean SE df lower.CL upper.CL
## false alarm 0.300 0.118 66.4 0.0656 0.535
## miss 0.552 0.118 66.4 0.3174 0.787
##
## Results are averaged over the levels of: session
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of sdt_type`
## contrast estimate SE df t.ratio p.value
## false alarm - miss -0.252 0.124 466 -2.030 0.0429
##
## Results are averaged over the levels of: session
## Degrees-of-freedom method: kenward-roger
2.2.4.6 Figure S1b
We plot the frequency of misses and false alarms as a function of task run and study session:
ggplot(data = dt_odd_behav_sdt_sub, mapping = aes(
plot_odd_sdt =y = as.numeric(freq), x = as.numeric(run_session),
fill = as.factor(sdt_type), color = as.factor(sdt_type))) +
stat_summary(geom = "bar", fun = mean, position = position_dodge(),
na.rm = TRUE) +
stat_summary(geom = "errorbar", fun.data = mean_se,
position = position_dodge(0.9), width = 0, color = "black") +
facet_wrap(~ as.factor(session), labeller = as_labeller(facet_labels_new)) +
geom_dotplot(
aes(group = interaction(run_session, session, sdt_type)),
binaxis = "y", stackdir = "center", stackratio = 0.2, alpha = 0.7,
inherit.aes = TRUE, binwidth = 0.2, position = position_dodge(),
color = "black") +
ylab("Frequency (%)") + xlab("Run") +
coord_capped_cart(left = "both", bottom = "both",
expand = TRUE, ylim = c(0, 15)) +
scale_fill_viridis(name = "Error", discrete = TRUE) +
scale_color_viridis(name = "Error", discrete = TRUE) +
theme(strip.text.x = element_text(margin = margin(b = 2, t = 2))) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks.x = element_line(color = "white")) +
theme(axis.line.x = element_line(color = "white")) +
theme(axis.ticks.y = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
plot_odd_sdt
2.2.4.7 Source Data File Fig. S1b
%>%
dt_odd_behav_sdt_sub select(-num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s1b.csv"),
row.names = FALSE)
2.2.5 Sequence trials
2.2.5.1 Effect of sequence speed
We calculate the mean behavioral accuracy on sequence trials for each of the five sequence speeds (inter-stimulus intervals):
dt_events %>%
dt_seq_behav = # filter behavioral events data for sequence trials only:
filter(condition == "sequence") %>%
setDT(.) %>%
# create additional variables to describe each trial:
.[, by = .(subject, trial), ":=" (
trial_key_down = ifelse(any(key_down == 1, na.rm = TRUE), 1, 0),
trial_accuracy = ifelse(any(accuracy == 1, na.rm = TRUE), 1, 0),
trial_target_position = serial_position[which(target == 1)],
trial_speed = unique(interval_time[which(!is.na(interval_time))])
%>%
)] # filter for choice trials only:
filter(trial_type == "choice") %>%
setDT(.) %>%
# group speed conditions into fast and slow conditions:
mutate(speed = ifelse(trial_speed %in% c(2.048, 0.512), "slow", "fast")) %>%
# define variable factors of interest as numeric:
transform(trial_speed = as.numeric(trial_speed)) %>%
transform(trial_target_position = as.numeric(trial_target_position)) %>%
setDT(.)
dt_seq_behav %>%
dt_seq_behav_speed = # filter out excluded subjects:
filter(!(subject %in% subjects_excluded)) %>%
setDT(.) %>%
# average accuracy for each participant:
.[, by = .(subject, trial_speed), .(
num_trials = .N,
mean_accuracy = mean(accuracy)
%>%
)] transform(mean_accuracy = mean_accuracy * 100) %>%
setDT(.) %>%
verify(all(num_trials == 15)) %>%
verify(.[, by = .(trial_speed), .(
num_subjects = .N
$num_subjects == 36) %>%
)] setorder(subject, trial_speed) %>%
mutate(trial_speed = as.numeric(trial_speed)) %>%
setDT(.)
We run a LME model to test the effect of sequence speed (inter-stimulus interval) on mean behavioral accuracy on sequence trials:
lmer(
lme_seq_behav =~ trial_speed + (1 + trial_speed | subject),
mean_accuracy data = dt_seq_behav_speed, na.action = na.omit, control = lcctrl)
summary(lme_seq_behav)
anova(lme_seq_behav)
emmeans(lme_seq_behav, list(pairwise ~ trial_speed))
emmeans_results = round_pvalues(summary(emmeans_results[[2]])$p.value)
emmeans_pvalues =
emmeans_results emmeans_pvalues
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_accuracy ~ trial_speed + (1 + trial_speed | subject)
## Data: dt_seq_behav_speed
## Control: lcctrl
##
## REML criterion at convergence: 1245.4
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.8011 -0.4849 0.2106 0.6766 2.3025
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subject (Intercept) 32.535 5.704
## trial_speed 3.969 1.992 -0.58
## Residual 44.342 6.659
## Number of obs: 180, groups: subject, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 91.0872 1.1316 35.0000 80.495 <2e-16 ***
## trial_speed 1.5063 0.7287 35.0000 2.067 0.0462 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## trial_speed -0.508
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## trial_speed 189.49 189.49 1 35 4.2733 0.04617 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## $`emmeans of trial_speed`
## trial_speed emmean SE df lower.CL upper.CL
## 0.557 91.9 0.989 35 89.9 93.9
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $` of trial_speed`
## contrast estimate SE df z.ratio p.value
## (nothing) nonEst NA NA NA NA
##
## Degrees-of-freedom method: kenward-roger
##
## [1] "p = NA"
We compare mean behavioral accuracy at each sequence speed level to the chancel level of 50%:
50
chance_level = dt_seq_behav_speed %>%
dt_seq_behav_mean = # average across participants:
.[, by = .(trial_speed), {
t.test(
ttest_results =mu = chance_level, alternative = "greater")
mean_accuracy, list(
mean_accuracy = round(mean(mean_accuracy), digits = 2),
sd_accuracy = round(sd(mean_accuracy), digits = 2),
tvalue = round(ttest_results$statistic, digits = 2),
conf_lb = round(ttest_results$conf.int[1], digits = 2),
conf_ub = round(ttest_results$conf.int[2], digits = 2),
pvalue = ttest_results$p.value,
cohens_d = round((mean(mean_accuracy) - chance_level)/sd(mean_accuracy), 2),
df = ttest_results$parameter,
num_subs = .N,
sem_upper = mean(mean_accuracy) + (sd(mean_accuracy)/sqrt(.N)),
sem_lower = mean(mean_accuracy) - (sd(mean_accuracy)/sqrt(.N))
%>%
)}] verify(num_subs == 36) %>%
mutate(sem_range = sem_upper - sem_lower) %>%
mutate(pvalue_adjust = p.adjust(pvalue, method = "fdr")) %>%
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust))
# print paged table:
::paged_table(dt_seq_behav_mean) rmarkdown
We calculate the reduction in mean behavioral accuracy comparing the fastest and slowest speed condition:
dt_seq_behav_mean$mean_accuracy[dt_seq_behav_mean$trial_speed == 2.048]
a = dt_seq_behav_mean$mean_accuracy[dt_seq_behav_mean$trial_speed == 0.032]
b = round((1 - (b/a)) * 100, 2)
reduced_acc =sprintf("reduction in accuracy: %.2f", reduced_acc)
## [1] "reduction in accuracy: 5.73"
2.2.5.2 Figure 1e
ggplot(data = dt_seq_behav_speed, mapping = aes(
fig_seq_speed =y = as.numeric(mean_accuracy), x = as.factor(as.numeric(trial_speed)*1000),
fill = as.factor(trial_speed), color = as.factor(trial_speed))) +
geom_bar(stat = "summary", fun = "mean") +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5,
color = "black", alpha = 0.5,
inherit.aes = TRUE, binwidth = 2) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
geom_hline(aes(yintercept = 50), linetype = "dashed", color = "black") +
ylab("Accuracy (%)") + xlab("Sequence speed (ms)") +
scale_fill_viridis(discrete = TRUE, guide = FALSE, option = "cividis") +
scale_color_viridis(discrete = TRUE, guide = FALSE, option = "cividis") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 100)) +
theme(plot.title = element_text(size = 12, face = "plain")) +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
ggtitle("Sequence") +
theme(plot.title = element_text(hjust = 0.5)) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks.x = element_line(color = "white")) +
theme(axis.line.x = element_line(color = "white")) +
theme(axis.ticks.y = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_seq_speed
2.2.5.3 Source Data File Fig. 1e
%>%
dt_seq_behav_speed select(-num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_1e.csv"),
row.names = FALSE)
2.2.5.4 Effect of target position
We calculate the mean behavioral accuracy on sequence trials for each of possible serial position of the target stimulus:
dt_seq_behav %>%
dt_seq_behav_position = # filter out excluded subjects:
filter(!(subject %in% subjects_excluded)) %>% setDT(.) %>%
# average accuracy for each participant:
.[, by = .(subject, trial_target_position), .(
num_trials = .N,
mean_accuracy = mean(accuracy)
%>%
)] verify(.[, by = .(trial_target_position), .(
num_subs = .N
$num_subs == 36) %>%
)] transform(mean_accuracy = mean_accuracy * 100) %>%
setorder(subject, trial_target_position)
lmer(
lme_seq_behav_position =~ trial_target_position + (1 + trial_target_position | subject),
mean_accuracy data = dt_seq_behav_position, na.action = na.omit, control = lcctrl)
summary(lme_seq_behav_position)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_accuracy ~ trial_target_position + (1 + trial_target_position |
## subject)
## Data: dt_seq_behav_position
## Control: lcctrl
##
## REML criterion at convergence: 1385.9
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.2760 -0.4809 0.1467 0.5073 2.1940
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subject (Intercept) 221.205 14.873
## trial_target_position 8.402 2.899 -1.00
## Residual 102.478 10.123
## Number of obs: 180, groups: subject, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 83.4370 3.0456 35.9399 27.396 < 2e-16 ***
## trial_target_position 2.2667 0.7197 42.0225 3.149 0.00301 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## trl_trgt_ps -0.936
anova(lme_seq_behav_position)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## trial_target_position 1016.4 1016.4 1 42.022 9.9178 0.003011 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
2.2.5.5 Figure S1d
ggplot(data = dt_seq_behav_position, mapping = aes(
fig_seq_position =y = as.numeric(mean_accuracy), x = as.factor(trial_target_position),
fill = as.factor(trial_target_position), color = as.factor(trial_target_position))) +
geom_bar(stat = "summary", fun = "mean") +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5,
color = "black", alpha = 0.5,
inherit.aes = TRUE, binwidth = 2) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
geom_hline(aes(yintercept = 50), linetype = "dashed", color = "black") +
ylab("Accuracy (%)") + xlab("Target position") +
scale_fill_manual(values = color_events, guide = FALSE) +
scale_color_manual(values = color_events, guide = FALSE) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 100)) +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks.x = element_line(color = "white")) +
theme(axis.line.x = element_line(color = "white")) +
theme(axis.ticks.y = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_seq_position
2.2.5.6 Source Data File Fig. S1d
%>%
dt_seq_behav_position select(-num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s1d.csv"),
row.names = FALSE)
2.2.6 Repetition trials
2.2.6.1 Mean accuracy
We calculate mean behavioral accuracy in repetition trials for each participant:
dt_events %>%
dt_rep_behav = # filter for repetition trials only:
filter(condition == "repetition") %>% setDT(.) %>%
# create additional variables to describe each trial:
.[, by = .(subject, trial), ":=" (
trial_key_down = ifelse(any(key_down == 1, na.rm = TRUE), 1, 0),
trial_accuracy = ifelse(any(accuracy == 1, na.rm = TRUE), 1, 0),
trial_target_position = serial_position[which(target == 1)],
trial_speed = unique(interval_time[which(!is.na(interval_time))])
%>%
)] # select only choice trials that contain the accuracy data:
filter(trial_type == "choice") %>% setDT(.) %>%
verify(all(trial_accuracy == accuracy)) %>%
# average across trials separately for each participant:
.[, by = .(subject, trial_target_position), .(
num_trials = .N,
mean_accuracy = mean(accuracy)
%>%
)] verify(all(num_trials == 5)) %>%
# transform mean accuracy into percent (%)
transform(mean_accuracy = mean_accuracy * 100) %>%
# check if accuracy values range between 0 and 100
verify(between(x = mean_accuracy, lower = 0, upper = 100)) %>%
mutate(interference = ifelse(
== 2, "fwd", trial_target_position)) %>%
trial_target_position transform(interference = ifelse(
== 9, "bwd", interference)) trial_target_position
We run separate one-sided one-sample t-tests to access if mean behavioral performance for every repetition condition differs from a 50% chance level:
50
chance_level = dt_rep_behav %>%
dt_rep_behav_chance = # filter out excluded subjects:
filter(!(subject %in% subjects_excluded)) %>%
setDT(.) %>%
# average across participants:
.[, by = .(trial_target_position), {
t.test(
ttest_results =mu = chance_level, alternative = "greater")
mean_accuracy, list(
mean_accuracy = round(mean(mean_accuracy), digits = 2),
sd_accuracy = round(sd(mean_accuracy), digits = 2),
tvalue = round(ttest_results$statistic, digits = 2),
pvalue = ttest_results$p.value,
conf_lb = round(ttest_results$conf.int[1], digits = 2),
conf_ub = round(ttest_results$conf.int[2], digits = 2),
cohens_d = round((mean(mean_accuracy) - chance_level)/sd(mean_accuracy), 2),
df = ttest_results$parameter,
num_subs = .N,
sem_upper = mean(mean_accuracy) + (sd(mean_accuracy)/sqrt(.N)),
sem_lower = mean(mean_accuracy) - (sd(mean_accuracy)/sqrt(.N))
%>% verify(num_subs == 36) %>%
)}] mutate(sem_range = sem_upper - sem_lower) %>%
setDT(.) %>%
filter(trial_target_position %in% seq(2,9)) %>%
# create additional variable to label forward and backward interference:
mutate(interference = ifelse(
== 2, "fwd", trial_target_position)) %>%
trial_target_position transform(interference = ifelse(
== 9, "bwd", interference)) %>%
trial_target_position mutate(pvalue_adjust = p.adjust(pvalue, method = "fdr")) %>%
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", "")) %>%
mutate(cohens_d = paste0("d = ", cohens_d, significance)) %>%
mutate(label = paste0(
- 1, "/", 10 - trial_target_position)) %>%
trial_target_position setDT(.) %>%
setorder(trial_target_position)
# print table:
::paged_table(dt_rep_behav_chance) rmarkdown
2.2.6.2 Figure 1f
We plot the results of the forward and backward interference conditions:
dt_rep_behav_chance %>% filter(trial_target_position %in% c(2,9))
plot_data = ggplot(data = plot_data, mapping = aes(
fig_behav_rep =y = as.numeric(mean_accuracy), x = fct_rev(as.factor(interference)),
fill = as.factor(interference))) +
geom_bar(stat = "summary", fun = "mean") +
geom_dotplot(data = dt_rep_behav %>%
filter(!(subject %in% subjects_excluded)) %>%
filter(trial_target_position %in% c(2,9)) %>% setDT(.) %>%
.[, by = .(subject, interference), .(
mean_accuracy = mean(mean_accuracy)
)],binaxis = "y", stackdir = "center", stackratio = 0.5,
color = "black", alpha = 0.5, inherit.aes = TRUE, binwidth = 2) +
geom_errorbar(aes(ymin = sem_lower, ymax = sem_upper), width = 0.0, color = "black") +
ylab("Accuracy (%)") + xlab("Interfererence") +
ggtitle("Repetition") +
theme(plot.title = element_text(hjust = 0.5)) +
scale_fill_manual(values = c("red", "dodgerblue"), guide = FALSE) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0,100)) +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
theme(plot.title = element_text(size = 12, face = "plain")) +
#scale_y_continuous(labels = label_fill(seq(0, 100, 12.5), mod = 2), breaks = seq(0, 100, 12.5)) +
geom_hline(aes(yintercept = 50), linetype = "dashed", color = "black") +
#geom_text(aes(y = as.numeric(mean_accuracy) + 10, label = pvalue_adjust_round), size = 3) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks.x = element_line(color = "white")) +
theme(axis.line.x = element_line(color = "white")) +
theme(axis.ticks.y = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_behav_rep
2.2.6.3 Source Data File Fig. 1f
%>%
dt_rep_behav select(-num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_1f.csv"),
row.names = FALSE)
2.2.6.4 Figure S1e
We plot the results of all intermediate repetition conditions:
dt_rep_behav_chance %>% filter(trial_target_position %in% seq(2,9))
plot_data = ggplot(data = plot_data, mapping = aes(
plot_behav_rep_all =y = as.numeric(mean_accuracy), x = as.numeric(trial_target_position),
fill = as.numeric(trial_target_position))) +
geom_bar(stat = "identity") +
geom_dotplot(data = dt_rep_behav %>%
filter(!(subject %in% subjects_excluded)) %>%
filter(trial_target_position %in% seq(2,9)) %>% setDT(.) %>%
.[, by = .(subject, trial_target_position), .(
mean_accuracy = mean(mean_accuracy)
)],aes(x = as.numeric(trial_target_position),
fill = as.numeric(trial_target_position),
group = as.factor(trial_target_position)),
binaxis = "y", stackdir = "center", stackratio = 0.5,
inherit.aes = TRUE, binwidth = 2, color = "black", alpha = 0.5) +
geom_errorbar(aes(ymin = sem_lower, ymax = sem_upper), width = 0.0, color = "black") +
geom_text(aes(y = as.numeric(mean_accuracy) + 7, label = cohens_d), size = 2.5) +
ylab("Accuracy (%)") + xlab("First / second item repetitions") +
scale_fill_gradient(low = "dodgerblue", high = "red", guide = FALSE) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0,100)) +
scale_x_continuous(labels = plot_data$label, breaks = seq(2, 9, 1)) +
geom_hline(aes(yintercept = 50), linetype = "dashed", color = "black") +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
theme(axis.text = element_text(color = "black")) +
theme(axis.ticks.x = element_line(color = "white")) +
theme(axis.line.x = element_line(color = "white")) +
theme(axis.ticks.y = element_line(color = "black")) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
plot_behav_rep_all
2.2.6.5 Source Data File Fig. S1e
%>%
dt_rep_behav select(-num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s1e.csv"),
row.names = FALSE)
ggsave(filename = "highspeed_plot_behavior_repetition_supplement.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures,
scale = 1, dpi = "retina", width = 5, height = 3, units = "in")
We run a LME model to test the effect of repetition condition on mean behavioral accuracy in repetition trials:
lmer(
lme_rep_behav_condition =~ trial_target_position + (1 + trial_target_position|subject),
mean_accuracy data = dt_rep_behav, na.action = na.omit, control = lcctrl)
summary(lme_rep_behav_condition)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_accuracy ~ trial_target_position + (1 + trial_target_position |
## subject)
## Data: dt_rep_behav
## Control: lcctrl
##
## REML criterion at convergence: 3287.6
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.3290 -0.6353 0.1102 0.7484 1.9475
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## subject (Intercept) 59.90 7.739
## trial_target_position 1.35 1.162 -0.02
## Residual 468.95 21.655
## Number of obs: 360, groups: subject, 40
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 87.7619 2.5538 39.0000 34.365 < 2e-16 ***
## trial_target_position -2.5976 0.3428 39.0000 -7.578 3.49e-09 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## trl_trgt_ps -0.643
anova(lme_rep_behav_condition)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## trial_target_position 26930 26930 1 39 57.426 3.493e-09 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
2.2.7 Figure Main
We plot the figure for the main text:
2.2.8 Figure SI
We plot the figure for the supplementary information:
ggsave(filename = "wittkuhn_schuck_figure_s1.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 8, height = 5)
2.3 Participants
We analyze characteristics of the participants:
# read data table with participant information:
do.call(rbind, lapply(Sys.glob(path_participants), fread))
dt_participants <-# remove selected participants from the data table:
dt_participants %>%
dt_participants = filter(!(participant_id %in% subjects_excluded)) %>%
setDT(.)
::table(dt_participants$sex)
baseround(sd(dt_participants$age), digits = 2)
::summary(
basec("age", "digit_span", "session_interval"), with = FALSE])
dt_participants[, round(sd(dt_participants$session_interval), digits = 2)
##
## f m
## 20 16
## [1] 3.77
## age digit_span session_interval
## Min. :20.00 Min. : 9.00 Min. : 1.000
## 1st Qu.:22.00 1st Qu.:15.00 1st Qu.: 4.000
## Median :24.00 Median :16.00 Median : 7.500
## Mean :24.61 Mean :16.75 Mean : 9.167
## 3rd Qu.:26.00 3rd Qu.:19.00 3rd Qu.:13.250
## Max. :35.00 Max. :24.00 Max. :24.000
## [1] 6.14
2.4 Decoding on slow trials
2.4.1 Initialization
We load the data and relevant functions:
# find the path to the root of this project:
if (!requireNamespace("here")) install.packages("here")
if ( basename(here::here()) == "highspeed" ) {
here::here("highspeed-analysis")
path_root =else {
} here::here()
path_root =
}# create a list of participants to exclude based on behavioral performance:
c("sub-24", "sub-31", "sub-37", "sub-40") sub_exclude <-
The next step is only evaluated during manual execution:
# source all relevant functions from the setup R script:
source(file.path(path_root, "code", "highspeed-analysis-setup.R"))
2.4.2 Decoding accuracy
2.4.2.1 Mean decoding accuracy
We calculate the mean decoding accuracy:
function(data, mask_label) {
calc_class_stim_acc <-# calculate the mean decoding accuracy for each participant:
data %>%
dt_odd_peak = # select data for the oddball peak decoding
# leave out the redundant "other" class predictions:
filter(test_set == "test-odd_peak" & class != "other" & mask == mask_label) %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>%
# only check classifier of stimulus presented on a given trial:
filter(class == stim) %>%
# calculate the decoding accuracy by comparing true and predicted label:
mutate(acc = 0) %>%
transform(acc = ifelse(pred_label == stim, 1, acc)) %>%
setDT(.) %>%
# calculate average decoding accuracy for every participant and classification:
.[, by = .(classification, id), .(
mean_accuracy = mean(acc) * 100,
num_trials = .N
%>%
)] verify(all(num_trials <= 480)) %>%
# verify if the number of participants matches:
verify(.[classification == "ovr", .(num_subs = .N)]$num_subs == 36)
return(dt_odd_peak)
}
function(data, mask_label) {
calc_max_prob_acc <- data %>%
dt_odd_peak <- # select data for the oddball peak decoding
# leave out the redundant "other" class predictions:
filter(test_set == "test-odd_peak" & class != "other" & mask == mask_label) %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>%
# calculate the decoding accuracy by comparing true and predicted label:
setDT(.) %>%
# calculate average decoding accuracy for every participant and classification:
.[, by = .(classification, id, trial, stim), .(
max_prob_label = class[which.max(probability)],
num_classes = .N
%>%
)] verify(num_classes == 5) %>%
.[, "acc" := ifelse(stim == max_prob_label, 1, 0)] %>%
.[, by = .(classification, id), .(
mean_accuracy = mean(acc) * 100,
num_trials = .N
%>%
)] verify(num_trials <= 480)
return(dt_odd_peak)
}
We print the results for the decoding accuracy:
function(accuracy, alt_label){
decoding_accuracy <-# print average decoding accuracy:
print(sprintf("decoding accuracy: m = %0.2f %%", mean(accuracy)))
# print sd decoding accuracy:
print(sprintf("decoding accuracy: sd = %0.2f %%", sd(accuracy)))
# print range of decoding accuracy:
print(sprintf("decoding accuracy: range = %s %%",
paste(round(range(accuracy),0), collapse = "-")))
# define decoding accuracy baseline (should equal 20%):
100/5
acc_baseline =print(sprintf("chance baseline = %d %%", acc_baseline))
# perform effect size (cohen`s d)
(mean(accuracy) - acc_baseline) / sd(accuracy)
cohens_d =print(sprintf("effect size (cohen's d) = %0.2f", cohens_d))
# perform shapiro-wilk test to test for normality of the data
print(shapiro.test(accuracy))
# perform one-sided one-sample t-test against baseline:
t.test(accuracy, mu = acc_baseline, alternative = alt_label)
results =print(results)
# perform one-sample wilcoxon test in case of non-normality of the data:
print(wilcox.test(accuracy, mu = acc_baseline, alternative = alt_label))
}
#dt_odd_peak <- calc_class_stim_acc(data = dt_pred, mask_label = "cv")
#dt_odd_peak_hpc <- calc_class_stim_acc(data = dt_pred, mask_label = "cv_hpc")
calc_max_prob_acc(data = dt_pred, mask_label = "cv")
dt_odd_peak <- calc_max_prob_acc(data = dt_pred, mask_label = "cv_hpc")
dt_odd_peak_hpc <-decoding_accuracy(
accuracy = subset(dt_odd_peak, classification == "ovr")$mean_acc,
alt_label = "greater")
decoding_accuracy(
accuracy = subset(dt_odd_peak_hpc, classification == "ovr")$mean_accuracy,
alt_label = "two.sided")
## [1] "decoding accuracy: m = 69.22 %"
## [1] "decoding accuracy: sd = 11.18 %"
## [1] "decoding accuracy: range = 38-89 %"
## [1] "chance baseline = 20 %"
## [1] "effect size (cohen's d) = 4.40"
##
## Shapiro-Wilk normality test
##
## data: accuracy
## W = 0.94786, p-value = 0.08953
##
##
## One Sample t-test
##
## data: accuracy
## t = 26.409, df = 35, p-value < 2.2e-16
## alternative hypothesis: true mean is greater than 20
## 95 percent confidence interval:
## 66.07499 Inf
## sample estimates:
## mean of x
## 69.22417
##
##
## Wilcoxon signed rank exact test
##
## data: accuracy
## V = 666, p-value = 1.455e-11
## alternative hypothesis: true location is greater than 20
##
## [1] "decoding accuracy: m = 20.52 %"
## [1] "decoding accuracy: sd = 1.49 %"
## [1] "decoding accuracy: range = 17-24 %"
## [1] "chance baseline = 20 %"
## [1] "effect size (cohen's d) = 0.35"
##
## Shapiro-Wilk normality test
##
## data: accuracy
## W = 0.94782, p-value = 0.08928
##
##
## One Sample t-test
##
## data: accuracy
## t = 2.1007, df = 35, p-value = 0.04294
## alternative hypothesis: true mean is not equal to 20
## 95 percent confidence interval:
## 20.01755 21.02752
## sample estimates:
## mean of x
## 20.52254
##
##
## Wilcoxon signed rank test with continuity correction
##
## data: accuracy
## V = 440, p-value = 0.09424
## alternative hypothesis: true location is not equal to 20
2.4.2.2 Figure 2a / S2a
We plot the mean decoding accuracy in occipito-temporal and hippocampal data:
function(data) {
plot_odd_peak <- ggplot(data = data, aes(
plot.odd =x = "mean_acc", y = as.numeric(mean_accuracy))) +
geom_bar(stat = "summary", fun = "mean", fill = "lightgray") +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5,
color = "black", fill = "lightgray", alpha = 0.5,
inherit.aes = TRUE, binwidth = 2) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
ylab("Accuracy (%)") + xlab("Condition") +
geom_hline(aes(yintercept = 20), linetype = "dashed", color = "black") +
scale_y_continuous(labels = label_fill(seq(0, 100, 20), mod = 1),
breaks = seq(0, 100, 20)) +
coord_capped_cart(left = "both", expand = TRUE, ylim = c(0, 100)) +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
theme(axis.title.x = element_blank(), axis.text.x = element_blank()) +
annotate("text", x = 1, y = 15, label = "Chance", color = "black",
size = rel(2.5), family = "Helvetica", fontface = "plain") +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
return(plot.odd)
} plot_odd_peak(subset(dt_odd_peak, classification == "ovr"))
fig_odd_peak <- plot_odd_peak(subset(dt_odd_peak_hpc, classification == "ovr"))
fig_odd_peak_hpc <- fig_odd_peak; fig_odd_peak_hpc
2.4.2.3 Source Data File Fig. 2a
subset(dt_odd_peak, classification == "ovr") %>%
select(-num_trials, -classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_2a.csv"),
row.names = FALSE)
2.4.2.4 Source Data File Fig. S2a
subset(dt_odd_peak_hpc, classification == "ovr") %>%
select(-num_trials, -classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s2a.csv"),
row.names = FALSE)
2.4.3 Fold-wise decoding accuracy
We calculate the mean decoding accuracy for each of the eight folds of the cross-validation procedure:
dt_pred %>%
dt_odd_peak_run = # select data for the oddball peak decoding
# leave out the redundant "other" class predictions:
filter(test_set == "test-odd_peak" & class != "other" & mask == "cv") %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>%
setDT(.) %>%
# calculate average decoding accuracy for every participant and classification:
.[, by = .(classification, id, run_study, trial, stim), .(
max_prob_label = class[which.max(probability)],
num_classes = .N
%>%
)] # check if there are five classifier predictions per trial:
verify(num_classes == 5) %>%
# determine accuracy if label with highest probability matches stimulus:
.[, "accuracy" := ifelse(stim == max_prob_label, 1, 0)]
# print results table:
::paged_table(dt_odd_peak_run) rmarkdown
# calculate average decoding accuracy for every participant and classification:
dt_odd_peak_run %>%
dt_odd_peak_run_mean <- .[, by = .(classification, id, run_study), .(
mean_accuracy = mean(accuracy) * 100,
num_trials = length(unique(trial))
%>%
)] # verify if the number of participants matches for every classification and run:
verify(.[, by = .(classification, run_study),
num_subs = .N)]$num_subs == 36) %>%
.( setorder(., classification, id, run_study)
# print the table:
::paged_table(dt_odd_peak_run_mean) rmarkdown
lmerTest::lmer(
lme_odd_peak_run <-~ run_study + (1 | id), control = lcctrl,
mean_accuracy data = subset(dt_odd_peak_run_mean, classification == "ovr")
)summary(lme_odd_peak_run)
anova(lme_odd_peak_run)
emmeans(lme_odd_peak_run, list(pairwise ~ run_study))
emmeans_results = emmeans_results
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_accuracy ~ run_study + (1 | id)
## Data: subset(dt_odd_peak_run_mean, classification == "ovr")
## Control: lcctrl
##
## REML criterion at convergence: 2081.6
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.3758 -0.6237 0.0020 0.6078 3.1304
##
## Random effects:
## Groups Name Variance Std.Dev.
## id (Intercept) 117.30 10.830
## Residual 63.38 7.961
## Number of obs: 288, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 73.400 2.240 70.879 32.764 < 2e-16 ***
## run_study2 -3.538 1.876 245.000 -1.885 0.060551 .
## run_study3 -5.096 1.876 245.000 -2.716 0.007079 **
## run_study4 -4.348 1.876 245.000 -2.317 0.021318 *
## run_study5 -2.934 1.876 245.000 -1.564 0.119200
## run_study6 -4.294 1.876 245.000 -2.288 0.022970 *
## run_study7 -6.131 1.876 245.000 -3.268 0.001240 **
## run_study8 -7.391 1.876 245.000 -3.939 0.000107 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) rn_st2 rn_st3 rn_st4 rn_st5 rn_st6 rn_st7
## run_study2 -0.419
## run_study3 -0.419 0.500
## run_study4 -0.419 0.500 0.500
## run_study5 -0.419 0.500 0.500 0.500
## run_study6 -0.419 0.500 0.500 0.500 0.500
## run_study7 -0.419 0.500 0.500 0.500 0.500 0.500
## run_study8 -0.419 0.500 0.500 0.500 0.500 0.500 0.500
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## run_study 1239.3 177.04 7 245 2.7936 0.008175 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## $`emmeans of run_study`
## run_study emmean SE df lower.CL upper.CL
## 1 73.4 2.24 70.9 68.9 77.9
## 2 69.9 2.24 70.9 65.4 74.3
## 3 68.3 2.24 70.9 63.8 72.8
## 4 69.1 2.24 70.9 64.6 73.5
## 5 70.5 2.24 70.9 66.0 74.9
## 6 69.1 2.24 70.9 64.6 73.6
## 7 67.3 2.24 70.9 62.8 71.7
## 8 66.0 2.24 70.9 61.5 70.5
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of run_study`
## contrast estimate SE df t.ratio p.value
## 1 - 2 3.5379 1.88 245 1.885 0.5622
## 1 - 3 5.0962 1.88 245 2.716 0.1227
## 1 - 4 4.3480 1.88 245 2.317 0.2885
## 1 - 5 2.9339 1.88 245 1.564 0.7715
## 1 - 6 4.2938 1.88 245 2.288 0.3043
## 1 - 7 6.1312 1.88 245 3.268 0.0268
## 1 - 8 7.3910 1.88 245 3.939 0.0027
## 2 - 3 1.5584 1.88 245 0.831 0.9912
## 2 - 4 0.8101 1.88 245 0.432 0.9999
## 2 - 5 -0.6039 1.88 245 -0.322 1.0000
## 2 - 6 0.7560 1.88 245 0.403 0.9999
## 2 - 7 2.5934 1.88 245 1.382 0.8647
## 2 - 8 3.8532 1.88 245 2.053 0.4482
## 3 - 4 -0.7482 1.88 245 -0.399 0.9999
## 3 - 5 -2.1623 1.88 245 -1.152 0.9442
## 3 - 6 -0.8024 1.88 245 -0.428 0.9999
## 3 - 7 1.0350 1.88 245 0.552 0.9993
## 3 - 8 2.2948 1.88 245 1.223 0.9245
## 4 - 5 -1.4140 1.88 245 -0.754 0.9951
## 4 - 6 -0.0541 1.88 245 -0.029 1.0000
## 4 - 7 1.7832 1.88 245 0.950 0.9806
## 4 - 8 3.0430 1.88 245 1.622 0.7368
## 5 - 6 1.3599 1.88 245 0.725 0.9962
## 5 - 7 3.1973 1.88 245 1.704 0.6847
## 5 - 8 4.4571 1.88 245 2.375 0.2582
## 6 - 7 1.8374 1.88 245 0.979 0.9770
## 6 - 8 3.0972 1.88 245 1.651 0.7189
## 7 - 8 1.2598 1.88 245 0.671 0.9976
##
## Degrees-of-freedom method: kenward-roger
## P value adjustment: tukey method for comparing a family of 8 estimates
2.5 Single-trial decoding time courses
We calculate the multivariate decoding time courses on single slow trials:
dt_pred %>%
dt_odd_long_sub = # select data for the oddball long decoding
# leave out the redundant "other" class predictions:
filter(test_set == "test-odd_long" & class != "other" & mask == "cv") %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>% setDT(.) %>%
# average across stimuli and runs for each participant
# create an index for the true stimulus presented on each trial:
.[, by = .(classification, id, seq_tr, class, stim), .(
num = .N,
mean_prob = mean(probability * 100),
current_stim = as.numeric(class == unique(stim))
)]
dt_odd_long_sub %>%
dt_odd_long_mean = # average across participants:
.[, by = .(classification, seq_tr, class, stim, current_stim), .(
mean_prob = mean(mean_prob),
num_subs = .N,
sem_upper = (mean(mean_prob) + (sd(mean_prob)/sqrt(.N))),
sem_lower = (mean(mean_prob) - (sd(mean_prob)/sqrt(.N)))
%>%
)] # create an additional variable that expresses time in seconds:
mutate(time = (seq_tr - 1) * 1.25) %>%
# check if the number of participants is correct:
verify(all(num_subs == 36))
2.5.0.1 Figure 2b
We plot the single-trial multi-variate decoding time courses on slow trials:
ggplot(data = subset(dt_odd_long_mean, classification == "ovr"), aes(
plot.odd.long =x = as.factor(seq_tr), y = as.numeric(mean_prob))) +
facet_wrap(~ as.factor(stim)) +
geom_line(data = subset(dt_odd_long_sub, classification == "ovr"), aes(
group = as.factor(interaction(id, class)), color = fct_rev(as.factor(current_stim))), alpha = 0.0) +
geom_line(data = subset(dt_odd_long_sub, classification == "ovr" & current_stim == 0), aes(
group = as.factor(interaction(id, class)), color = fct_rev(as.factor(current_stim))), alpha = 0.3) +
geom_line(data = subset(dt_odd_long_sub, classification == "ovr" & current_stim == 1), aes(
group = as.factor(interaction(id, class)), color = fct_rev(as.factor(current_stim))), alpha = 0.3) +
geom_ribbon(
data = subset(dt_odd_long_mean, classification == "ovr"),
aes(ymin = sem_lower, ymax = sem_upper, fill = fct_rev(as.factor(current_stim)),
group = as.factor(class)), alpha = 0.0, color = NA) +
geom_line(
data = subset(dt_odd_long_mean, classification == "ovr", alpha = 0),
aes(color = fct_rev(as.factor(current_stim)), group = as.factor(class))) +
geom_ribbon(
data = subset(dt_odd_long_mean, classification == "ovr" & current_stim == 1),
aes(ymin = sem_lower, ymax = sem_upper, fill = fct_rev(as.factor(current_stim)),
group = as.factor(class)), alpha = 0.3, color = NA) +
geom_line(
data = subset(dt_odd_long_mean, classification == "ovr" & current_stim == 1),
aes(color = fct_rev(as.factor(current_stim)), group = as.factor(class))) +
ylab("Probability (%)") + xlab("Time from stimulus onset (TRs)") +
scale_fill_manual(name = "Classified class", values = c("black", "lightgray"), labels = c("true", "other")) +
scale_color_manual(name = "Classified class", values = c("black", "lightgray"), labels = c("true", "other")) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 80)) +
theme(legend.position = c(1, 0), legend.justification = c(1, 0)) +
#theme(legend.position = c(0.95, 0.8), legend.justification = c(1, 0)) +
#theme(legend.title = element_text(size = 8), legend.text = element_text(size = 8)) +
#theme(legend.key.size = unit(0.7, "line")) +
scale_x_discrete(labels = label_fill(seq(0, 7, 1), mod = 1), breaks = seq(0, 7, 1)) +
guides(color = guide_legend(ncol = 2), fill = guide_legend(ncol = 2)) +
theme(strip.text.x = element_text(margin = margin(b = 2, t = 2))) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
#theme(strip.text.x = element_blank())
plot.odd.long
2.5.0.2 Source Data File Fig. 2b
subset(dt_odd_long_sub, classification == "ovr") %>%
select(-num, -classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_2b.csv"),
row.names = FALSE)
We compare the mean classification probability of the current stimulus versus the mean of all other stimuli for each TR:
dt_pred %>%
dt_odd_long_mean_stat = # select data for the oddball long decoding
# leave out the redundant "other" class predictions:
filter(test_set == "test-odd_long" & class != "other" & mask == "cv") %>%
setDT(.) %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>% setDT(.) %>%
# average across stimuli and runs for each participant
# create a new variable that indicated whether stimulus is current or not:
.[, by = .(classification, id, seq_tr, class, stim), .(
num_stim = .N,
mean_prob = mean(probability * 100),
stim_type = ifelse(class == unique(stim), "current", "other")
%>%
)] verify(num_stim <= 96) %>%
# average across stimulus type:
.[, by = .(classification, id, seq_tr, stim, stim_type), .(
num_class = .N,
mean_prob = mean(mean_prob)
%>%
)] verify(num_class %in% c(1, 4)) %>%
select(., -num_class) %>%
# turn into wide format:
spread(key = stim_type, value = mean_prob) %>% setDT(.) %>%
# calculate a paired t-test at every TR for each unique stimulus to
# compare probability of the current vs. the mean other class probabilities:
.[, by = .(classification, seq_tr, stim), {
t.test(current, other, paired = TRUE, alternative = "two.sided")
results =list(
tvalue = results$statistic,
df = results$parameter,
pvalue = results$p.value,
cohens_d = (mean(current) - mean(other)) / sd(current - other),
num_subs = .N,
mean_current = mean(current),
mean_other = mean(other),
pvalue_round = round_pvalues(results$p.value)
)%>%
}] # ajdust the p-value for multiple comparisons using Bonferroni correction:
.[, by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "bonferroni", n = .N)
%>%
)] # check if correction was done for 5 (stimuli) by 7 (TRs) = 35 comparisons:
verify(all(num_comp == 5 * 7)) %>%
# verify if number of participants matches:
verify(all(num_subs == 36)) %>%
# check if degrees-of-freedom = n - 1
verify(all(df == num_subs - 1)) %>%
# round the bonferroni-adjusted p-values for clarity:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort the datatable according to classification, stimulus and TR:
setorder(., classification, stim, seq_tr) %>%
# only look at the fourth TR where probabilities are expected to peak
filter(seq_tr == 4 & classification == "ovr")
# print the data table:
::paged_table(dt_odd_long_mean_stat) rmarkdown
2.6 Response functions
2.6.1 Define response functions
We define the sine wave response function:
function(params, time) {
sine_truncated <-if (!is.list(params)) {
as.list(params)
params =names(params) = c('frequency', 'amplitude', 'shift', 'baseline')
} rep(0, 13)
y = params$amp/2 * sin(2*pi*params$freq*time - 2*pi*params$freq*params$shift - 0.5*pi) + params$baseline + params$amp/2
y =# flatten response function after one cycle:
< (params$shift)] = params$baseline
y[time > (params$shift + 1/params$freq)] = params$baseline
y[time return(y)
}
We define a function to evaluate during optimization:
function(params, time, data) {
sine_truncated_eval = sine_truncated(params, time)
y = sum((data - y)^2)
SSE =return(SSE)
}
2.6.2 Mean parameters
We calculate the multivariate decoding time courses for each stimulus class:
# calculate mean probability for every stimulus for every TR:
dt_pred %>%
dt_odd_long_mean_class = # filter for oddball long predictions and only select ovr classifier:
filter(test_set == "test-odd_long" & class != "other" &
classification == "ovr" & mask == "cv") %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>% setDT(.) %>%
# select only predictions for the class currently shown on any given trial:
filter(class == stim) %>% setDT(.) %>%
# average the probability across trials
.[, by = .(id, classification, stim, seq_tr), .(
mean_probability = mean(probability, na.rm = TRUE)
)]
We fit the truncated sine wave response function to data from every participant:
# set optimization parameters:
list("algorithm" = "NLOPT_LN_COBYLA", "xtol_rel" = 1.0e-8, "maxeval" = 1.0e+5)
opts = c(0.2, 0.6, 0, 0.1)
default_params = c(0.01, 0.1, 0, 0)
lower_bounds = c(0.5, 1, 5, 0.3)
upper_bounds = 0:6 time =
dt_odd_long_mean_class %>%
dt_odd_long_fit = # fit the truncated sine wave to probabilities of every decoding timecourse:
.[, by = .(id, classification, stim), {
nloptr(
res <-x0 = default_params, eval_f = sine_truncated_eval, time = time,
data = mean_probability, lb = lower_bounds, ub = upper_bounds, opts = opts)
list(
num_trs = .N,
frequency = res$solution[1],
wavelength = 1/res$solution[1],
amplitude = res$solution[2],
shift = res$solution[3],
baseline = res$solution[4],
params = list(res$solution)
%>%
)}] verify(all(num_trs == length(time)))
# print the mean model parameters:
summary(dt_odd_long_fit[, c("frequency", "wavelength", "amplitude", "shift", "baseline")])
# calculate the mean parameters across all participants to be used later:
dt_odd_long_fit %>%
mean_parameters = # average the fitted parameters across participants:
.[, by = .(classification), .(
mean_freq = mean(frequency),
mean_wavelength = mean(wavelength),
mean_amplitude = mean(amplitude),
mean_shift = mean(shift),
mean_baseline = mean(baseline)
)]
## frequency wavelength amplitude shift
## Min. :0.1307 Min. :2.000 Min. :0.1000 Min. :0.0000
## 1st Qu.:0.1776 1st Qu.:4.711 1st Qu.:0.3982 1st Qu.:0.4021
## Median :0.1942 Median :5.149 Median :0.4860 Median :0.5590
## Mean :0.1962 Mean :5.239 Mean :0.4858 Mean :0.5640
## 3rd Qu.:0.2123 3rd Qu.:5.630 3rd Qu.:0.5907 3rd Qu.:0.7087
## Max. :0.5000 Max. :7.651 Max. :0.8667 Max. :2.4777
## baseline
## Min. :0.02556
## 1st Qu.:0.06147
## Median :0.07928
## Mean :0.08081
## 3rd Qu.:0.09918
## Max. :0.15975
We fit the sine response function for every participant using individual parameters:
dt_odd_long_fit %>%
dt_odd_long_fit_single = # calculate a sine-wave respone function timecourse for every participant:
.[, by = .(id, classification, stim), .(
csine = sine_truncated(params = unlist(params), seq(0, 6, 0.1))
%>%
)] # add a counter that is used for plotting below:
.[, by = .(classification, id, stim), ":=" (
t = seq(0, 6, 0.1))]
2.6.2.1 Figure S4a
We plot the single sine wave fits for three example participants (supplement):
2.6.2.2 Source Data File Fig. S4a
%>%
dt_odd_long_mean_class select(-classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s4a.csv"),
row.names = FALSE)
We calculate the mean shape of the sine wave response function across participants:
dt_odd_long_fit %>%
dt_odd_long_fit_mean = # average the fitting parameters across participants:
.[, by = .(classification, stim), .(
num_subs = .N,
mean_freq = mean(frequency),
mean_amplitude = mean(amplitude),
mean_shift = mean(shift),
mean_baseline = mean(baseline)
%>% verify(all(num_subs == 36)) %>%
)] .[, by = .(classification, stim), .(
csine = sine_truncated(params = c(mean_freq, mean_amplitude, mean_shift, mean_baseline),
seq(0, 6, 0.1)))] %>%
.[, by = .(classification, stim), ":=" (
t = seq(0, 6, 0.1))]
2.6.2.3 Figure S4b
ggplot(data = dt_odd_long_mean_class) +
fig_s2 = geom_line(data = dt_odd_long_mean_class,
aes(x = (seq_tr - 1), y = mean_probability * 100, group = as.factor(id),
color = "Data"), alpha = 0.3) +
geom_line(data = dt_odd_long_fit_mean, aes(x = t, y = csine * 100, color = "Model"),
size = 1) +
facet_wrap(~ as.factor(stim), nrow = 1) +
coord_capped_cart(left = "both", bottom = "both", ylim = c(0, 80)) +
xlab("Time from stimulus onset (in TRs)") + ylab("Probability (%)") +
scale_colour_manual(name = "", values = c("gray", "black"), guide = FALSE) +
scale_x_continuous(labels = label_fill(seq(1, 7, 1), mod = 1), breaks = seq(0, 6, 1)) +
#theme(plot.margin = unit(c(t = 1, r = 1, b = 1, l = 1), "pt")) +
theme(legend.position = "bottom", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = -10, r = 0, b = 0, l = 0)) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_s2
2.6.2.4 Source Data File Fig. S4b
%>%
dt_odd_long_mean_class select(-classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s4b.csv"),
row.names = FALSE)
2.6.2.5 Figure S4
plot_grid(fig_s1, fig_s2, nrow = 2, ncol = 1, hjust = c(0, 0),
rel_heights = c(4, 2), labels = c("a", "b"), label_fontface = "bold")
We evaluate the response function for two time-shifted events:
# horizontal phase shift (in TRs) that will be added to the second sine wave:
0.5
add_shift = seq(0, 8, 0.1)
time = dt_odd_long_fit %>%
dt_odd_long_fit_shift = # average the fitted parameters across participants:
.[, by = .(classification), .(
mean_freq = mean(frequency),
mean_amplitude = mean(amplitude),
mean_shift = mean(shift),
mean_baseline = mean(baseline)
%>%
)] # add variable that adds shift (in TRs) for the second sine wave:
mutate(mean_shift_shifted = mean_shift + add_shift) %>%
mutate(amp = mean_amplitude * sin(add_shift * mean_freq * pi)) %>%
mutate(freq = mean_freq / (add_shift * mean_freq + 1)) %>% setDT(.) %>%
# calculate first and second response filter time-courses
.[, by = .(classification), .(
first = sine_truncated(params = c(mean_freq, mean_amplitude, mean_shift, mean_baseline), time),
second = sine_truncated(params = c(mean_freq, mean_amplitude, mean_shift_shifted, mean_baseline), time),
cosine = amp * sin(2 * pi * time * freq - 2 * pi * freq * mean_shift),
t = time
%>%
)] # shift the cosine wave by 0.6 (only for illustrational purposes):
mutate(cosine = cosine - 0.6) %>% setDT(.) %>%
# signify the tails of the sine wave (that will otherwise be flattened):
.[time < mean_parameters$mean_shift, ":=" (
cosine_tails = cosine,
cosine = NA)] %>%
.[time > (mean_parameters$mean_shift + add_shift + 1/mean_parameters$mean_freq), ":=" (
cosine_tails = cosine,
cosine = -NA)] %>%
# calculate the difference between the first and second wave
# adjust by 0.2 for illustrational purposes only:
mutate(difference = first - second - 0.2) %>%
gather(key = "event", value = "probability", -classification, -t)
We calculate the mean probability timecourses for the true stimulus class across participants:
# calculate the mean decoding time course across all classes and participants:
dt_pred %>%
dt_odd_long_mean = # filter for oddball multivariate data and one-versus-rest classification only:
filter(test_set == "test-odd_long" & class != "other" &
classification == "ovr" & mask == "cv") %>%
# filter out excluded participants:
filter(!(id %in% sub_exclude)) %>%
# filter for all trials where the predicted class matches the true stimulus:
filter(class == stim) %>% setDT(.) %>%
# calculate the mean decoding time courses for each participant:
.[, by = .(id, classification, stim, seq_tr), .(
mean_probability = mean(probability, na.rm = TRUE) * 100)] %>%
# average mean decoding time courses across participants:
.[, by = .(classification, stim, seq_tr), .(
mean_probability = mean(mean_probability),
num_subs = .N,
sem_upper = mean(mean_probability) + (sd(mean_probability)/sqrt(.N)),
sem_lower = mean(mean_probability) - (sd(mean_probability)/sqrt(.N))
%>%
)] verify(all(num_subs == 36))
2.6.2.6 Figure 2c
2.6.2.7 Source Data File Fig. 2c
%>%
dt_odd_long_mean select(-classification, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_2c.csv"),
row.names = FALSE)
2.6.3 Response model and difference dynamics
# get the average horizontal shift of the first sine wave:
mean(dt_odd_long_fit$shift)
t_first =# calculate the shift of the second sine wave (shift + half a wavelength)
mean(dt_odd_long_fit$shift) + add_shift + 1/mean(dt_odd_long_fit$freq)
t_second =# calculate the time point of cross over between the two sine functions:
0.5/mean(dt_odd_long_fit$freq) + mean(dt_odd_long_fit$shift) + add_shift/2
t_crossover =# create a vector of time steps:
seq(0, 8, 0.1)
time =
max(dt_odd_long_fit_shift$probability[dt_odd_long_fit_shift$event == "difference"], na.rm = TRUE)
max_prob = dt_odd_long_fit_shift$t[dt_odd_long_fit_shift$probability == max_prob & !is.na(dt_odd_long_fit_shift$probability)]
t_max_diff = dt_odd_long_fit_shift$probability[dt_odd_long_fit_shift$t == t_max_diff & dt_odd_long_fit_shift$event == "first"] a =
2.6.3.1 Figure 2d
# select colors used for plotting:
c("darkgray", "darkgray", "black", "dodgerblue", "red", "black")
colors =# plot sine wave difference illustration:
ggplot(data = dt_odd_long_fit_shift, aes(
fig_b =x = time, y = probability * 100, group = event, color = event)) +
# create rectangles to indicate the forward and backward phase:
annotate("rect", xmin = t_first, xmax = t_crossover, ymin = -80, ymax = 95,
alpha = 0.05, fill = "dodgerblue") +
annotate("rect", xmin = t_crossover, xmax = t_second, ymin = -80, ymax = 95,
alpha = 0.05, fill = "red") +
# plot the onset of the first event:
geom_vline(xintercept = t_first, color = "gray", linetype = "dashed") +
# plot the offset of the second event (d + lambda + s in manuscript text):
geom_vline(xintercept = t_second, color = "gray", linetype = "dashed") +
# plot the crossover (d + 0.5 * (lambda + s) in manuscript text)
geom_vline(xintercept = t_crossover, color = "gray", linetype = "dashed") +
# plot the horizontal line for the difference time course:
geom_hline(yintercept = -20, color = "gray") +
# plot the horizontal line for the cosine wave:
geom_hline(yintercept = -60, color = "gray") +
# plot the first and second event time courses:
geom_line(aes(x = t, linetype = fct_rev(event)), na.rm = TRUE) +
coord_capped_cart(left = "both", bottom = "both", ylim = c(-80, 85)) +
xlab("Time (in TRs)") + ylab("Probability (a.u.)") +
scale_colour_manual(name = "Serial event", values = colors) +
scale_linetype_manual(values = c(rep("solid", 3), "dashed", "solid")) +
scale_x_continuous(labels = c(label_fill(seq(1,7,1), mod = 1), rep("", 2)),
breaks = seq(0,8,1)) +
theme(axis.text.y = element_blank(), axis.ticks.y = element_line(colour = "white"),
axis.line.y = element_line(colour = "white")) +
theme(legend.position = "none") +
annotate("label", x = 0.5, y = 45, label = "~1^'st'~event",
color = "dodgerblue", parse = TRUE, size = 3) +
annotate("label", x = 0.5, y = 30, label = "~2^'nd'~event",
color = "red", parse = TRUE, size = 3) +
annotate("label", x = 0.5, y = -7, label = "Difference",
color = "black", size = 3) +
annotate("label", x = 0.5, y = -35, label = "Difference\n(no flattening)",
color = "darkgray", size = 3) +
# add annotation for forward period:
geom_segment(aes(x = t_first, xend = t_crossover, yend = 85, y = 85,
colour = "segment"), color = "dodgerblue") +
annotate("label", x = t_first + (t_crossover - t_first)/2, y = 85,
label = "Forward period",
color = "dodgerblue", fill = "white", hjust = 0.5) +
# add annotations for backward period:
geom_segment(aes(x = t_crossover, xend = t_second, y = 85, yend = 85,
colour = "segment"), color = "red") +
annotate("label", x = t_crossover + (t_second - t_crossover)/2, y = 85,
label = "Backward period",
color = "red", fill = "white", hjust = 0.5) +
# add annotation for early forward phase
geom_segment(aes(x = t_first, xend = t_first + (t_crossover - t_first)/2, yend = 70, y = 70,
colour = "segment"), color = "gray") +
annotate("label", x = (t_first + (t_crossover - t_first)/4),
y = 70, label = "early",
color = "gray", fill = "white", hjust = 0.5) +
# add annotation for late forward phase
geom_segment(aes(x = t_first + (t_crossover - t_first)/2, xend = t_crossover,
yend = 68, y = 68, colour = "segment"), color = "gray") +
annotate("label", x = (t_first + (t_crossover - t_first)/4 * 3),
y = 68, label = "late",
color = "gray", fill = "white", hjust = 0.5) +
# add annotation for early backward phase
geom_segment(aes(x = t_crossover, xend = t_crossover + (t_second - t_crossover)/2,
yend = 70, y = 70, colour = "segment"), color = "gray") +
annotate("label", x = (t_crossover + (t_second - t_crossover)/4),
y = 70, label = "early", color = "gray", fill = "white", hjust = 0.5) +
# add annotation for late backward phase
geom_segment(aes(x = t_crossover + (t_second - t_crossover)/2, xend = t_second,
yend = 68, y = 68, colour = "segment"), color = "gray") +
annotate("label", x = (t_crossover + (t_second - t_crossover)/4 * 3),
y = 68, label = "late", color = "gray", fill = "white", hjust = 0.5) +
# add annotation for delta:
geom_segment(aes(x = t_first + (t_crossover - t_first)/2,
xend = t_first + (t_crossover - t_first)/2 + add_shift,
y = 35 , yend = 35, colour = "segment"), color = "darkgray", linetype = "dotted") +
annotate("text", x = t_first + (t_crossover - t_first)/2 - 0.5 + add_shift / 2, y = 36,
label = "delta", color = "darkgray", hjust = 0.5, parse = TRUE) +
# add annotation for TRs:
annotate("text", x = 8, y = -80, label = "1 TR = 1.25 s",
hjust = 1, size = rel(2)) +
theme(axis.line.x = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_b
2.6.3.2 Source Data File Fig. 2d
%>%
dt_odd_long_fit_shift select(-classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_2d.csv"),
row.names = FALSE)
function(s, A) {A*cos(s*mean(dt_odd_long_fit$freq)*pi - 0.5*pi)}
ampfun = seq(0, 0.5/mean(dt_odd_long_fit$freq), 0.01)
cs = data.table(cs = cs, diff = ampfun(cs, 1))
ba = ggplot(data = ba, aes(x = cs, y = diff)) +
fig_c = geom_line() +
xlab("Time-shift (in TRs)") + ylab("Max. diff. in amplitude") +
coord_capped_cart(left = "both", bottom = "both", xlim = c(0,3)) +
scale_x_continuous(labels = c("0", rep("", 2), "Resp. peak"), breaks = seq(0,3)) +
scale_y_continuous(labels = c("0", rep("", 3), "Max")) +
#theme(plot.margin = unit(c(t = 10, r = 30, b = 1, l = 1), "pt")) +
theme(axis.text.y = element_text(angle = 90, hjust = 0.5)) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_c
We plot a combination of the previous plots:
plot_grid(fig_a, fig_b, ncol = 2, hjust = c(0,0),
labels = c("c", "d", "e"), rel_widths = c(2, 4),
label_fontface = "bold", label_size = 14)
2.6.4 Forward and backward period for different speeds
Here, we calculate the expected forward and backward periods for two events separated by different speed levels (different values for delta).
function(speed, stim_dur = 0.1, num_stim = 5, tr = 1.25){
calc_periods =# mean wavelength based on fitted models, in TRs, round to two digits:
round(mean_parameters$mean_wavelength, 2)
lambda =# mean phase-shift based on fitted models, in TRs, round to two digits:
round(mean_parameters$mean_shift, 2)
phase_shift =# delta, in sec (time shift depending on speed)
stim_dur * (num_stim - 1) + (num_stim - 1) * speed
delta_sec =# delta, in trs:
delta_sec / tr
delta_tr =# end of forward period, in TRs:
0.5 * (lambda + delta_tr) + phase_shift
fwd_end =# end of the backward period, in TRs:
lambda + phase_shift + delta_tr
bwd_end =# forward period, in TRs:
list(seq(round(phase_shift) + 1, round(fwd_end - 0.5) + 1))
forward =# backward period, in TRs:
list(seq(round(fwd_end + 0.5) + 1, round(bwd_end) + 1))
backward =# entire relevant trial period, in rounded TRs:
list(seq(round(phase_shift) + 1, round(lambda + phase_shift + delta_tr) + 1))
trial_period =# save results as a data table and return results:
data.table(speed, lambda, delta_sec, delta_tr, fwd_end, bwd_end, forward, backward, trial_period)
dt =return(dt)
}# get the relevant timeperiods for each sequence speed condition:
c(0.032, 0.064, 0.128, 0.512, 2.048)
speeds = do.call(rbind, lapply(speeds, calc_periods))
dt_periods =::paged_table(dt_periods) rmarkdown
We save the periods of interest for different speeds which will be used for the analysis of sequence and repetition trials:
save(dt_periods, file = file.path(path_root, 'data', "tmp", "dt_periods.Rdata"))
2.6.5 Probability differences at different speeds
We plot the probability differences at different speeds:
seq(0, 12, 0.1)
time = c(0.032, 0.064, 0.128, 0.512, 2.048)
speeds = 0.1
stim_dur =
dt_odd_long_fit %>% setDT(.) %>%
dt_odd_seq_sim = # average the fitted parameters across stimuli for each participant:
.[, by = .(classification, id), .(
mean_freq = mean(frequency),
mean_amplitude = mean(amplitude),
mean_shift = mean(shift),
mean_baseline = mean(baseline)
%>%
)] # repeat rows once for each speed condition (5 repetitions in total):
.[rep(seq(1, nrow(.)), length(speeds))] %>%
# add the different speed conditions for every participant:
.[, by = .(classification, id), ":=" (speed = speeds, num_events = 1)] %>%
# repeat rows once for each hypothetical event (here, 2 events):
.[rep(seq(1, nrow(.)), num_events)] %>%
# add a counter for the number of events:
.[, by = .(classification, id, speed), ":=" (event = 5)] %>%
# calculate a speed-specific shift for each event:
.[, by = .(classification, id, speed, event), {
(speed * (event - 1) + (event - 1) * stim_dur)/1.25
shift = mean_amplitude * sin(shift * 0.5 * mean_freq * pi)
amp = (1/(1/mean_freq + shift))
freq = amp * sin(2 * pi * time * freq - 2 * pi * freq * mean_shift)
c_d =# flatten the tails of the response function
time < mean_shift | time > (mean_shift + (1 / freq))
cidx = 0
c_d[cidx] =# save the response function:
list(time = time, probability = c_d)
%>%
}] filter(classification == "ovr") %>% setDT(.)
dt_odd_seq_sim %>%
dt_odd_seq_sim_diff = .[, by = .(classification, speed, time), .(
num_subs = .N,
mean_difference = mean(probability),
sem_upper = mean(probability) + (sd(probability)/sqrt(.N)),
sem_lower = mean(probability) - (sd(probability)/sqrt(.N))
%>% verify(all(num_subs == 36)) )]
save(dt_odd_seq_sim_diff, file = file.path(
"data", "tmp", "dt_odd_seq_sim_diff.Rdata"))
path_root, save(dt_odd_seq_sim, file = file.path(
"data", "tmp", "dt_odd_seq_sim.Rdata")) path_root,
2.6.5.1 Figure 2e
ggplot(data = dt_odd_seq_sim_diff, mapping = aes(
fig_seq_sim_diff =x = time, y = as.numeric(mean_difference), color = as.factor(speed * 1000),
fill = as.factor(speed * 1000))) +
geom_hline(aes(yintercept = 0), linetype = "solid", color = "gray") +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.5, color = NA) +
geom_line() +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
xlab("Time from sequence onset (TRs)") + ylab("Probability difference") +
scale_colour_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
scale_fill_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE,
ylim = c(-0.4, 0.4), xlim = c(0, 12)) +
scale_x_continuous(labels = label_fill(seq(1, 13, 1), mod = 4), breaks = seq(0, 12, 1)) +
guides(color = guide_legend(nrow = 1)) +
geom_segment(aes(x = 0, xend = 0, y = 0.01, yend = 0.4),
arrow = arrow(length = unit(5, "pt")), color = "darkgray") +
geom_segment(aes(x = 0, xend = 0, y = -0.01, yend = -0.4),
arrow = arrow(length = unit(5, "pt")), color = "darkgray") +
annotate(geom = "text", x = 0.4, y = 0.2, label = "Forward order",
color = "darkgray", angle = 90, size = 3) +
annotate(geom = "text", x = 0.4, y = -0.2, label = "Backward order",
color = "darkgray", angle = 90, size = 3) +
annotate("text", x = 12, y = -0.4, label = "1 TR = 1.25 s",
hjust = 1, size = rel(2)) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_seq_sim_diff
2.6.5.2 Source Data File Fig. 2e
%>%
dt_odd_seq_sim_diff select(-num_subs, -classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_2e.csv"),
row.names = FALSE)
2.6.6 Figure 2
plot_grid(fig_odd_peak, plot.odd.long, fig_a, labels = c("a", "b", "c"), ncol = 3,
upper =rel_widths = c(0.8, 3.5, 2), label_fontface = "bold", hjust = c(0, 0))
plot_grid(fig_b, fig_seq_sim_diff, labels = c("d", "e"), nrow = 1,
lower =label_fontface = "bold", hjust = c(0, 0), rel_widths = c(0.45, 0.53))
plot_grid(upper, lower, nrow = 2, ncol = 1, rel_heights = c(2.5, 3))
ggsave(filename = "highspeed_plot_decoding_oddball_data.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 6.5)
ggsave(filename = "wittkuhn_schuck_figure_2.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 6.5)
2.7 Decoding: Sequence trials
2.7.1 Initialization
2.7.1.1 Load data and files
We load the data and relevant functions:
# find the path to the root of this project:
if (!requireNamespace("here")) install.packages("here")
if ( basename(here::here()) == "highspeed" ) {
here::here("highspeed-analysis")
path_root =else {
} here::here()
path_root =
}# source all relevant functions from the setup R script:
load(file.path(path_root, "data", "tmp", "dt_periods.Rdata"))
load(file.path(path_root, "data", "tmp", "dt_odd_seq_sim_diff.Rdata"))
load(file.path(path_root, "data", "tmp", "dt_odd_seq_sim.Rdata"))
c("sub-24", "sub-31", "sub-37", "sub-40") sub_exclude <-
The next step is only evaluated during manual execution:
# source all relevant functions from the setup R script:
source(file.path(path_root, "code", "highspeed-analysis-setup.R"))
2.7.1.2 Data preparation
We create a function to determine early and late zones of forward and backward periods:
function(trs_in){
get_zones =if (length(trs_in) == 3) {
trs_in[c(2)]
early = trs_in[c(3)]
late =else if (length(trs_in) == 4) {
} trs_in[c(2)]
early = trs_in[c(4)]
late =else if (length(trs_in) == 5) {
} trs_in[c(2)]
early = trs_in[c(4)]
late =else if (length(trs_in) == 6) {
} trs_in[c(2, 3)]
early = trs_in[c(5, 6)]
late =else if (length(trs_in) == 7) {
} trs_in[c(2, 3)]
early = trs_in[c(5, 6)]
late =
}return(list(early = early, late = late))
}
We prepare event and decoding data of sequence trials:
# create a subset of the events data table only including sequence task events:
dt_events %>%
dt_events_seq = filter(condition == "sequence" & trial_type == "stimulus")
# create a subset of the decoding data only including the sequence task data:
dt_pred %>%
dt_pred_seq = filter(condition == "sequence" & class != "other" & mask == "cv" & stim != "cue") %>%
setDT(.) %>%
# add serial position, change trial and target cue to the sequence data table:
.[, c("position", "change", "trial_cue", "accuracy", "trial_cue_position") := get_pos(
.(id, trial, class), .SDcols = c("id", "trial", "class")] %>%
.SD, dt_events_seq), by = # add variable to later pool trial_cue_position 1 to 3:
mutate(cue_pos_label = ifelse(trial_cue_position <= 3, "1-3", trial_cue_position)) %>%
setDT(.)
We define the forward and backward periods depending on the response functions:
# define forward and backward period depending on response functions:
for (cspeed in unique(dt_pred_seq$tITI)) {
for (period in c("forward", "backward")) {
# get trs in the relevant forward or backward period based on response functions:
dt_periods[[which(dt_periods$speed == cspeed), period]]
trs_period =# set the period variable in the sequence data table accordingly:
$period[
dt_pred_seq$tITI %in% cspeed & dt_pred_seq$seq_tr %in% trs_period] = period
dt_pred_seqfor (zone in c("early", "late")) {
get_zones(trs_period)[[zone]]
trs_zone =$zone[
dt_pred_seq$tITI %in% cspeed & dt_pred_seq$seq_tr %in% trs_zone] = zone
dt_pred_seq
}
}
}# assign the excluded label to all trs that are not in the forward or backward period:
$period[is.na(dt_pred_seq$period)] = "excluded" dt_pred_seq
We exclude all participants with below-chance performance from the analyses:
# exclude participants with below-chance performance:
dt_pred_seq %>%
dt_pred_seq = filter(!(id %in% sub_exclude)) %>%
verify(length(unique(id)) == 36) %>%
setDT(.)
We calculate the number of correct and incorrect sequence trials:
dt_pred_seq %>%
dt_num_correct <- # calculate the number of trials for each accuracy level for each participant:
.[, by = .(classification, id, accuracy), .(
num_trials = length(unique(trial))
%>%
)] # select only the one-versus-rest decoding approach:
filter(classification == "ovr") %>%
setDT(.) %>%
# verify that the sum of all trials equals 75 for all participants:
verify(.[, by = .(classification, id), .(
sum_trials = sum(num_trials))]$sum_trials == 75) %>%
# complete missing values for number of trials for each accuracy level:
complete(classification, id, accuracy, fill = list(num_trials = 0)) %>%
setDT(.) %>%
# verify that there are two accuracy levels per participant:
verify(.[, by = .(classification, id), .(
num_acc_levels = .N)]$num_acc_levels == 2) %>%
# calculate the mean number of (in)accurate trials per participant:
.[, by = .(classification, accuracy), .(
num_subs = .N,
mean_num_trials = mean(num_trials),
percent_trials = mean(num_trials)/75
)]# print formatted table:
::paged_table(dt_num_correct) rmarkdown
2.7.2 Probability time courses
We calculate the decoding probability time-courses:
# select the variable of interest:
"probability_norm"
variable = dt_pred_seq %>%
dt_pred_seq_prob = # average across trials separately for each position, TR, and participant
.[, by = .(id, classification, tITI, period, seq_tr, position), .(
num_trials = .N,
mean_prob = mean(get(variable)) * 100
%>%
)] # check if the averaged data consists of 15 sequence trial per participant:
verify(all(num_trials == 15)) %>%
# average across participants and calculate standard error of the mean:
.[, by = .(classification, tITI, period, seq_tr, position), .(
num_subs = .N,
mean_prob = mean(mean_prob),
sem_upper = mean(mean_prob) + (sd(mean_prob)/sqrt(.N)),
sem_lower = mean(mean_prob) - (sd(mean_prob)/sqrt(.N))
%>%
)] # check if averaged data is consistent with expected number of participants:
verify(all(num_subs == 36)) %>%
# create a new variable that expresses TRs as time from stimulus onset:
mutate(time = (seq_tr - 1) * 1.25) %>%
setDT(.)
2.7.2.1 Figure 3a
We plot the decoding probability time-courses:
function(dt1){
plot_raw_probas = dt1 %>%
dt_reduced = setDT(.) %>%
.[, by = .(classification, tITI, period), .(
xmin = min(seq_tr) - 0.5,
xmax = max(seq_tr) + 0.5
%>%
)] filter(period != "excluded") %>%
mutate(fill = ifelse(period == "forward", "dodgerblue", "red"))
ggplot(data = dt1, mapping = aes(
plot =x = as.factor(seq_tr), y = as.numeric(mean_prob),
group = as.factor(position)), environment = environment()) +
geom_rect(data = dt_reduced, aes(
xmin = xmin, xmax = xmax, ymin = 0, ymax = 40),
alpha = 0.05, inherit.aes = FALSE, show.legend = FALSE, fill = dt_reduced$fill) +
facet_wrap(facets = ~ as.factor(tITI), labeller = get_labeller(array = dt1$tITI), nrow = 1) +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper,
fill = as.factor(position)), alpha = 0.5) +
geom_line(mapping = aes(color = as.factor(position))) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
xlab("Time from sequence onset (TRs; 1 TR = 1.25 s)") +
ylab("Probability (%)") +
scale_color_manual(values = color_events, name = "Serial event") +
scale_fill_manual(values = color_events, name = "Serial event") +
scale_x_discrete(labels = label_fill(seq(1, 13, 1), mod = 4), breaks = seq(1, 13, 1)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 25)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) +
theme(strip.text.x = element_text(margin = margin(b = 2, t = 2))) +
guides(color = guide_legend(nrow = 1))
return(plot)
} plot_raw_probas(dt1 = subset(dt_pred_seq_prob, classification == "ovr"))
fig_seq_probas = fig_seq_probas
2.7.2.2 Source Data File Fig. 3a
subset(dt_pred_seq_prob, classification == "ovr") %>%
select(-classification, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3a.csv"),
row.names = FALSE)
We plot the decoding probabilities as a heat-map:
2.7.3 Influence of target cue position
We analyze the probabilities by target cue position:
# select the variable of interest:
"probability_norm"
variable = dt_pred_seq %>%
dt_pred_seq_prob_cue = # average across trials separately for each position, TR, and participant
.[, by = .(id, classification, tITI, period, seq_tr, position, cue_pos_label), .(
num_trials = .N,
mean_prob = mean(get(variable)) * 100
%>%
)] # average across participants and calculate standard error of the mean:
.[, by = .(classification, tITI, period, seq_tr, position,cue_pos_label), .(
num_subs = .N,
mean_num_trials = mean(num_trials),
sd_num_trials = sd(num_trials),
mean_prob = mean(mean_prob),
sem_upper = mean(mean_prob) + (sd(mean_prob)/sqrt(.N)),
sem_lower = mean(mean_prob) - (sd(mean_prob)/sqrt(.N))
%>%
)] # check if averaged data is consistent with expected number of participants:
verify(all(num_subs == 36)) %>%
# check that the SD of the number of trials per cue position is 0
# which means that each participant has the same number of trials per cue position:
verify(all(sd_num_trials == 0)) %>%
verify(all(mean_num_trials == 5)) %>%
# create a new variable that expresses TRs as time from stimulus onset:
mutate(time = (seq_tr - 1) * 1.25) %>%
transform(tITI = paste0(as.numeric(tITI) * 1000, " ms")) %>%
transform(tITI = factor(tITI, levels = c(
"32 ms", "64 ms", "128 ms", "512 ms", "2048 ms"))) %>%
setDT(.)
We plot the probabilities by target cue position:
function(dt1){
plot_raw_probas_cue = ggplot(data = dt1, mapping = aes(
plot =x = as.factor(seq_tr), y = as.numeric(mean_prob),
group = as.factor(position))) +
facet_grid(rows = vars(cue_pos_label), cols = vars(tITI)) +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper,
fill = as.factor(position)), alpha = 0.5) +
geom_line(mapping = aes(color = as.factor(position))) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
xlab("Time from sequence onset (TRs)") + ylab("Probability (%)") +
scale_color_manual(values = color_events, name = "Serial event") +
scale_fill_manual(values = color_events, name = "Serial event") +
scale_x_discrete(labels = label_fill(seq(1, 13, 1), mod = 4), breaks = seq(1, 13, 1)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 25)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
theme(strip.text.x = element_text(margin = margin(b = 2, t = 2))) +
guides(color = guide_legend(nrow = 1))
return(plot)
} plot_raw_probas_cue(dt1 = subset(dt_pred_seq_prob_cue, classification == "ovr"))
fig_seq_probas_cue = fig_seq_probas_cue
2.7.4 Regression slope timecourses
We compare the mean indices of association (regression slope, correlation, mean serial position) for every TR:
# select positions within every TR that should be selected:
seq(1, 5)
pos_sel =# define relevant variables:
"probability_norm"
variable = "kendall"
cor_method =# calculate indices of association at every TR:
dt_pred_seq %>%
dt_pred_seq_cor = # here, we can filter for specific sequence events:
filter(position %in% seq(1, 5, by = 1)) %>%
setDT(.) %>%
# order positions by decreasing probability and calculate step size
# calculate correlation and slope between position and probability
# verify that there are five probabilities (one for each class) per volume
# verify that all correlations range between -1 and 1
.[, by = .(id, classification, tITI, period, trial_tITI, seq_tr), {
# order the probabilities in decreasing order (first = highest):
order(get(variable), decreasing = TRUE)
prob_order_idx =# order the positions by probability:
position[prob_order_idx]
pos_order =# order the probabilities:
get(variable)[prob_order_idx]
prob_order =# select positions
pos_order[pos_sel]
pos_order_sel = prob_order[pos_sel]
prob_order_sel =list(
# calculate the number of events:
num_events = length(pos_order_sel[!is.na(pos_order_sel)]),
# calculate the mean step size between probability-ordered events:
mean_step = mean(diff(pos_order_sel)),
# calculate the mean correlation between positions and their probabilities:
cor = cor.test(pos_order_sel, prob_order_sel, method = cor_method)$estimate,
# calculate the slope of a linear regression between position and probabilities:
slope = coef(lm(prob_order_sel ~ pos_order_sel))[2]
# verify that the number of events matches selection and correlations -1 < r < 1
%>% verify(all(num_events == length(pos_sel))) %>% #verify(between(cor, -1, 1)) %>%
)}] # average across trials for each participant (flip values by multiplying with -1):
# verify that the number of trials per participant is correct:
.[, by = .(id, classification, tITI, period, seq_tr), .(
num_trials = .N,
mean_cor = mean(cor) * (-1),
mean_step = mean(mean_step),
mean_slope = mean(slope) * (-1)
%>%
)] verify(all(num_trials == 15)) %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
mutate(color = ifelse(period_short == "fwd", "dodgerblue", "red")) %>% setDT(.)
We compare the mean indices of association (regression slope, correlation, mean serial position) against zero (the expectation of no association) for every TR:
function(data, variable){
seq_test_time <- data %>%
data_out = # average across participants for every speed at every TR:
# check if the number of participants matches:
.[, by = .(classification, tITI, period, seq_tr), {
# perform a two-sided one-sample t-test against zero (baseline):
t.test(get(variable), alternative = "two.sided", mu = 0);
ttest_results =list(
num_subs = .N,
mean_variable = mean(get(variable)),
pvalue = ttest_results$p.value,
tvalue = ttest_results$statistic,
df = ttest_results$parameter,
cohens_d = round(abs(mean(mean(get(variable)) - 0)/sd(get(variable))), 2),
sem_upper = mean(get(variable)) + (sd(get(variable))/sqrt(.N)),
sem_lower = mean(get(variable)) - (sd(get(variable))/sqrt(.N))
%>% verify(all(num_subs == 36)) %>% verify(all((num_subs - df) == 1)) %>%
)}] # adjust p-values for multiple comparisons (filter for forward and backward period):
# check if the number of comparisons matches expectations:
.[period %in% c("forward", "backward"), by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>% verify(all(num_comp == 39, na.rm = TRUE)) %>%
)] # round the original p-values according to the apa standard:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort data table:
setorder(., classification, period, tITI, seq_tr) %>%
# create new variable indicating significance below 0.05
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", ""))
return(data_out)
}
# filter for significant p-values to make reporting easier:
::paged_table(seq_test_time(data = dt_pred_seq_cor, variable = "mean_cor") %>%
rmarkdown filter(pvalue_adjust < 0.05, classification == "ovr"))
::paged_table(seq_test_time(data = dt_pred_seq_cor, variable = "mean_step") %>%
rmarkdown filter(pvalue_adjust < 0.05, classification == "ovr"))
::paged_table(seq_test_time(data = dt_pred_seq_cor, variable = "mean_slope") %>%
rmarkdown filter(pvalue_adjust < 0.05, classification == "ovr"))
function(dt, variable){
plot_seq_cor_time =# select the variable of interest, determine y-axis label and adjust axis:
if (variable == "mean_slope") {
"Regression slope"
ylabel = 0.1
adjust_axis =else if (variable == "mean_cor") {
} expression("Correlation ("*tau*")")
ylabel = 1
adjust_axis =else if (variable == "mean_step") {
} "Mean step size"
ylabel = 1
adjust_axis =
}
ggplot(data = dt, mapping = aes(
plot =x = seq_tr, y = mean_variable, group = as.factor(as.numeric(tITI) * 1000),
fill = as.factor(as.numeric(tITI) * 1000))) +
geom_hline(aes(yintercept = 0), linetype = "solid", color = "gray") +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.5, color = NA) +
geom_line(mapping = aes(color = as.factor(as.numeric(tITI) * 1000))) +
xlab("Time from sequence onset (TRs)") + ylab(ylabel) +
scale_colour_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
scale_fill_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
scale_x_continuous(labels = label_fill(seq(1, 13, 1), mod = 4), breaks = seq(1, 13, 1)) +
guides(color = guide_legend(nrow = 1)) +
annotate("text", x = 1, y = -0.4 * adjust_axis, label = "1 TR = 1.25 s",
hjust = 0, size = rel(2)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE,
ylim = c(-0.4 * adjust_axis, 0.4 * adjust_axis)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
geom_segment(aes(x = 0.05, xend = 0.05, y = 0.01 * adjust_axis, yend = 0.4 * adjust_axis),
arrow = arrow(length = unit(5, "pt")), color = "darkgray") +
geom_segment(aes(x = 0.05, xend = 0.05, y = -0.01 * adjust_axis, yend = -0.4 * adjust_axis),
arrow = arrow(length = unit(5, "pt")), color = "darkgray") +
annotate(geom = "text", x = 0.4, y = 0.2 * adjust_axis, label = "Forward order",
color = "darkgray", angle = 90, size = 3) +
annotate(geom = "text", x = 0.4, y = -0.2 * adjust_axis, label = "Backward order",
color = "darkgray", angle = 90, size = 3)
return(plot)
}
2.7.4.1 Figure 3b
plot_seq_cor_time(dt = subset(
fig_seq_cor_time =seq_test_time(data = dt_pred_seq_cor, variable = "mean_cor"),
== "ovr"), variable = "mean_cor")
classification
plot_seq_cor_time(dt = subset(
fig_seq_slope_time =seq_test_time(data = dt_pred_seq_cor, variable = "mean_slope"),
== "ovr"), variable = "mean_slope")
classification
plot_seq_cor_time(dt = subset(
fig_seq_step_time =seq_test_time(data = dt_pred_seq_cor, variable = "mean_step"),
== "ovr"), variable = "mean_step")
classification
fig_seq_cor_time; fig_seq_slope_time; fig_seq_step_time
2.7.4.2 Source Data File Fig. 3b
subset(
seq_test_time(data = dt_pred_seq_cor, variable = "mean_slope"),
== "ovr") %>%
classification select(-classification, -num_subs, -num_comp, -pvalue_adjust,
-pvalue_round, -pvalue_adjust_round, -pvalue, -df, -cohens_d,
-tvalue, -significance) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3b.csv"),
row.names = FALSE)
2.7.4.3 Source Data File Fig. S5a
subset(
seq_test_time(data = dt_pred_seq_cor, variable = "mean_cor"),
== "ovr") %>%
classification select(-classification, -num_subs, -num_comp, -pvalue_adjust,
-pvalue_round, -pvalue_adjust_round, -pvalue, -df, -cohens_d,
-tvalue, -significance) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s5a.csv"),
row.names = FALSE)
2.7.4.4 Source Data File Fig. S5c
subset(
seq_test_time(data = dt_pred_seq_cor, variable = "mean_step"),
== "ovr") %>%
classification select(-classification, -num_subs, -num_comp, -pvalue_adjust,
-pvalue_round, -pvalue_adjust_round, -pvalue, -df, -cohens_d,
-tvalue, -significance) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s5c.csv"),
row.names = FALSE)
We test depending on the target cue position:
# select positions within every TR that should be selected:
seq(1, 5)
pos_sel =# define relevant variables:
"probability_norm"
variable = "kendall"
cor_method =# calculate indices of association at every TR:
dt_pred_seq %>%
dt_pred_seq_cor_cue = # here, we can filter for specific sequence events:
filter(position %in% seq(1, 5, by = 1)) %>% setDT(.) %>%
# order positions by decreasing probability and calculate step size
# calculate correlation and slope between position and probability
# verify that there are five probabilities (one for each class) per volume
# verify that all correlations range between -1 and 1
.[, by = .(id, classification, tITI, period, trial_tITI, seq_tr, cue_pos_label), {
# order the probabilities in decreasing order (first = highest):
order(get(variable), decreasing = TRUE)
prob_order_idx =# order the positions by probability:
position[prob_order_idx]
pos_order =# order the probabilities:
get(variable)[prob_order_idx]
prob_order =# select positions
pos_order[pos_sel]
pos_order_sel = prob_order[pos_sel]
prob_order_sel =list(
# calculate the number of events:
num_events = length(pos_order_sel[!is.na(pos_order_sel)]),
# calculate the mean step size between probability-ordered events:
mean_step = mean(diff(pos_order_sel)),
# calculate the mean correlation between positions and their probabilities:
cor = cor.test(pos_order_sel, prob_order_sel, method = cor_method)$estimate,
# calculate the slope of a linear regression between position and probabilities:
slope = coef(lm(prob_order_sel ~ pos_order_sel))[2]
# verify that the number of events matches selection and correlations -1 < r < 1
%>% verify(all(num_events == length(pos_sel))) %>% #verify(between(cor, -1, 1)) %>%
)}] # average across trials for each participant (flip values by multiplying with -1):
# verify that the number of trials per participant is correct:
.[, by = .(id, classification, tITI, period, seq_tr, cue_pos_label), .(
num_trials = .N,
mean_cor = mean(cor) * (-1),
mean_step = mean(mean_step),
mean_slope = mean(slope) * (-1)
%>%
)] verify(all(num_trials == 5)) %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
mutate(color = ifelse(period_short == "fwd", "dodgerblue", "red")) %>% setDT(.)
function(data, variable){
seq_test_time_cue <- data %>%
data_out = # average across participants for every speed at every TR:
# check if the number of participants matches:
.[, by = .(classification, tITI, period, seq_tr, cue_pos_label), {
# perform a two-sided one-sample t-test against zero (baseline):
t.test(get(variable), alternative = "two.sided", mu = 0);
ttest_results =list(
num_subs = .N,
mean_variable = mean(get(variable)),
pvalue = ttest_results$p.value,
tvalue = ttest_results$statistic,
df = ttest_results$parameter,
cohens_d = round(abs(mean(mean(get(variable)) - 0)/sd(get(variable))), 2),
sem_upper = mean(get(variable)) + (sd(get(variable))/sqrt(.N)),
sem_lower = mean(get(variable)) - (sd(get(variable))/sqrt(.N))
%>% verify(all(num_subs == 36)) %>% verify(all((num_subs - df) == 1)) %>%
)}] # adjust p-values for multiple comparisons (filter for forward and backward period):
# check if the number of comparisons matches expectations:
.[period %in% c("forward", "backward"), by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>% #verify(all(num_comp == 39, na.rm = TRUE)) %>%
)] # round the original p-values according to the apa standard:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort data table:
setorder(., classification, period, tITI, seq_tr) %>%
# create new variable indicating significance below 0.05
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", ""))
return(data_out)
}
plot_seq_cor_time_cue(dt = subset(
fig_seq_slope_time_cue =seq_test_time_cue(data = dt_pred_seq_cor_cue, variable = "mean_slope"),
== "ovr"), variable = "mean_slope")
classification fig_seq_slope_time_cue
2.7.5 Correlation of regression time courses
2.7.5.1 Correlation between participants
We calculate the correlations between the predicted and the observed time courses between participants for each of the five speed conditions (inter-stimulus intervals):
# observed time courses:
seq_test_time(
dt_data_between =data = dt_pred_seq_cor, variable = "mean_slope") %>%
filter(classification == "ovr") %>%
transform(tITI = as.factor(as.numeric(tITI) * 1000)) %>%
setorder(classification, tITI, seq_tr)
# predicted time courses:
dt_odd_seq_sim_diff %>%
dt_model_between = transform(time = time + 1) %>%
filter(time %in% seq(1, 13, 1)) %>%
setorder(classification, speed, time)
# combine in one data table:
data.table(
dt_between =speed = dt_data_between$tITI,
tr = dt_data_between$seq_tr,
empirical = dt_data_between$mean_variable,
prediction = dt_model_between$mean_difference)
# calculate the correlation between
dt_between %>%
dt_between_results = .[, by = .(speed), {
cor.test(empirical, prediction, method = "pearson")
cor =list(
num_trs = .N,
pvalue = cor$p.value,
pvalue_round = round_pvalues(cor$p.value),
correlation = round(cor$estimate, 2)
)%>%
}] verify(num_trs == 13) %>%
select(-num_trs)
# show the table with the correlations:
::paged_table(dt_between_results) rmarkdown
2.7.5.2 Figure 3d
We plot the correlations between the regression slope time courses predicted by the model vs. the observed data between participants:
ggplot(
fig_seq_cor_between =data = dt_between,
mapping = aes(
x = prediction, y = empirical, color = speed, fill = speed)) +
geom_point(alpha = 1) +
geom_smooth(method = lm, se = FALSE, alpha = 0.5, fullrange = TRUE) +
scale_colour_viridis(
name = "Speed (ms)", discrete = TRUE,
option = "cividis", guide = FALSE) +
scale_fill_viridis(
name = "Speed (ms)", discrete = TRUE,
option = "cividis", guide = FALSE) +
xlab("Predicted slope") +
ylab("Observed slope") +
# guides(color = guide_legend(nrow = 1)) +
coord_capped_cart(
left = "both", bottom = "both", expand = TRUE,
xlim = c(-0.4, 0.4), ylim = c(-0.05, 0.05)) +
theme(legend.position = "top",
legend.direction = "horizontal",
legend.justification = "center",
legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
# show the plot:
fig_seq_cor_between
2.7.5.3 Source Data File Fig. 3d
%>%
dt_between write.csv(., file = file.path(path_sourcedata, "source_data_figure_3d.csv"),
row.names = FALSE)
2.7.5.4 Correlation within participants
We calculate the correlations between the predicted and the observed time courses within participants for each of the five speed conditions (inter-stimulus intervals):
# observed regression slope time courses
dt_pred_seq_cor %>%
dt_data_within = filter(classification == "ovr") %>%
transform(tITI = as.factor(as.numeric(tITI) * 1000)) %>%
setorder(classification, id, tITI, seq_tr)
# predicted regression slope time courses
dt_odd_seq_sim %>%
dt_model_within = transform(time = time + 1) %>%
filter(time %in% seq(1, 13, 1)) %>%
setorder(classification, id, speed, time)
# combine in one data table:
data.table(
dt_within =id = dt_data_within$id,
speed = dt_data_within$tITI,
time = dt_data_within$seq_tr,
empirical = dt_data_within$mean_slope,
prediction = dt_model_within$probability)
# run correlations:
dt_within %>%
dt_within_cor = .[, by = .(id, speed), {
cor.test(empirical, prediction, method = "pearson")
cor =list(
num_trs = .N,
pvalue = cor$p.value,
estimate = as.numeric(cor$estimate)
%>%
)}] verify(num_trs == 13)
# run t-tests over correlation coefficients for each speed level:
setDT(dt_within_cor) %>%
dt_within_cor_results = .[, by = .(speed), {
t.test(
ttest_results =mu = 0, alternative = "two.sided", paired = FALSE)
estimate, list(
num_subs = .N,
mean_estimate = round(mean(estimate), 2),
pvalue = ttest_results$p.value,
tvalue = round(ttest_results$statistic, 2),
df = ttest_results$parameter,
cohens_d = round((mean(estimate) - 0)/sd(estimate), 2)
%>%
)}] verify(num_subs == 36) %>%
verify((num_subs - df) == 1) %>%
# adjust p-values for multiple comparisons:
# check if the number of comparisons matches expectations:
.[, ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] # round the original p-values according to the apa standard:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# create new variable indicating significance below 0.05
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", ""))
# show the table with the t-test results:
::paged_table(dt_within_cor_results) rmarkdown
2.7.5.5 Figure 3e
We plot the correlations between the predicted and the observed time courses within participants for each of the five speed conditions (inter-stimulus intervals):
ggplot(
fig_seq_cor_within =data = dt_within_cor,
mapping = aes(
x = speed, y = estimate, color = speed, fill = speed, group = speed)) +
stat_summary(geom = "bar", fun = "mean") +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5,
color = "white", alpha = 0.5,
inherit.aes = TRUE, binwidth = 0.05) +
stat_summary(geom = "errorbar", fun.data = "mean_se", width = 0, color = "black") +
scale_colour_viridis(
name = "Speed (ms)", discrete = TRUE, option = "cividis", guide = FALSE) +
scale_fill_viridis(
name = "Speed (ms)", discrete = TRUE, option = "cividis", guide = FALSE) +
xlab("Speed (in ms)") +
ylab("Correlation (r)") +
#guides(color = guide_legend(nrow = 1)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
theme(axis.ticks.x = element_line(colour = "white"),
axis.line.x = element_line(colour = "white")) +
theme(axis.title.x = element_blank(), axis.text.x = element_blank()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
# show figure:
fig_seq_cor_within
2.7.5.6 Source Data File Fig. 3e
%>%
dt_within_cor select(-num_trs, -pvalue) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3e.csv"),
row.names = FALSE)
2.7.6 Regression slope means
Calculate the average correlation or average regression slope for each time period (forward versus backward) for all five speed conditions:
function(data, variable){
seq_test_period <- data %>%
data_out = # filter out the excluded time period (select only forward and backward period):
filter(period != "excluded") %>%
setDT(.) %>%
# average for each time period and speed condition for every participant:
.[, by = .(classification, id, tITI, period), .(
mean_variable = mean(get(variable)))] %>%
# average across participants for every speed at every TR:
# check if the number of participants matches:
.[, by = .(classification, tITI, period), {
# perform a two-sided one-sample t-test against zero (baseline):
t.test(mean_variable, alternative = "two.sided", mu = 0);
ttest_results =list(
num_subs = .N,
mean_variable = mean(mean_variable),
pvalue = ttest_results$p.value,
tvalue = round(abs(ttest_results$statistic), 2),
df = ttest_results$parameter,
cohens_d = abs(round((mean(mean_variable) - 0) / sd(mean_variable), 2)),
sem_upper = mean(mean_variable) + (sd(mean_variable)/sqrt(.N)),
sem_lower = mean(mean_variable) - (sd(mean_variable)/sqrt(.N))
)%>%
}] verify(all(num_subs == 36)) %>%
verify(all((num_subs - df) == 1)) %>%
# adjust p-values for multiple comparisons:
# check if the number of comparisons matches expectations:
.[period %in% c("forward", "backward"), by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] verify(num_comp == 10) %>%
# add variable that indicates significance with stupid significance stars:
mutate(significance = ifelse(pvalue < 0.05, "*", "")) %>%
# round the original p-values according to APA manual:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort data table:
setorder(classification, period, tITI) %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
mutate(color = ifelse(period_short == "fwd", "dodgerblue", "red")) %>% setDT(.)
return(data_out)
}
::paged_table(
rmarkdownseq_test_period(data = dt_pred_seq_cor, variable = "mean_cor") %>%
filter(pvalue_adjust < 0.05, classification == "ovr"))
::paged_table(
rmarkdownseq_test_period(data = dt_pred_seq_cor, variable = "mean_step") %>%
filter(pvalue_adjust < 0.05, classification == "ovr"))
::paged_table(
rmarkdownseq_test_period(data = dt_pred_seq_cor, variable = "mean_slope") %>%
filter(pvalue_adjust < 0.05, classification == "ovr"))
2.7.6.1 Figure 3c
function(data, variable){
plot_seq_cor_period =
# select the variable of interest, determine y-axis label and adjust axis:
if (variable == "mean_slope") {
"Regression slope"
ylabel = 0.1
adjust_axis =else if (variable == "mean_cor") {
} expression("Correlation ("*tau*")")
ylabel = 1
adjust_axis =else if (variable == "mean_step") {
} "Mean step size"
ylabel = 1
adjust_axis =
}
data.table(xmin = 0, xmax = 5.5, ymin = 0, ymax = 0.4 * adjust_axis)
dt_forward = data.table(xmin = 0, xmax = 5.5, ymin = 0, ymax = -0.4 * adjust_axis)
dt_backward =
# average across participants for every speed at every TR:
data %>% setDT(.) %>%
plot_data = .[, by = .(classification, id, tITI, period_short), .(
mean_variable = mean(get(variable))
%>% filter(classification == "ovr" & period_short != "excluded")
)] seq_test_period(data = data, variable = variable)
plot_stat =
# plot average correlation or betas for each speed condition and time period:
ggplot(data = plot_data, aes(
plot =x = fct_rev(as.factor(period_short)), y = as.numeric(mean_variable),
fill = as.factor(as.numeric(tITI) * 1000))) +
geom_bar(stat = "summary", fun = "mean", width = 0.9, show.legend = TRUE) +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5, alpha = 0.2,
binwidth = 0.01 * adjust_axis, show.legend = FALSE) +
#geom_point(position = position_jitterdodge(jitter.height = 0, seed = 4, jitter.width = 0.2),
# pch = 21, alpha = 0.2, show.legend = FALSE) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
geom_text(data = subset(plot_stat, classification == "ovr"), aes(
x = fct_rev(as.factor(period_short)), y = round_updown(as.numeric(mean_variable), 0.6 * adjust_axis),
label = paste0("d=", sprintf("%.2f", cohens_d), significance)), size = 3.3, show.legend = FALSE,
color = subset(plot_stat, classification == "ovr")$color) +
facet_wrap(~ as.factor(as.numeric(tITI) * 1000), strip.position = "bottom", nrow = 1) +
xlab("Period") + ylab(ylabel) +
scale_fill_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(-0.6, 0.6) * adjust_axis) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) +
theme(axis.ticks.x = element_line(color = "white"),
axis.line.x = element_line(color = "white")) +
theme(legend.position = "top", legend.direction = "horizontal", legend.box = "vertical",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
theme(panel.spacing = unit(0, "lines"), strip.background = element_blank(),
strip.placement = "outside", strip.text = element_blank())
return(plot)
} plot_seq_cor_period(data = dt_pred_seq_cor, variable = "mean_cor")
fig_seq_cor_period = plot_seq_cor_period(data = dt_pred_seq_cor, variable = "mean_slope")
fig_seq_slope_period = plot_seq_cor_period(data = dt_pred_seq_cor, variable = "mean_step")
fig_seq_step_period = fig_seq_cor_period; fig_seq_step_period; fig_seq_slope_period;
2.7.6.2 Source Data File Fig. 3e / S5b / S5d
%>%
dt_pred_seq_cor select(-classification, -num_trials, -color) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3c.csv"),
row.names = FALSE)
%>%
dt_pred_seq_cor select(-classification, -num_trials, -color) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s5b.csv"),
row.names = FALSE)
%>%
dt_pred_seq_cor select(-classification, -num_trials, -color) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s5d.csv"),
row.names = FALSE)
2.7.6.3 Source Data File Fig. S5b
%>%
dt_pred_seq_cor select(-classification, -num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3c.csv"),
row.names = FALSE)
2.7.6.4 Source Data File Fig. S5c
%>%
dt_pred_seq_cor select(-classification, -num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3c.csv"),
row.names = FALSE)
We repeat the same calculations, just splitting up the data by the serial position of the cued image:
function(data, variable){
seq_test_period_cue <- data %>%
data_out = # filter out the excluded time period (select only forward and backward period):
filter(period != "excluded") %>% setDT(.) %>%
# average for each time period and speed condition for every participant:
.[, by = .(classification, id, tITI, period, cue_pos_label), .(
mean_variable = mean(get(variable)))] %>%
# average across participants for every speed at every TR:
# check if the number of participants matches:
.[, by = .(classification, tITI, period, cue_pos_label), {
# perform a two-sided one-sample t-test against zero (baseline):
t.test(mean_variable, alternative = "two.sided", mu = 0);
ttest_results =list(
num_subs = .N,
mean_variable = mean(mean_variable),
pvalue = ttest_results$p.value,
tvalue = round(abs(ttest_results$statistic), 2),
df = ttest_results$parameter,
cohens_d = abs(round((mean(mean_variable) - 0) / sd(mean_variable), 2)),
sem_upper = mean(mean_variable) + (sd(mean_variable)/sqrt(.N)),
sem_lower = mean(mean_variable) - (sd(mean_variable)/sqrt(.N))
)%>% verify(all(num_subs == 36)) %>% verify(all((num_subs - df) == 1)) %>%
}] # adjust p-values for multiple comparisons:
# check if the number of comparisons matches expectations:
.[period %in% c("forward", "backward"), by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>% #verify(num_comp == 10) %>%
)] # add variable that indicates significance with stupid significance stars:
mutate(significance = ifelse(pvalue < 0.05, "*", "")) %>%
# round the original p-values according to APA manual:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort data table:
setorder(classification, period, tITI) %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
mutate(color = ifelse(period_short == "fwd", "dodgerblue", "red")) %>% setDT(.)
return(data_out)
}
::paged_table(
rmarkdownseq_test_period_cue(data = dt_pred_seq_cor_cue, variable = "mean_cor") %>%
filter(pvalue_adjust < 0.05, classification == "ovr"))
::paged_table(
rmarkdownseq_test_period_cue(data = dt_pred_seq_cor_cue, variable = "mean_step") %>%
filter(pvalue_adjust < 0.05, classification == "ovr"))
::paged_table(
rmarkdownseq_test_period_cue(data = dt_pred_seq_cor_cue, variable = "mean_slope") %>%
filter(pvalue_adjust < 0.05, classification == "ovr"))
We plot the same data, just splitting up the data by the serial position of the cued image:
function(data, variable){
plot_seq_cor_period_cue =
# select the variable of interest, determine y-axis label and adjust axis:
if (variable == "mean_slope") {
"Regression slope"
ylabel = 0.1
adjust_axis =else if (variable == "mean_cor") {
} expression("Correlation ("*tau*")")
ylabel = 1
adjust_axis =else if (variable == "mean_step") {
} "Mean step size"
ylabel = 1
adjust_axis =
}
data.table(xmin = 0, xmax = 5.5, ymin = 0, ymax = 0.4 * adjust_axis)
dt_forward = data.table(xmin = 0, xmax = 5.5, ymin = 0, ymax = -0.4 * adjust_axis)
dt_backward =
# average across participants for every speed at every TR:
data %>% setDT(.) %>%
plot_data = .[, by = .(classification, id, tITI, period_short, cue_pos_label), .(
mean_variable = mean(get(variable))
%>% filter(classification == "ovr" & period_short != "excluded")
)] seq_test_period_cue(data = data, variable = variable)
plot_stat =
# plot average correlation or betas for each speed condition and time period:
ggplot(data = plot_data, aes(
plot =x = fct_rev(as.factor(period_short)), y = as.numeric(mean_variable),
fill = as.factor(as.numeric(tITI) * 1000))) +
geom_bar(stat = "summary", fun = "mean", width = 0.9, show.legend = TRUE) +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5, alpha = 0.2,
binwidth = 0.01 * adjust_axis, show.legend = FALSE) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
geom_text(data = subset(plot_stat, classification == "ovr"), aes(
x = fct_rev(as.factor(period_short)), y = round_updown(as.numeric(mean_variable), 0.5 * adjust_axis),
label = paste0("d=", cohens_d, significance)), size = 3.0, show.legend = FALSE,
color = subset(plot_stat, classification == "ovr")$color) +
facet_grid(rows = vars(cue_pos_label),
cols = vars(as.factor(as.numeric(tITI) * 1000))) +
xlab("Period") + ylab(ylabel) +
scale_fill_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(-0.6, 0.6) * adjust_axis) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) +
#theme(axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
theme(legend.position = "top", legend.direction = "horizontal", legend.box = "vertical",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0))
#theme(panel.spacing = unit(0, "lines"), strip.background = element_blank(),
# strip.placement = "outside", strip.text = element_blank())
return(plot)
}
plot_seq_cor_period_cue(data = dt_pred_seq_cor_cue, variable = "mean_cor")
fig_seq_cor_period_cue = plot_seq_cor_period_cue(data = dt_pred_seq_cor_cue, variable = "mean_slope")
fig_seq_slope_period_cue = plot_seq_cor_period_cue(data = dt_pred_seq_cor_cue, variable = "mean_step")
fig_seq_step_period_cue = fig_seq_cor_period_cue; fig_seq_step_period_cue; fig_seq_slope_period_cue;
Combine plots for cue period:
# create a data frame with the relevant data to run the LME:
dt_pred_seq_cor %>%
lme_seq_cor_data = filter(classification == "ovr" & period != "excluded") %>%
transform(tITI = as.factor(tITI))
# define linear mixed effects model with by-participant random intercepts:
lmer(mean_slope ~ tITI * period + (1|id),
lme_seq_cor =data = lme_seq_cor_data, na.action = na.omit, control = lcctrl)
summary(lme_seq_cor)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_slope ~ tITI * period + (1 | id)
## Data: lme_seq_cor_data
## Control: lcctrl
##
## REML criterion at convergence: -7231.9
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.8317 -0.6639 -0.0140 0.6540 3.3043
##
## Random effects:
## Groups Name Variance Std.Dev.
## id (Intercept) 7.510e-06 0.00274
## Residual 3.104e-04 0.01762
## Number of obs: 1404, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) -7.007e-03 1.756e-03 9.090e+02 -3.991 7.11e-05 ***
## tITI0.064 3.263e-04 2.398e-03 1.359e+03 0.136 0.89177
## tITI0.128 2.314e-03 2.243e-03 1.359e+03 1.032 0.30234
## tITI0.512 -3.238e-03 2.243e-03 1.359e+03 -1.444 0.14898
## tITI2.048 -1.266e-02 2.076e-03 1.359e+03 -6.098 1.40e-09 ***
## periodforward 9.762e-03 2.398e-03 1.359e+03 4.072 4.94e-05 ***
## tITI0.064:periodforward -1.205e-04 3.391e-03 1.359e+03 -0.036 0.97166
## tITI0.128:periodforward 6.389e-04 3.283e-03 1.359e+03 0.195 0.84573
## tITI0.512:periodforward 9.803e-03 3.172e-03 1.359e+03 3.091 0.00204 **
## tITI2.048:periodforward 2.986e-02 2.936e-03 1.359e+03 10.170 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) tITI0.064 tITI0.128 tITI0.512 tITI2.048 prdfrw tITI0.064:
## tITI0.064 -0.683
## tITI0.128 -0.730 0.535
## tITI0.512 -0.730 0.535 0.571
## tITI2.048 -0.788 0.577 0.617 0.617
## periodfrwrd -0.683 0.500 0.535 0.535 0.577
## tITI0.064:p 0.483 -0.707 -0.378 -0.378 -0.408 -0.707
## tITI0.128:p 0.499 -0.365 -0.683 -0.390 -0.422 -0.730 0.516
## tITI0.512:p 0.516 -0.378 -0.404 -0.707 -0.436 -0.756 0.535
## tITI2.048:p 0.557 -0.408 -0.436 -0.436 -0.707 -0.816 0.577
## tITI0.128: tITI0.512:
## tITI0.064
## tITI0.128
## tITI0.512
## tITI2.048
## periodfrwrd
## tITI0.064:p
## tITI0.128:p
## tITI0.512:p 0.552
## tITI2.048:p 0.596 0.617
anova(lme_seq_cor)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## tITI 0.001393 0.000348 4 1359 1.1219 0.3445
## period 0.103680 0.103680 1 1359 334.0077 <2e-16 ***
## tITI:period 0.058332 0.014583 4 1359 46.9793 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans(lme_seq_cor, list(pairwise ~ period | tITI))
emmeans_results = round_pvalues(summary(emmeans_results[[2]])$p.value) emmeans_pvalues =
2.7.7 Serial target position
We calculate the average serial position at each TR:
dt_pred_seq %>%
dt_pred_seq_pos = # get the position with the highest probability at every TR:
.[, by = .(classification, id, period, tITI, trial_tITI, seq_tr), .(
num_positions = .N,
max_position = position[which.max(probability_norm)]
%>%
)] # verify that the number of position per TR matches:
verify(all(num_positions == 5)) %>%
# average the maximum position across trials for each speed condition:
.[, by = .(classification, id, period, tITI, seq_tr), .(
num_trials = .N,
mean_position = mean(max_position)
%>%
)] # verify that the number of trials per participant is correct:
verify(all(num_trials == 15)) %>%
# calculate the difference of the mean position from baseline (which is 3)
mutate(position_diff = mean_position - 3) %>%
setDT(.) %>%
# set the speed condition and period variable to a factorial variable:
transform(tTII = as.factor(tITI)) %>%
transform(period = as.factor(period))
We calculate whether the average serial position is significantly different from baseline separately for every speed and period (forward vs. backward):
dt_pred_seq_pos %>%
dt_pred_seq_pos_period = # focus on the forward and backward period only:
filter(period != "excluded") %>% setDT(.) %>%
# average the mean position across trs for each period and speed condition:
.[, by = .(classification, id, period, tITI), .(
position_diff = mean(position_diff)
%>%
)] # average across participants for each speed condition and volume:
.[, by = .(classification, period, tITI), {
t.test(position_diff, alternative = "two.sided", mu = 0)
ttest_results =list(
num_subs = .N,
tvalue = round(ttest_results$statistic, 2),
pvalue = ttest_results$p.value,
df = ttest_results$parameter,
cohens_d = abs(round((mean(position_diff) - 0) / sd(position_diff), 2)),
position_diff = mean(position_diff),
conf_lb = round(ttest_results$conf.int[1], 2),
conf_ub = round(ttest_results$conf.int[2], 2),
sd_position = sd(position_diff),
sem_upper = mean(position_diff) + (sd(position_diff)/sqrt(.N)),
sem_lower = mean(position_diff) - (sd(position_diff)/sqrt(.N))
)%>% verify(all(num_subs == 36)) %>%
}] # adjust p-values for multiple comparisons:
.[, by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] verify(all(num_comp == 10)) %>%
# add variable that indicates significance with stupid significance stars:
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", "")) %>%
# round the original p-values:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the p-values adjusted for multiple comparisons:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort the datatable for each speed and TR:
setorder(., classification, period, tITI) %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
mutate(color = ifelse(period_short == "fwd", "dodgerblue", "red")) %>% setDT(.)
%>%
dt_pred_seq_pos_period filter(classification == "ovr", pvalue_adjust < 0.05) %>%
rmarkdown::paged_table(.)
We calculate the mean serial position at every TR and compare it against baseline:
dt_pred_seq_pos %>%
dt_pred_seq_pos_tr = # average across participants for each speed condition and volume:
.[, by = .(classification, period, tITI, seq_tr), {
t.test(mean_position, alternative = "two.sided", mu = 3)
ttest_results =list(
num_subs = .N,
tvalue = ttest_results$statistic,
pvalue = ttest_results$p.value,
df = ttest_results$parameter,
mean_position = mean(mean_position),
sd_position = sd(mean_position),
sem_upper = mean(mean_position) + (sd(mean_position)/sqrt(.N)),
sem_lower = mean(mean_position) - (sd(mean_position)/sqrt(.N))
)%>% verify(all(num_subs == 36)) %>%
}] # adjust p-values for multiple comparisons:
.[period %in% c("forward", "backward"), by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>% verify(all(num_comp == 39, na.rm = TRUE)) %>%
)] # round the original p-values:
mutate(pvalue_rounded = round_pvalues(pvalue)) %>%
# round the p-values adjusted for multiple comparisons:
mutate(pvalue_adjust_rounded = round_pvalues(pvalue_adjust)) %>%
#
mutate(significance = ifelse(pvalue_adjust < 0.05, "***", "")) %>%
# sort the datatable for each speed and TR:
setorder(., classification, period, tITI, seq_tr)
%>%
dt_pred_seq_pos_tr filter(classification == "ovr", pvalue_adjust < 0.05) %>%
rmarkdown::paged_table(.)
list(variable = "mean_position", threshold = 2.021, baseline = 3,
cfg =grouping = c("classification", "tITI"), n_perms = 10000, n_trs = 13)
cluster_permutation(dt_pred_seq_pos_sub, cfg) dt_pred_seq_pos_cluster =
# define linear mixed effects model with by-participant random intercepts:
lmer(position_diff ~ tITI * period + (1 + tITI + period |id),
lme_seq_pos =data = subset(dt_pred_seq_pos, classification == "ovr" & period != "excluded"),
na.action = na.omit, control = lcctrl)
## Warning: Model failed to converge with 2 negative eigenvalues: -1.2e-01 -4.0e+01
summary(lme_seq_pos)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: position_diff ~ tITI * period + (1 + tITI + period | id)
## Data:
## subset(dt_pred_seq_pos, classification == "ovr" & period != "excluded")
## Control: lcctrl
##
## REML criterion at convergence: 1843
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.1107 -0.7007 0.0074 0.6663 3.2056
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 0.000000 0.00000
## tITI0.064 0.001009 0.03177 NaN
## tITI0.128 0.000316 0.01778 NaN 1.00
## tITI0.512 0.008749 0.09354 NaN 1.00 1.00
## tITI2.048 0.013608 0.11665 NaN 1.00 1.00 1.00
## periodforward 0.066908 0.25867 NaN -1.00 -1.00 -1.00 -1.00
## Residual 0.203320 0.45091
## Number of obs: 1404, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 2.130e-01 4.339e-02 1.359e+03 4.908 1.03e-06 ***
## tITI0.064 -1.790e-02 6.159e-02 1.025e+03 -0.291 0.771373
## tITI0.128 -7.824e-02 5.747e-02 1.199e+03 -1.361 0.173671
## tITI0.512 5.000e-02 5.948e-02 3.345e+02 0.841 0.401142
## tITI2.048 2.917e-01 5.659e-02 2.100e+02 5.154 5.86e-07 ***
## periodforward -2.346e-01 7.499e-02 1.810e+02 -3.128 0.002052 **
## tITI0.064:periodforward -7.407e-03 8.678e-02 1.359e+03 -0.085 0.931987
## tITI0.128:periodforward -1.543e-04 8.402e-02 1.359e+03 -0.002 0.998535
## tITI0.512:periodforward -2.710e-01 8.117e-02 1.359e+03 -3.338 0.000865 ***
## tITI2.048:periodforward -8.142e-01 7.515e-02 1.359e+03 -10.834 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) tITI0.064 tITI0.128 tITI0.512 tITI2.048 prdfrw tITI0.064:
## tITI0.064 -0.704
## tITI0.128 -0.755 0.536
## tITI0.512 -0.730 0.536 0.564
## tITI2.048 -0.767 0.570 0.597 0.649
## periodfrwrd -0.579 0.358 0.407 0.271 0.246
## tITI0.064:p 0.500 -0.704 -0.377 -0.365 -0.383 -0.579
## tITI0.128:p 0.516 -0.364 -0.682 -0.377 -0.396 -0.598 0.516
## tITI0.512:p 0.535 -0.377 -0.404 -0.682 -0.410 -0.619 0.535
## tITI2.048:p 0.577 -0.407 -0.436 -0.421 -0.664 -0.668 0.577
## tITI0.128: tITI0.512:
## tITI0.064
## tITI0.128
## tITI0.512
## tITI2.048
## periodfrwrd
## tITI0.064:p
## tITI0.128:p
## tITI0.512:p 0.552
## tITI2.048:p 0.596 0.617
anova(lme_seq_pos)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## tITI 2.000 0.4999 4 191.31 2.4588 0.04694 *
## period 16.834 16.8338 1 36.86 82.7945 5.831e-11 ***
## tITI:period 43.623 10.9056 4 1358.61 53.6377 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans(lme_seq_pos, list(pairwise ~ period | tITI))
emmeans_results = round_pvalues(summary(emmeans_results[[2]])$p.value) emmeans_pvalues =
2.7.7.1 Figure 3g
"position_diff"
variable = dt_pred_seq_pos %>%
plot_data = # average across participants for every speed at every TR:
.[, by = .(classification, id, tITI, period), .(
mean_variable = mean(get(variable))
%>%
)] filter(classification == "ovr" & period != "excluded") %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
mutate(color = ifelse(period_short == "fwd", "dodgerblue", "red")) %>%
setDT(.)
# plot average correlation or betas for each speed condition and time period:
ggplot(data = plot_data, aes(
fig_seq_pos_period =x = fct_rev(as.factor(period_short)), y = as.numeric(mean_variable),
fill = as.factor(as.numeric(tITI) * 1000))) +
geom_bar(stat = "summary", fun = "mean", width = 0.9, show.legend = TRUE) +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 0.5, alpha = 0.2,
binwidth = 0.05, show.legend = FALSE) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
geom_text(data = subset(dt_pred_seq_pos_period, classification == "ovr"), aes(
x = fct_rev(as.factor(period_short)), y = round_updown(as.numeric(get(variable)), 1.2),
label = paste0("d=", sprintf("%.2f", cohens_d), significance)), show.legend = FALSE, size = 3.2,
color = subset(dt_pred_seq_pos_period, classification == "ovr")$color) +
facet_wrap(~ as.factor(as.numeric(tITI) * 1000), strip.position = "bottom", nrow = 1) +
xlab("Period") + ylab("Event position\ncompared to baseline") +
scale_colour_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
scale_fill_viridis(name = "Speed (ms)", discrete = TRUE, option = "cividis") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(-1.5, 1.5)) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
theme(panel.spacing = unit(0, "lines"), strip.background = element_blank(),
strip.placement = "outside", strip.text = element_blank()) +
theme(axis.ticks.x = element_line(colour = "white"),
axis.line.x = element_line(colour = "white")) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_seq_pos_period
2.7.7.2 Source Data File Fig. 3g
subset(plot_data, classification == "ovr") %>%
select(-classification, -color) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3g.csv"),
row.names = FALSE)
2.7.7.3 Figure 3f
2.7.7.4 Source Data File Fig. 3f
subset(dt_pred_seq_pos_tr, classification == "ovr") %>%
select(-classification, -num_subs, -num_comp, -pvalue_adjust,
-pvalue_rounded, -pvalue_adjust_rounded, -pvalue, -df,
-tvalue, -significance, -sd_position) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3f.csv"),
row.names = FALSE)
2.7.8 Transititons
We calculate the step size between consecutively decoded (highest probability) events:
dt_pred_seq %>%
dt_pred_seq_step = # get the position with the highest probability for every TR:
.[, by = .(classification, id, tITI, trial_tITI, seq_tr), ":=" (
num_classes = .N,
rank_order = rank(-probability),
max_prob = as.numeric(probability == max(probability))
%>%
)] # verify that there are five classes per TR:
verify(all(num_classes == 5)) %>%
# sort the data table:
setorder(., classification, id, tITI, trial_tITI, seq_tr) %>%
# select only classes with the highest probability for every TR:
filter(max_prob == 1) %>%
setDT(.) %>%
# check if the rank order of the event with highest probability match:
verify(all(rank_order == max_prob)) %>%
# group by classification, id, speed and trial and calculate step sizes:
.[, by = .(classification, id, tITI, trial_tITI),
:= position - shift(position)] step
We calculate the mean step size for early and late period in the forward and backward phase:
dt_pred_seq_step %>%
dt_pred_seq_step_mean = filter(period != "excluded") %>%
filter(!(is.na(zone))) %>% setDT(.) %>%
# shorten the period name:
mutate(period_short = ifelse(period == "forward", "fwd", period)) %>%
transform(period_short = ifelse(period == "backward", "bwd", period_short)) %>%
setDT(.) %>%
.[, by = .(classification, id, tITI, period_short, zone), .(
mean_step = mean(step, na.rm = TRUE))]
We compare the forward and the backward period using t-tests:
dt_pred_seq_step_mean %>%
dt_pred_seq_step_stat = spread(key = period_short, value = mean_step, drop = TRUE) %>%
mutate(difference = fwd - bwd) %>% setDT(.) %>%
# average across participants for each speed condition and volume:
.[, by = .(classification, tITI, zone), {
t.test(fwd, bwd, alternative = "two.sided", paired = TRUE)
ttest_results =list(
num_subs = .N,
tvalue = round(ttest_results$statistic, 2),
pvalue = ttest_results$p.value,
df = ttest_results$parameter,
cohens_d = abs(round((mean(fwd) - mean(bwd)) / sd(fwd - bwd), 2)),
mean_step = mean(difference),
sd_step = sd(difference),
sem_upper = mean(difference) + (sd(difference)/sqrt(.N)),
sem_lower = mean(difference) - (sd(difference)/sqrt(.N))
)%>% verify(all(num_subs == 36)) %>%
}] # adjust p-values for multiple comparisons:
.[, by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] verify(all(num_comp == 10)) %>%
# add variable that indicates significance with stupid significance stars:
mutate(significance = ifelse(pvalue < 0.05, "*", "")) %>%
# round the original p-values:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the p-values adjusted for multiple comparisons:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort the datatable for each speed and TR:
setorder(., classification, zone, tITI)
%>%
dt_pred_seq_step_stat filter(classification == "ovr") %>%
rmarkdown::paged_table(.)
We compare each period to the baseline:
dt_pred_seq_step_mean %>%
dt_pred_seq_step_stat_baseline = # average across participants for each speed condition and volume:
.[, by = .(classification, tITI, period_short, zone), {
t.test(mean_step, mu = 0, alternative = "two.sided")
ttest_results =list(
num_subs = .N,
tvalue = round(ttest_results$statistic, 2),
pvalue = ttest_results$p.value,
df = ttest_results$parameter,
cohens_d = abs(round((mean(mean_step) - 0) / sd(mean_step), 2)),
mean_step = mean(mean_step),
sd_step = sd(mean_step),
sem_upper = mean(mean_step) + (sd(mean_step)/sqrt(.N)),
sem_lower = mean(mean_step) - (sd(mean_step)/sqrt(.N))
)%>% verify(all(num_subs == 36)) %>%
}] # adjust p-values for multiple comparisons:
.[, by = .(classification), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] # verf
verify(all(num_comp == 20)) %>%
# add variable that indicates significance with stupid significance stars:
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", "")) %>%
# round the original p-values:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the p-values adjusted for multiple comparisons:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# sort the datatable for each speed and TR:
setorder(., classification, period_short, zone, tITI)
%>%
dt_pred_seq_step_stat_baseline filter(classification == "ovr", pvalue < 0.05) %>%
rmarkdown::paged_table(.)
2.7.8.1 Figure 3h
# plot average correlation or betas for each speed condition and time period:
ggplot(data = subset(dt_pred_seq_step_mean, classification == "ovr"), aes(
fig_seq_step =x = fct_rev(as.factor(period_short)), y = as.numeric(mean_step),
fill = as.factor(as.numeric(tITI) * 1000)), color = as.factor(as.numeric(tITI) * 1000)) +
facet_grid(vars(as.factor(zone)), vars(as.factor(as.numeric(tITI) * 1000)), switch = "x") +
geom_bar(stat = "summary", fun = "mean", width = 0.9) +
geom_point(position = position_jitterdodge(jitter.height = 0, seed = 4, jitter.width = 0.2),
pch = 21, alpha = 0.05, color = "black", show.legend = FALSE) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0, color = "black") +
geom_text(data = subset(dt_pred_seq_step_stat, classification == "ovr"), aes(
y = 2, label = paste0("d=", sprintf("%.2f", cohens_d), significance), x = 1.5),
inherit.aes = FALSE, color = "black", size = 3.3) +
xlab("Period") + ylab("Step size") +
scale_colour_viridis(discrete = TRUE, option = "cividis", name = "Speed (ms)") +
scale_fill_viridis(discrete = TRUE, option = "cividis", name = "Speed (ms)") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(-2, 2)) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = -5, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
theme(panel.spacing.x = unit(0, "lines"), strip.background.x = element_blank(),
strip.placement.x = "outside", strip.text.x = element_blank()) +
theme(axis.ticks.x = element_line(colour = "white"),
axis.line.x = element_line(colour = "white")) +
#theme(axis.title.x = element_blank()) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_seq_step
2.7.8.2 Source Data File Fig. 3h
subset(dt_pred_seq_step_mean, classification == "ovr") %>%
select(-classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_3h.csv"),
row.names = FALSE)
plot_grid(fig_seq_pos_period, fig_seq_step, labels = "auto",
ncol = 2, label_fontface = "bold", rel_widths = c(5, 6))
plot_grid(fig_seq_cor_time, fig_seq_cor_period, fig_seq_step_time, fig_seq_step_period,
labels = "auto", ncol = 2, nrow = 2, label_fontface = "bold")
2.7.8.3 Source Data File Fig. 3f
seq_test_time(data = dt_pred_seq_cor, variable = "mean_slope") %>%
filter(pvalue_adjust < 0.05 & classification == "ovr") %>%
rmarkdown::paged_table(.)
seq_test_time(data = dt_pred_seq_cor, variable = "mean_cor") %>%
filter(pvalue_adjust < 0.05 & classification == "ovr") %>%
rmarkdown::paged_table(.)
seq_test_time(data = dt_pred_seq_cor, variable = "mean_step") %>%
filter(pvalue_adjust < 0.05 & classification == "ovr") %>%
rmarkdown::paged_table(.)
2.7.8.4 Figure S6
plot_seq_cor_facet(dt = subset(
fig_seq_slope_time_facet =seq_test_time(data = dt_pred_seq_cor, variable = "mean_slope"),
== "ovr"), variable = "mean_slope")
classification
plot_seq_cor_facet(dt = subset(
fig_seq_cor_time_facet =seq_test_time(data = dt_pred_seq_cor, variable = "mean_cor"),
== "ovr"), variable = "mean_cor")
classification
plot_seq_cor_facet(dt = subset(
fig_seq_step_time_facet =seq_test_time(data = dt_pred_seq_cor, variable = "mean_step"),
== "ovr"), variable = "mean_step")
classification
theme(axis.title.x = element_blank())
remove_xaxis = theme(strip.background = element_blank(), strip.text.x = element_blank())
remove_facets =
plot_grid(fig_seq_slope_time_facet + remove_xaxis,
+ remove_xaxis + theme(legend.position = "none") + remove_facets,
fig_seq_pos_time_facet + remove_xaxis + theme(legend.position = "none") + remove_facets,
fig_seq_cor_time_facet + theme(legend.position = "none") + remove_facets,
fig_seq_step_time_facet labels = "auto", ncol = 1, label_fontface = "bold")
ggsave(filename = "wittkuhn_schuck_figure_s6.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 7, height = 9)
2.7.8.5 Source Data File Fig. S6a
subset(seq_test_time(data = dt_pred_seq_cor, variable = "mean_slope"),
== "ovr") %>%
classification select(-classification, -num_subs, -num_comp) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s6a.csv"),
row.names = FALSE)
2.7.8.6 Source Data File Fig. S6b
subset(dt_pred_seq_pos_tr, classification == "ovr") %>%
select(-classification, -num_subs, -num_comp) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s6b.csv"),
row.names = FALSE)
2.7.8.7 Source Data File Fig. S6c
subset(seq_test_time(data = dt_pred_seq_cor, variable = "mean_cor"),
== "ovr") %>%
classification select(-classification, -num_subs, -num_comp) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s6c.csv"),
row.names = FALSE)
2.7.8.8 Source Data File Fig. S6d
subset(seq_test_time(data = dt_pred_seq_cor, variable = "mean_step"),
== "ovr") %>%
classification select(-classification, -num_subs, -num_comp) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s6d.csv"),
row.names = FALSE)
2.7.9 Figure 3
Plot Figure 3 in the main text:
plot_grid(
plot_grid(fig_seq_probas, labels = c("a"), nrow = 1),
plot_grid(fig_seq_slope_time, fig_seq_slope_period, labels = c("b", "c"),
ncol = 2, nrow = 1, label_fontface = "bold", rel_widths = c(4.9, 5)),
plot_grid(fig_seq_cor_between, fig_seq_cor_within, fig_seq_pos_time,
labels = c("d", "e", "f"), ncol = 3, rel_widths = c(0.325, 0.325, 0.35)),
plot_grid(fig_seq_pos_period, fig_seq_step, labels = c("g", "h"),
ncol = 2, label_fontface = "bold", nrow = 1),
nrow = 4, label_fontface = "bold", rel_heights = c(2, 3)
)
## `geom_smooth()` using formula 'y ~ x'
ggsave(filename = "wittkuhn_schuck_figure_3.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 12)
2.7.9.1 Figure S7
2.8 Decoding: Repetition trials
2.8.1 Preparation
2.8.1.1 Initialization
We load the data and relevant functions:
# find the path to the root of this project:
if (!requireNamespace("here")) install.packages("here")
if ( basename(here::here()) == "highspeed" ) {
here::here("highspeed-analysis")
path_root =else {
} here::here()
path_root =
}# create a list of participants to exclude based on behavioral performance:
c("sub-24", "sub-31", "sub-37", "sub-40") sub_exclude <-
The next step is only evaluated during manual execution:
# source all relevant functions from the setup R script:
source(file.path(path_root, "code", "highspeed-analysis-setup.R"))
2.8.1.2 Prepare events and decoding data
We prepare the behavioral events data and the decoding data of repetition trials:
# create a subset of the events data table only including repetition task events:
dt_events %>%
dt_events_rep = filter(condition == "repetition" & trial_type == "stimulus") %>%
setDT(.)
# create a data table containing all classifier predictions on repetition trials:
dt_pred %>%
dt_pred_rep = # create a subset of the decoding data only including the repetition task data:
filter(condition == "repetition" & class != "other" & mask == "cv" & stim != "cue") %>%
setDT(.) %>%
# filter excluded participants:
filter(!(id %in% sub_exclude)) %>%
setDT(.) %>%
# add the serial position, change trial and target cue to the repetition data table:
.[, c("position", "change", "trial_cue") := get_pos(.SD, dt_events_rep),
.(id, trial, class), .SDcols = c("id", "trial", "class")] %>%
by = # add a counter for the number of change trials in the repetition data:
.[, by = .(id, classifier, class, change), ":=" (
trial_change = rep(1:length(unique(trial)), each = max(seq_tr))
%>%
)] # add random position indices for out-of-class stimuli:
.[is.na(position), by = .(id, classification, trial), ":=" (
position = rep(permute(3:5), each = max(seq_tr))
%>%
)] # check if there are any NA values:
verify(!is.na(position)) %>%
# create a new variable containing the position label by copying the position:
mutate(position_label = position) %>%
# re-label all positions > 2 to "other":
transform(position_label = ifelse(position_label > 2, "none", position_label)) %>%
setDT(.) %>%
# create a new variable counting the occurrence of each event position:
.[position != 2, ":=" (occurence = change - 1)] %>%
.[position == 2 & change <= 9, ":=" (occurence = max(change) + 1 - change)] %>%
.[position == 2 & change == 16, ":=" (occurence = max(change) + 1 - change)]
2.8.2 Probability time-courses
First, we average across trials for each factor grouping and calculate the mean classification probability for each serial event. Note, that this approach averages across the specific stimuli used in any given trial as the specific stimulus identities are not considered important here. We verify if the correct number of trials was used for the calculations. Next, we average across participants. We calculate the average probability of each serial event and also calculate the standard error of them mean.
dt_pred_rep %>%
dt_pred_rep_timecourses = # calculate the mean probability for every repetition condition and TR
.[, by = .(classification, id, seq_tr, change, position), .(
num_events = .N,
mean_probability = mean(probability * 100)
%>% verify(num_events == 5) %>%
)] # average mean probability across participants:
.[, by = .(classification, seq_tr, change, position), .(
num_sub = .N,
mean_probability = mean(mean_probability),
sem_upper = mean(mean_probability) + (sd(mean_probability)/sqrt(.N)),
sem_lower = mean(mean_probability) - (sd(mean_probability)/sqrt(.N))
%>%
)] verify(all(num_sub == 36))
We define the colors used for plotting as well as the facet labels:
# define colors for plotting:
colorRampPalette(c("dodgerblue", "red"), space = "Lab")(2)
colors_inseq = colorRampPalette(c("gray70", "gray90"), alpha = TRUE)(3)
colors_outseq = c(colors_inseq, colors_outseq)
colors =# change the facet labels such that it shows the time-point of the change from
# the first to the second unique stimulus:
c(sort(unique(paste0(dt_pred_rep_timecourses$change[
facet_labels_new =$change != 16]-1, "/", 10-dt_pred_rep_timecourses$change[
dt_pred_rep_timecourses$change != 16]))), unique(paste0(dt_pred_rep_timecourses$change[
dt_pred_rep_timecourses$change == 16]-1, "/1")))
dt_pred_rep_timecourses1] = paste("short", sprintf('\u2192'), "long")
facet_labels_new[1] = "Forward interference"
facet_labels_new[length(facet_labels_new)-1] = "Backward interference"
facet_labels_new[ as.character(sort(unique(dt_pred_rep_timecourses$change)))
facet_labels_old =names(facet_labels_new) = facet_labels_old
2.8.2.1 Figure 4a
We plot the probability time courses:
c(2, 7)
trs = function(data, xmin = 1, xmax = 13) {
plot_rep_probas <-# reduced the data frame to plot rectangles (see below):
subset(data, position == 1 & seq_tr == 1)
data_factor_reduced =ggplot(data, aes(x = as.numeric(seq_tr), y = as.numeric(mean_probability),
color = as.factor(position), fill = as.factor(position))) +
facet_wrap(~ as.factor(change), ncol = 3, labeller = as_labeller(facet_labels_new)) +
geom_rect(data = data_factor_reduced, fill = "gray", alpha = 0.15, color = NA,
aes(xmin = xmin, xmax = xmax, ymin = 0, ymax = 60)) +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.5, color = NA) +
geom_line() +
xlab("Time from sequence onset (TRs)") + ylab("Probability (%)") +
annotate("text", x = 13, y = 0, label = "1 TR = 1.25 s",
hjust = 1, size = rel(2)) +
scale_colour_manual(name = "Serial event", values = colors) +
scale_fill_manual(name = "Serial event", values = colors) +
scale_x_continuous(labels = label_fill(seq(1,13,1), mod = 4), breaks = seq(1,13,1)) +
coord_capped_cart(left = "both", bottom = "both", ylim = c(0,60)) +
theme(strip.text.x = element_text(margin = margin(b = 3, t = 2))) +
guides(color = guide_legend(nrow = 1)) +
theme(legend.position = "bottom", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0,0,0,0),
legend.box.margin = margin(t = -5, r = 0, b = 10, l = 0)) +
theme(panel.background = element_blank()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
} dt_pred_rep_timecourses %>% filter(classification == "ovr" & change %in% c(2,9))
plot_data = plot_rep_probas(data = plot_data, xmin = trs[1] - 0.5, xmax = trs[2] + 0.5)
fig_a = plot_rep_probas(data = subset(dt_pred_rep_timecourses, classification == "ovr"),
fig_s1_a =xmin = trs[1] - 0.5, xmax = trs[2] + 0.5)
fig_a; fig_s1_a;
2.8.2.2 Source Data File Fig. 4a
subset(dt_pred_rep_timecourses, classification == "ovr") %>%
select(-classification, -num_sub) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_4a.csv"),
row.names = FALSE)
2.8.3 Mean probabilities
2.8.3.1 Figure 4b
We calculate the mean decoding probabilities for each event:
# define the subset of selected TRs:
seq(2, 7)
trs =# calculate the mean probability for the event types across conditions:
dt_pred_rep %>%
dt_pred_rep_prob = # only consider the two extreme conditions (2 and 9):
filter(change %in% c(2, 9)) %>% verify(length(unique(change)) == 2) %>% setDT(.) %>%
# verify that the number of repetition trials per condition is correct:
verify(all(.[, by = .(classification, id, change), .(
num_trials = length(unique(trial))
$num_trials == 5)) %>%
)] # filter specific TRs:
filter(seq_tr %in% trs) %>%
transform(probability_z = scale(probability)) %>%
filter(classification == "ovr") %>% setDT(.)
# calculate the mean probability for each position label:
dt_pred_rep_prob %>%
dt_pred_rep_mean_prob = # calculate the mean probability for each position label on each trial:
.[, by = .(id, classification, trial, position_label), .(
num_events = .N,
probability = mean(probability * 100)
%>%
)] # verify that the number of events for each position label is correct:
verify(all(num_events %in% c(length(trs), length(trs) * 3))) %>%
# set the position label as factor and z-score the probability values:
mutate(position_label = as.factor(position_label)) %>%
transform(probability_z = scale(probability)) %>%
setorder(id, classification, trial, position_label)
# average the data for each position label across trials:
dt_pred_rep_mean_prob %>% setDT(.) %>%
dt_pred_rep_mean_prob_plot = .[, by = .(id, classification, position_label), .(
num_trials = .N,
probability = mean(probability)
%>% verify(all(num_trials == 10)) )]
# define linear mixed effects model with by-participant random intercepts:
lmer(probability ~ position_label + (1 + position_label|id),
lme_rep_mean_prob =data = subset(dt_pred_rep_mean_prob, classification == "ovr"), control = lcctrl)
summary(lme_rep_mean_prob)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: probability ~ position_label + (1 + position_label | id)
## Data: subset(dt_pred_rep_mean_prob, classification == "ovr")
## Control: lcctrl
##
## REML criterion at convergence: 9002.9
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.6657 -0.6370 -0.1176 0.4832 4.0397
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 6.930 2.632
## position_label2 20.593 4.538 -0.94
## position_labelnone 5.728 2.393 -1.00 0.91
## Residual 242.541 15.574
## Number of obs: 1080, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 20.1857 0.9307 35.0264 21.688 < 2e-16 ***
## position_label2 4.5897 1.3855 35.2853 3.313 0.00214 **
## position_labelnone -12.4656 1.2274 85.1270 -10.156 2.45e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) pstn_2
## positn_lbl2 -0.764
## postn_lblnn -0.743 0.558
anova(lme_rep_mean_prob)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## position_label 53424 26712 2 57.776 110.13 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans(lme_rep_mean_prob, list(pairwise ~ position_label))
emmeans_results =print(emmeans_results)
## $`emmeans of position_label`
## position_label emmean SE df lower.CL upper.CL
## 1 20.19 0.931 35 18.30 22.08
## 2 24.78 0.903 35 22.94 26.61
## none 7.72 0.822 35 6.05 9.39
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of position_label`
## contrast estimate SE df t.ratio p.value
## 1 - 2 -4.59 1.39 35 -3.313 0.0060
## 1 - none 12.47 1.23 35 10.156 <.0001
## 2 - none 17.06 1.24 35 13.797 <.0001
##
## Degrees-of-freedom method: kenward-roger
## P value adjustment: tukey method for comparing a family of 3 estimates
round_pvalues(summary(emmeans_results[[2]])$p.value) emmeans_pvalues =
data.table(
dt_significance =contrast = emmeans_results$contrast,
position_label = rep(c("1", "2", "none"), each = 2),
group = c(1.5, 2.0, 2.5),
probability = c(55, 75, 65),
pvalue = emmeans_pvalues
)
ggplot(data = subset(dt_pred_rep_mean_prob_plot, classification == "ovr"), aes(
fig_b =x = as.factor(position_label), y = as.numeric(probability),
fill = as.factor(position_label))) +
geom_flat_violin(scale = "width", trim = FALSE, aes(color = NA),
position = position_nudge(x = 0.2, y = 0), alpha = 0.4) +
geom_point(stat = "summary", fun = "mean", color = "black", pch = 23, size = 3,
position = position_nudge(x = 0.125, y = 0)) +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0.0,
position = position_nudge(x = 0.125, y = 0), color = "black") +
geom_boxplot(outlier.colour = "black", outlier.shape = NA, alpha = 0.5,
notch = FALSE, outlier.alpha = 1, width = 0.05, color = "black",
position = position_nudge(x = 0.25, y = 0)) +
geom_point(position = position_jitter(width = .05, height = 0, seed = 4),
pch = 21, alpha = 1, color = "black") +
xlab("Serial event") + ylab("Probability (%)") +
scale_fill_manual(name = "Serial event", values = colors) +
scale_color_manual(name = "Serial event", values = colors) +
#scale_y_continuous(labels = label_fill(seq(0, 100, 10), mod = 2), breaks = seq(0, 100, 10)) +
coord_capped_cart(left = "both", bottom = "both", ylim = c(0, 100)) +
theme(legend.position = "bottom", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0,0,0,0),
legend.box.margin = margin(t = -10, r = 0, b = 10, l = 0)) +
theme(panel.background = element_blank()) +
#theme(plot.margin = unit(c(t = 1, r = 1, b = 1, l = 1), "pt")) +
geom_line(data = dt_significance, aes(group = group),
linetype = "solid", position = position_nudge(x = 0.1, y = 0)) +
geom_text(data = dt_significance %>% distinct(group, probability, .keep_all = TRUE),
aes(x = group, y = probability + 1.5, label = pvalue), color = "black",
position = position_nudge(x = 0.125, y = 4), parse = FALSE) +
theme(axis.line.y = element_line(colour = "black"),
axis.line.x = element_line(colour = "white"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(colour = "white"))
fig_b
2.8.3.2 Source Data File Fig. 4b
subset(dt_pred_rep_mean_prob_plot, classification == "ovr") %>%
select(-classification, -num_trials) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_4b.csv"),
row.names = FALSE)
2.8.3.3 Figure 4c
seq(2, 7)
trs =# calculate means for each participant:
dt_pred_rep %>%
dt_pred_rep_a2_sub = # only consider the two extreme conditions (2 and 9):
#filter(change %in% c(2,9)) %>%
filter(seq_tr %in% trs) %>%
# set the change variable to a factor:
transform(change = as.factor(change)) %>%
transform(position_label = as.factor(position_label)) %>% setDT(.) %>%
# check if the number of change conditions really only equals 2:
#verify(length(unique(change)) == 2) %>%
.[, by = .(id, classification, change, position_label), .(
probability = mean(probability * 100)
%>%
)] # standardize the probabilities:
#mutate(probability_z = scale(probability)) %>% setDT(.) %>%
setorder(id, classification, change, position_label) %>%
filter(classification == "ovr") %>% setDT(.)
dt_pred_rep_a2_sub %>% filter(classification == "ovr" & change %in% c(2,9))
lme_data = lmer(probability ~ position_label * change + (1 + position_label + change|id),
lme_rep_prob_rep =data = lme_data, na.action = na.omit, control = lcctrl)
summary(lme_rep_prob_rep)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: probability ~ position_label * change + (1 + position_label +
## change | id)
## Data: lme_data
## Control: lcctrl
##
## REML criterion at convergence: 1364.3
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.08370 -0.61552 -0.02366 0.55922 2.57687
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 16.3158 4.0393
## position_label2 42.2923 6.5033 -0.86
## position_labelnone 16.3072 4.0382 -0.98 0.74
## change9 0.9134 0.9557 0.34 0.18 -0.54
## Residual 26.6103 5.1585
## Number of obs: 216, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 13.499 1.092 54.859 12.362 < 2e-16 ***
## position_label2 18.052 1.629 64.902 11.083 < 2e-16 ***
## position_labelnone -5.748 1.390 93.815 -4.136 7.70e-05 ***
## change9 13.373 1.226 137.028 10.905 < 2e-16 ***
## position_label2:change9 -26.925 1.720 140.000 -15.658 < 2e-16 ***
## position_labelnone:change9 -13.436 1.720 140.000 -7.814 1.19e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) pstn_2 pstn_l chang9 ps_2:9
## positn_lbl2 -0.770
## postn_lblnn -0.779 0.564
## change9 -0.524 0.385 0.400
## pstn_lbl2:9 0.394 -0.528 -0.309 -0.701
## pstn_lbln:9 0.394 -0.264 -0.619 -0.701 0.500
anova(lme_rep_prob_rep)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## position_label 7778.4 3889.2 2 41.642 146.1547 <2e-16 ***
## change 0.3 0.3 1 90.029 0.0126 0.9109
## position_label:change 6524.4 3262.2 2 140.000 122.5913 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans(lme_rep_prob_rep, list(pairwise ~ position_label | change))
emmeans_results =print(emmeans_results)
## $`emmeans of position_label | change`
## change = 2:
## position_label emmean SE df lower.CL upper.CL
## 1 13.50 1.092 53.8 11.31 15.69
## 2 31.55 1.052 55.6 29.44 33.66
## none 7.75 0.872 68.8 6.01 9.49
##
## change = 9:
## position_label emmean SE df lower.CL upper.CL
## 1 26.87 1.137 52.0 24.59 29.15
## 2 18.00 1.126 52.4 15.74 20.26
## none 7.69 0.863 69.7 5.97 9.41
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of position_label | change`
## change = 2:
## contrast estimate SE df t.ratio p.value
## 1 - 2 -18.05 1.63 62.6 -11.083 <.0001
## 1 - none 5.75 1.39 77.0 4.136 0.0003
## 2 - none 23.80 1.43 74.3 16.701 <.0001
##
## change = 9:
## contrast estimate SE df t.ratio p.value
## 1 - 2 8.87 1.63 62.6 5.447 <.0001
## 1 - none 19.18 1.39 77.0 13.804 <.0001
## 2 - none 10.31 1.43 74.3 7.236 <.0001
##
## Degrees-of-freedom method: kenward-roger
## P value adjustment: tukey method for comparing a family of 3 estimates
summary(emmeans_results[[2]]) emmeans_summary =
data.table(
dt_significance =change = rep(emmeans_summary$change, each = 2),
contrast = rep(emmeans_summary$contrast, each = 2),
pvalue = rep(round_pvalues(emmeans_summary$p.value), each = 2),
group = rep(c(1.5, 2.0, 2.5), each = 2),
position_label = c("1", "2", "1", "none", "2", "none"),
probability = rep(c(60, 80, 70), each = 2)
)
dt_pred_rep_a2_sub %>% filter(classification == "ovr" & change %in% c(2,9))
dt_plot = ggplot(data = dt_plot, aes(
fig_c =x = as.factor(position_label), y = as.numeric(probability),
fill = as.factor(position_label))) +
geom_flat_violin(
scale = "width", trim = FALSE, aes(color = NA),
position = position_nudge(x = 0.25, y = 0), alpha = 0.4) +
geom_point(
stat = "summary", fun = "mean", color = "black", pch = 23, size = 3,
position = position_nudge(x = 0.15, y = 0)) +
geom_errorbar(
stat = "summary", fun.data = "mean_se", width = 0.0,
position = position_nudge(x = 0.15, y = 0), color = "black") +
geom_boxplot(
outlier.colour = "black", outlier.shape = NA, outlier.size = 50,
notch = FALSE, outlier.alpha = 1, width = 0.05, color = "black",
position = position_nudge(x = 0.35, y = 0), alpha = 0.5) +
geom_point(
position = position_jitter(width = .05, height = 0, seed = 4),
pch = 21, alpha = 1) +
facet_wrap(~ as.factor(change), labeller = as_labeller(facet_labels_new)) +
xlab("Serial event") + ylab("Probability (%)") +
scale_fill_manual(name = "Serial event", values = colors) +
scale_color_manual(name = "Serial event", values = colors) +
#scale_y_continuous(labels = label_fill(seq(0, 100, 10), mod = 2), breaks = seq(0, 100, 10)) +
coord_capped_cart(left = "both", bottom = "both", ylim = c(0, 100), expand = TRUE) +
theme(legend.position = "bottom", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0,0,0,0),
legend.box.margin = margin(t = -10, r = 0, b = 10, l = 0)) +
theme(panel.background = element_blank()) +
theme(strip.text.x = element_text(margin = margin(b = 3, t = 2))) +
theme(axis.title.x = element_blank(), axis.text.x = element_blank(),
axis.ticks.x = element_blank(), axis.line.x = element_blank()) +
#theme(plot.margin = unit(c(t = 1, r = 1, b = 1, l = 1), "pt")) +
geom_line(data = dt_significance, aes(group = group, x = position_label, y = probability),
linetype = "solid", position = position_nudge(x = 0.1, y = 0)) +
geom_text(data = dt_significance %>% distinct(group, change, probability, .keep_all = TRUE),
aes(x = group, y = probability + 1, label = pvalue), color = "black",
position = position_nudge(x = 0.1, y = 4), parse = FALSE) +
theme(axis.line.y = element_line(colour = "black"),
axis.line.x = element_line(colour = "white"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(colour = "white"))
fig_c
2.8.3.4 Source Data File Fig. 4c
subset(dt_pred_rep_a2_sub, classification == "ovr") %>%
select(-classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_4c.csv"),
row.names = FALSE)
2.8.3.5 Figure S8
# select specific trs:
seq(2, 7)
trs = dt_pred_rep %>%
dt_pred_rep_all_reps_sub = # filter out the extremely long condition (with 16 items):
filter(occurence != 15) %>% setDT(.) %>%
# filter for specific trs:
filter(seq_tr %in% trs) %>% setDT(.) %>%
# transform the main variables of interest:
transform(
occurence = as.numeric(occurence),
position_label = as.factor(position_label),
probability = probability * 100
%>% setDT(.) %>%
) # calculate the mean probability for each serial event item (first, second, other)
# separately depending on the number of indiviual repetitions
.[, by = .(id, classification, occurence, position_label), .(
probability = mean(probability)
%>%
)] setorder(., classification, id, occurence, position_label)
dt_pred_rep_all_reps_sub %>%
dt_pred_rep_all_reps_mean = # average across participants and calculate the standard error of the mean:
. [, by = .(classification, occurence, position_label), {
list(
mean_probability = mean(probability),
num_subs = .N,
sem_upper = mean(probability) + (sd(probability)/sqrt(.N)),
sem_lower = mean(probability) - (sd(probability)/sqrt(.N))
%>%
)}] verify(all(num_subs == 36)) %>%
setorder(., classification, occurence, position_label)
Are the short repetitions of event 1 and event 2 different from noise:
LME model including all three event types:
# model including all three event types:
lmer(probability ~ position_label * occurence + (1 + position_label + occurence|id),
lme_pred_rep_all_reps =data = subset(dt_pred_rep_all_reps_sub, classification == "ovr" & occurence != 15),
na.action = na.omit, control = lcctrl)
summary(lme_pred_rep_all_reps)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: probability ~ position_label * occurence + (1 + position_label +
## occurence | id)
## Data:
## subset(dt_pred_rep_all_reps_sub, classification == "ovr" & occurence !=
## 15)
## Control: lcctrl
##
## REML criterion at convergence: 5592
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.2610 -0.5744 -0.0611 0.5061 3.6198
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 3.07985 1.7550
## position_label2 7.56801 2.7510 -0.28
## position_labelnone 4.41464 2.1011 -0.62 -0.04
## occurence 0.06141 0.2478 0.05 0.32 -0.81
## Residual 34.75199 5.8951
## Number of obs: 864, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 11.58826 0.81954 160.93054 14.140 < 2e-16
## position_label2 4.65762 1.17576 274.88239 3.961 9.5e-05
## position_labelnone -3.87294 1.13790 389.16818 -3.404 0.000734
## occurence 1.90219 0.15713 313.08078 12.106 < 2e-16
## position_label2:occurence 0.08274 0.21440 752.99954 0.386 0.699656
## position_labelnone:occurence -1.86040 0.21440 752.99953 -8.677 < 2e-16
##
## (Intercept) ***
## position_label2 ***
## position_labelnone ***
## occurence ***
## position_label2:occurence
## position_labelnone:occurence ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) pstn_2 pstn_l occrnc pst_2:
## positn_lbl2 -0.647
## postn_lblnn -0.697 0.433
## occurence -0.798 0.592 0.513
## pstn_lbl2:c 0.589 -0.821 -0.424 -0.682
## pstn_lblnn: 0.589 -0.410 -0.848 -0.682 0.500
anova(lme_pred_rep_all_reps)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## position_label 1667.4 833.7 2 278.97 23.989 2.435e-10 ***
## occurence 6363.2 6363.2 1 58.73 183.102 < 2.2e-16 ***
## position_label:occurence 3650.8 1825.4 2 753.00 52.527 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
LME model excluding out-of-sequence events:
lmer(probability ~ position_label * occurence + (1 + position_label + occurence|id),
lme_pred_rep_all_reps_reduced =data = subset(dt_pred_rep_all_reps_sub, classification == "ovr" & occurence != 15 & position_label != "none"),
na.action = na.omit, control = lcctrl)
## Warning: Model failed to converge with 1 negative eigenvalue: -6.7e+00
summary(lme_pred_rep_all_reps_reduced)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: probability ~ position_label * occurence + (1 + position_label +
## occurence | id)
## Data:
## subset(dt_pred_rep_all_reps_sub, classification == "ovr" & occurence !=
## 15 & position_label != "none")
## Control: lcctrl
##
## REML criterion at convergence: 3927.2
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.83205 -0.66198 -0.07749 0.63366 3.02136
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 0.000 0.0000
## position_label2 4.212 2.0523 NaN
## occurence 0.133 0.3647 NaN 0.38
## Residual 49.631 7.0450
## Number of obs: 576, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 11.58826 0.91490 501.89714 12.666 < 2e-16 ***
## position_label2 4.65762 1.33831 350.19281 3.480 0.000564 ***
## occurence 1.90219 0.19110 298.33537 9.954 < 2e-16 ***
## position_label2:occurence 0.08274 0.25622 501.89714 0.323 0.746875
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) pstn_2 occrnc
## positn_lbl2 -0.684
## occurence -0.845 0.609
## pstn_lbl2:c 0.630 -0.862 -0.670
anova(lme_pred_rep_all_reps_reduced)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## position_label 601.1 601.1 1 350.19 12.1119 0.0005643 ***
## occurence 9323.8 9323.8 1 125.87 187.8611 < 2.2e-16 ***
## position_label:occurence 5.2 5.2 1 501.90 0.1043 0.7468748
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
dt_pred_rep_all_reps_sub %>%
dt_pred_rep_all_reps_stats = filter(occurence %in% c(8)) %>% setDT(.) %>%
transform(position_label = paste0("pos_", position_label)) %>%
spread(key = position_label, value = probability, drop = FALSE) %>%
filter(classification == "ovr")
# some ugly code to perform three t-tests:
summary(dt_pred_rep_all_reps_stats$pos_2); round(sd(dt_pred_rep_all_reps_stats$pos_2), 2)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 18.27 25.46 31.63 31.55 36.37 47.05
## [1] 6.94
summary(dt_pred_rep_all_reps_stats$pos_1); round(sd(dt_pred_rep_all_reps_stats$pos_1), 2)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 9.432 21.742 25.970 26.872 31.418 46.455
## [1] 8.34
summary(dt_pred_rep_all_reps_stats$pos_none); round(sd(dt_pred_rep_all_reps_stats$pos_none), 2)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 3.195 5.395 7.396 7.689 9.562 14.290
## [1] 2.69
# perform separate t-tests
t.test(dt_pred_rep_all_reps_stats$pos_2, dt_pred_rep_all_reps_stats$pos_1,
test1 =paired = TRUE, alternative = "two.sided")
t.test(dt_pred_rep_all_reps_stats$pos_2, dt_pred_rep_all_reps_stats$pos_none,
test2 =paired = TRUE, alternative = "two.sided")
t.test(dt_pred_rep_all_reps_stats$pos_1, dt_pred_rep_all_reps_stats$pos_none,
test3 =paired = TRUE, alternative = "two.sided")
test1; test2; test3
##
## Paired t-test
##
## data: dt_pred_rep_all_reps_stats$pos_2 and dt_pred_rep_all_reps_stats$pos_1
## t = 2.5877, df = 35, p-value = 0.01397
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 1.008362 8.350423
## sample estimates:
## mean of the differences
## 4.679393
##
## Paired t-test
##
## data: dt_pred_rep_all_reps_stats$pos_2 and dt_pred_rep_all_reps_stats$pos_none
## t = 18.964, df = 35, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 21.30830 26.41741
## sample estimates:
## mean of the differences
## 23.86285
##
## Paired t-test
##
## data: dt_pred_rep_all_reps_stats$pos_1 and dt_pred_rep_all_reps_stats$pos_none
## t = 12.518, df = 35, p-value = 1.75e-14
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 16.07247 22.29445
## sample estimates:
## mean of the differences
## 19.18346
# correct for multiple comparisons
p.adjust(c(test1$p.value, test2$p.value, test3$p.value), method = "bonferroni", n = 6)
## [1] 8.383045e-02 3.250184e-19 1.049727e-13
ggplot(data = subset(dt_pred_rep_all_reps_mean, classification == "ovr"), aes(
fig_d =x = as.factor(occurence), y = as.numeric(mean_probability),
group = as.factor(position_label), color = as.factor(position_label))) +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper,
fill = as.factor(position_label)), alpha = 0.5) +
geom_line(size = 0.7, mapping = aes(color = as.factor(position_label))) +
xlab("Number of item repetitions") +
ylab("Probability (in %)") +
scale_fill_manual(name = "Serial event", values = colors) +
scale_color_manual(name = "Serial event", values = colors) +
scale_y_continuous(labels = label_fill(seq(0, 100, 10), mod = 2), breaks = seq(0, 100, 10)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0,40)) +
theme(legend.position = "right", legend.direction = "vertical") +
guides(color = guide_legend(ncol = 1)) +
theme(legend.position = "right", legend.direction = "vertical",
legend.justification = "center", legend.margin = margin(0,0,0,0),
legend.box.margin = margin(t = 0, r = , b = 0, l = -20)) +
theme(panel.background = element_blank()) +
theme(plot.margin = unit(c(t = 1, r = 1, b = 1, l = 1), "pt")) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_d
2.8.3.6 Source Data File Fig. S8
subset(dt_pred_rep_all_reps_mean, classification == "ovr") %>%
select(-classification, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_s8.csv"),
row.names = FALSE)
2.8.4 Step-sizes
Are there more within sequence items in the classifier predictions? To this end, we check if serial events 1 and 2 (of 2) are decoded more often than other (out-of-sequence) serial events in the repetition trials.
# define the number of TRs per trial (used below):
seq(2, 7)
select_trs = dt_pred_rep %>%
dt_pred_rep_count = # get the position with the highest probability:
.[, by = .(classification, id, trial, change, seq_tr), ":=" (
num_events = .N,
position_max_prob = as.numeric(probability == max(probability))
%>%
)] # check if the data consisted of 5 trials per TR:
verify(num_events == 5) %>%
# check if there is only one event with the maximum probability:
verify(.[, by = .(classification, id, trial, change, seq_tr), .(
num_max_events = sum(position_max_prob)
$num_max_events == 1) %>%
)] # get the position with the highest probability:
filter(position_max_prob == 1) %>%
setDT(.) %>%
# WARNING: HERE WE FILTER OUT SPECIFIC TRIALS SET ABOVE:
filter(seq_tr %in% select_trs) %>%
setDT(.) %>%
# calculate the absolute and relative occurrence of each maximum position:
.[, by = .(classification, id, change, trial, position), .(
count_num = .N / length(select_trs),
freq = .N
%>%
)] # complete missing positions with zeros:
complete(nesting(classification, id, change, trial),
nesting(position), fill = list(count_num = 0, freq = 0)) %>%
# create a new variable containing the type by copying the max position:
mutate(type = position) %>%
transform(type = ifelse(type %in% c(1,2), type, "none")) %>% setDT(.) %>%
# sort the data table
setorder(., classification, id, change, trial, position, type) %>%
# check if the sum of frequencies matches the number of trs:
verify(.[, by = .(classification, id, change, trial), .(
sum_freq = sum(freq)
$sum_freq == length(select_trs)) %>%
)] # average relative proportion of each position across trials per change:
setDT(.) %>% .[, by = .(classification, id, change, type), .(
mean_count = mean(count_num) * 100,
sum_freq = sum(freq),
num_trials = .N
%>%
)] #check if the data consisted of 5 trials per TR:
verify(num_trials %in% c(5, 15)) %>%
# change variable types for mixed effects models:
transform(change = as.factor(change)) %>%
transform(type = as.factor(type)) %>%
#mutate(mean_count_z = scale(mean_count, center = TRUE, scale = FALSE)) %>% setDT(.) %>%
# create a new variable:
.[type == "1", ":=" (is_first = 1, is_second = 0)] %>%
.[type == "2", ":=" (is_first = 0, is_second = 1)] %>%
.[type == "none", ":=" (is_first = 0, is_second = 0)] %>%
transform(is_first = as.numeric(is_first)) %>%
transform(is_second = as.numeric(is_second))
dt_pred_rep_count %>%
dt_pred_rep_count_mean = # average the number of counts across participants:
setDT(.) %>%
.[, by = .(classification, change, type), .(
mean_count = mean(mean_count),
sd_count = sd(mean_count),
num_subs = .N,
sem_upper = mean(mean_count) + (sd(mean_count)/sqrt(.N)),
sem_lower = mean(mean_count) - (sd(mean_count)/sqrt(.N))
%>%
)] # check if the number of data points (here, participants) is correct:
verify(num_subs == 36) %>%
#verify(.[, by = .(classification, change), .(
# mean_count = sum(mean_count)
#)]$mean_count == 100) %>%
# order the data table:
setorder(., classification, change, type)
lmer(mean_count ~ type * change + (1 + type + change|id),
lme_rep_count =data = subset(dt_pred_rep_count, classification == "ovr" & change %in% c(2,9)),
na.action = na.omit, control = lcctrl)
summary(lme_rep_count)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: mean_count ~ type * change + (1 + type + change | id)
## Data: subset(dt_pred_rep_count, classification == "ovr" & change %in%
## c(2, 9))
## Control: lcctrl
##
## REML criterion at convergence: 1481.9
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.31498 -0.56768 0.04092 0.53526 2.53048
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 19.835 4.4536
## type2 92.142 9.5991 -0.79
## typenone 27.993 5.2908 -0.97 0.61
## change9 0.637 0.7981 0.32 -0.84 -0.07
## Residual 46.779 6.8395
## Number of obs: 216, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 19.352 1.360 67.130 14.226 < 2e-16 ***
## type2 24.537 2.271 60.844 10.804 8.82e-16 ***
## typenone -7.099 1.837 91.869 -3.863 0.000208 ***
## change9 16.759 1.618 140.223 10.361 < 2e-16 ***
## type2:change9 -34.907 2.280 140.000 -15.311 < 2e-16 ***
## typenone:change9 -16.296 2.280 140.000 -7.148 4.45e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) type2 typenn chang9 typ2:9
## type2 -0.723
## typenone -0.773 0.516
## change9 -0.576 0.305 0.434
## type2:chng9 0.419 -0.502 -0.310 -0.705
## typnn:chng9 0.419 -0.251 -0.620 -0.705 0.500
anova(lme_rep_count)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## type 10785 5392.5 2 38.108 115.2755 <2e-16 ***
## change 5 5.0 1 125.584 0.1078 0.7433
## type:change 10983 5491.4 2 140.000 117.3905 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans(lme_rep_count, list(pairwise ~ type | change))
emmeans_results = summary(emmeans_results[[2]]) emmeans_summary =
Test for significance
Test 1: Using a paired t-test, we test how well we are able to decode a single briefly presented item in a 32 ms sequence compared to items that were not presented, when the item (first event) is followed by a statistical representation that could mask the item (change = 2 condition).
Test 2: Using a paired t-test, we then test how well we are able to decode a single briefly presented item in a 32 ms sequence compared to items that were not presented, when the item (last event) is preceded by a statistical signal (change = 9 condition).
2.8.5 Transitions
We analyzed if there more within sequence transitions than transitions to out-of-sequence elements:
function(head, tail){
transition_type <-if (head == 1 & tail == 2){
"forward"
transition_type =else if (head == 2 & tail == 1) {
} "backward"
transition_type =else if (head == 1 & tail == 1) {
} "repetition 1"
transition_type =else if (head == 2 & tail == 2) {
} "repetition 2"
transition_type =#} else if(head == tail) {
# transition_type = "repetition"
else if (head %in% c(1,2) & tail > 2) {
} "outwards"
transition_type =else if (head > 2 & tail %in% c(1,2)) {
} "inwards"
transition_type =else if (head > 2 & tail > 2 & head != tail) {
} "outside"
transition_type =else if (head > 2 & tail > 2 & head == tail) {
} "repetition out"
transition_type =else {
} "ERROR!"
transition_type =
}return(transition_type)
}
2.8.5.1 Figure 4e
seq(2, 7)
trs =# define the number of transitions, which is the number of TRs - 1
length(trs) - 1
num_transitions =# calculate the transitions between decoded repetition trial elements
dt_pred_rep %>%
dt_pred_rep_step = # filter for specific trs in a selected time window:
filter(seq_tr %in% trs) %>% setDT(.) %>%
# for each classification, id, trial, get position with highest probability:
.[, by = .(classification, id, trial, change, seq_tr), .(
num_trials = .N,
position_max = position[which.max(probability)]
%>%
)] # check if the data consisted of 5 trials per TR:
verify(num_trials == 5) %>%
# get the head and tail of the maximum positions (sequence shifted by 1):
# head = tr 1 to n-1 and tail = tr 2 to n:
.[, by = .(classification, id, trial, change), .(
n = .N,
head = head(position_max, -1),
tail = tail(position_max, -1)
%>%
)] # get the number of each unique combination of consecutive positions indices:
.[, .N, .(classification, id, trial, change, head, tail)] %>%
# complete all missing pairings with 0s for each trial:
complete(nesting(classification, id, trial, change), nesting(head, tail),
fill = list(N = 0)) %>% setDT(.) %>%
# check if the number of true transitions matching the expected number:
verify(.[, by = .(classification, id, trial, change), .(
sum_of_n = sum(N)
$sum_of_n == num_transitions) %>%
)] # set the order: sort by classification, participant, trial and positions:
setorder(classification, id, trial, change, head, tail) %>%
# define the transition type for every transition:
mutate(step_type = mapply(transition_type, head, tail)) %>%
# calculate the relative proportion per trial across TRs for each transition
mutate(proportion = N/num_transitions) %>% setDT(.) %>%
# check if any step type is not properly assigned:
verify(all(!is.na(step_type))) %>% setDT(.)
dt_pred_rep_step %>%
dt_pred_rep_step_version1 = # average the proportion of transitions across trials:
.[, by = .(classification, id, change, step_type), .(
mean_proportion = mean(proportion) * 100
)]
dt_pred_rep_step %>%
dt_pred_rep_step_version2 = # sum up the number of transitions for each transition type:
.[, by = .(classification, id, trial, change, step_type), .(
num = sum(N)
%>%
)] # check if the number of true transitions matching the expected number per trial:
#verify(.[, by = .(classification, id, trial, change), .(
# num_transitions = sum(num)
#)]$num_transitions == num_transitions) %>%
# calculate the proportion per trial across TRs for each transition
mutate(proportion = num/num_transitions) %>% setDT(.) %>%
# average the proportion of transitions across trials:
.[, by = .(classification, id, change, step_type), .(
mean_num = mean(num),
mean_proportion = mean(proportion) * 100
)]
dt_pred_rep_step_version1 %>%
dt_pred_rep_step_mean = # average across participants and compute the standard error:
.[, by = .(classification, change, step_type), .(
mean_proportion = mean(mean_proportion),
num_subs = .N,
sem_upper = mean(mean_proportion) + (sd(mean_proportion)/sqrt(.N)),
sem_lower = mean(mean_proportion) - (sd(mean_proportion)/sqrt(.N))
%>%
)] verify(all(num_subs == 36)) %>%
setorder(classification, change, step_type) %>%
filter(classification == "ovr")
function(dt){
plot_rep_trans_mat =ggplot(data = dt, mapping = aes(
x = as.factor(head), y = fct_rev(as_factor(tail)),
size = mean_proportion, fill = step_type)) +
geom_point(aes(size = mean_proportion, fill = step_type), pch = 21, color = "black") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE) +
xlab("Decoded event at t") +
ylab("Decoded event at t + 1") +
facet_wrap(~ as.factor(change), labeller = as_labeller(facet_labels_new)) +
scale_size(range = c(1, 15), name = "Proportion per trial (in %)",
guide = guide_legend(
title.position = "top", direction = "horizontal",
nrow = 1,
order = 2)) +
scale_fill_manual(name = "Transition type",
values = c(colors, "white", "white", "white"),
guide = guide_legend(ncol = 2)) +
theme(strip.text.x = element_text(margin = margin(b = 3, t = 2))) +
scale_y_discrete(limits = rev(levels(~tail))) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) +
# remove background of legend keys:
theme(legend.key = element_blank()) +
theme(legend.background = element_blank())
# add annotation
#annotate("segment", x = 0, xend = 6, y = 6, yend = 0,
# colour = "white", size = 10, alpha = 0.8) +
#annotate(geom = "text", x = 3, y = 3, angle = 360 - 45, hjust = 0.5,
# label = "Repetitions of the same decoded event",
# color = "gray")
}
dt_pred_rep_step %>%
dt_pred_rep_step_trans = .[, by = .(classification, change, head, tail, step_type), .(
num_data = .N,
mean_proportion = mean(proportion) * 100
%>%
)] # check if averaged is number of participants times number of trials
verify(all(num_data == 36 * 5)) %>%
# create new variable containing the the averaged number as a label:
mutate(mean_num_label = as.character(sub('^(-)?0[.]', '\\1.', round(mean_proportion, 2)))) %>%
# remove all repetition transitions:
mutate(step_type = ifelse(stringr::str_detect(step_type, "repetition"), "repetition", step_type)) %>%
setDT(.)
plot_rep_trans_mat(
fig_trans_mat =dt = subset(dt_pred_rep_step_trans, classification == "ovr" & change %in% c(2,9)))
fig_trans_mat
2.8.5.2 Source Data File Fig. 4e
subset(dt_pred_rep_step_trans, classification == "ovr" & change %in% c(2,9)) %>%
select(-classification, -num_data) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_4e.csv"),
row.names = FALSE)
2.8.5.3 Figure 4d
function(dt){
plot_rep_trans <-ggplot(
data = dt, aes(x = as.factor(step_type), y = as.numeric(mean_proportion),
fill = as.factor(step_type))) +
geom_bar(stat = "summary", fun = "mean",
aes(fill = as.factor(step_type)),
color = "black",
position = position_dodge()) +
geom_point(position = position_jitter(width = .2, height = 0, seed = 4),
pch = 21, alpha = 0.5, color = "black") +
geom_errorbar(stat = "summary", fun.data = "mean_se", width = 0,
position = position_dodge(.9), color = "black") +
facet_wrap(~ as.factor(change), labeller = as_labeller(facet_labels_new)) +
xlab("Serial event") +
#xlab(expression(paste("Time-point of change to ", 2^nd, " serial event"))) +
ylab("Proportion per trial (%)") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0,10)) +
scale_fill_manual(name = "Transition", values = c(colors, "lightblue", "lightcoral", "darkgray")) +
theme(strip.text.x = element_text(margin = margin(b = 3, t = 2))) +
#scale_x_discrete(labels = as_labeller(facet_labels_new)) +
#theme(legend.position = "right", legend.direction = "vertical", legend.justification = "center") +
theme(legend.position = "bottom", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = -10, r = 0, b = 10, l = 0)) +
guides(fill = guide_legend(nrow = 2, title.position = "top", title.hjust = 0.5)) +
theme(panel.background = element_blank()) +
#theme(plot.margin = unit(c(t = 1, r = 1, b = 1, l = 1), "pt")) +
theme(axis.line.y = element_line(colour = "black"),
axis.line.x = element_line(colour = "white"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(colour = "white"))
}
# & change %in% c(2,9)
plot_rep_trans(dt = subset(
fig_trans_prop === "ovr" & change %in% c(2,9) &
dt_pred_rep_step_version1, classification step_type != "repetition 1" & step_type != "repetition 2" & step_type != "repetition out"
)) fig_trans_prop
2.8.5.4 Source Data File Fig. 4d
subset(
== "ovr" & change %in% c(2,9) &
dt_pred_rep_step_version1, classification step_type != "repetition 1" & step_type != "repetition 2" & step_type != "repetition out"
%>%
) select(-classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_4d.csv"),
row.names = FALSE)
We compare the average proportion of forward transitions with the average proportion of outwards steps in the change = 2 condition. We also compare the average proportion of forward transitions with the average number of within out-of-sequence transitions:
dt_pred_rep_step_version1 %>%
dt_pred_rep_trans_test = filter(step_type %in% c("forward", "outwards", "outside") & change == 9) %>%
spread(key = step_type, value = mean_proportion, drop = TRUE) %>% setDT(.) %>%
.[, by = .(classification), {
t.test(x = forward, y = outwards, paired = TRUE);
ttest_outwards = t.test(x = forward, y = outside, paired = TRUE);
ttest_outside =list(mean_forward = round(mean(forward), 2),
mean_outwards = round(mean(outwards), 2),
mean_outside = round(mean(outside), 2),
df = ttest_outwards$parameter,
ttest_outwards_pvalue = p.adjust(ttest_outwards$p.value,
method = "bonferroni", n = 4),
ttest_outwards_tvalue = round(ttest_outwards$statistic, 2),
ttest_outside_pvalue = p.adjust(ttest_outside$p.value,
method = "bonferroni", n = 4),
ttest_outside_tvalue = round(ttest_outside$statistic, 2),
num_subs = .N)
%>%
}] verify(all(num_subs == 36)) %>%
filter(classification == "ovr")
dt_pred_rep_trans_test
## classification mean_forward mean_outwards mean_outside df
## 1: ovr 7.22 2.67 1.06 35
## ttest_outwards_pvalue ttest_outwards_tvalue ttest_outside_pvalue
## 1: 4.17355e-05 5.14 7.054921e-08
## ttest_outside_tvalue num_subs
## 1: 7.26 36
2.8.6 Figure 4
plot_grid(plot_grid(fig_a, fig_b, labels = c("a", "b")),
#plot_grid(fig_c, fig_d, labels = c("c", "d")),
plot_grid(fig_c, fig_trans_prop, labels = c("c", "d")),
plot_grid(fig_trans_mat, labels = c("e")),
ncol = 1, label_fontface = "bold")
ggsave(filename = "wittkuhn_schuck_figure_4.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 10)
2.9 Decoding: Resting-state
2.9.1 Preparation
2.9.1.1 Initialization
We load the data and relevant functions:
# find the path to the root of this project:
if (!requireNamespace("here")) install.packages("here")
if ( basename(here::here()) == "highspeed" ) {
here::here("highspeed-analysis")
path_root =else {
} here::here()
path_root =
}# list of participants with chance performance on sequence and repetition trials:
c("sub-24", "sub-31", "sub-37", "sub-40") chance_performer =
The next step is only evaluated during manual execution:
# source all relevant functions from the setup R script:
source(file.path(path_root, "code", "highspeed-analysis-setup.R"))
2.9.1.2 Prepare sequence events data
We select all events files for sequence trials when the stimuli were presented:
# create a subset of the events data table only including sequence task events:
subset(dt_events, condition == "sequence" & trial_type == "stimulus")
dt_events_seq =::paged_table(dt_events_seq) rmarkdown
2.9.1.3 Prepare resting decoding data
We prepare the resting state data for analysis:
# create a subset that only includes the resting state data:
dt_pred %>%
dt_pred_rest = # filter for rest only data that was created by the union mask:
filter(condition == "rest" & class != "other" & mask == "union") %>%
# sort the data frame by classification, session, etc.:
setorder(id, classification, session, run_study, seq_tr, class) %>%
# add random positions for the events detected during resting state:
# group by classification and participants to generate class specific indices:
group_by(classification, id) %>%
# create class specific position indices randomly for every participant:
group_modify( ~ { .x %>% mutate(position = mapvalues(
$class, from = unique(.x$class), to = sample(length(unique(.x$class)))))}) %>%
.x ungroup %>%
transform(position = as.numeric(position)) %>%
setDT(.) %>%
# there should be a unique position for every class in every participant:
verify(all(.[, by = .(classification, id, class), .(
unique_positions = length(unique(position))
$unique_positions == 1)) %>%
)] # verify that every class is represented by all indices across participants:
verify(all(.[, by = .(classification, class), .(
unique_positions = length(unique(position))
$unique_positions == 5)) %>%
)] # check that the number of TRs for each resting state run is correct
verify(all(.[, by = .(classification, id, run_study, session, class), .(
num_trs = .N)]$num_trs == 233))
We did not acquire pre-task resting-state data in Session 1. We filter for these participants below:
# create an array with all participant ids who do not have all four resting state runs:
unique(
ids_no_pre_s1 =%>% setDT(.) %>%
(dt_pred_rest .[, by = .(id, session), .(num_runs = length(unique(run_study)))] %>%
filter(!(num_runs == 2)))$id)
# print the ids of the four participants without pre-task session 1 rest:
print(ids_no_pre_s1)
## [1] "sub-01" "sub-02" "sub-03" "sub-04"
We select data from all remaining participants with pre-task session 1 rest:
dt_pred_rest %>%
dt_pred_rest_s1 = # exclude participants without session 1 pre-task rest:
filter(!(id %in% c(ids_no_pre_s1))) %>%
# exclude participants who performed below chance in seq and rep trials:
filter(!(id %in% c(chance_performer))) %>%
# filter for pre-task resting state data in session 1:
#filter(run_study == "run-pre" & session == "ses-01") %>%
# filter for the one-versus-rest classification approach:
filter(classification == "ovr") %>%
# filter resting state data to the same length as concatenated sequence data:
#filter(seq_tr %in% seq(1, 180)) %>%
# check if the number of participants is correct:
verify(length(unique(id)) == 32) %>%
setDT(.) %>%
# create unique run indices through the combination of run and session labels:
.[grepl("run-pre", run_study, fixed = TRUE) & session == "ses-01",
:= "run-pre_ses-01"] %>%
run_study .[grepl("run-pre", run_study, fixed = TRUE) & session == "ses-02",
:= "run-pre_ses-02"] %>%
run_study .[grepl("run-post", run_study, fixed = TRUE) & session == "ses-01",
:= "run-post_ses-01"] %>%
run_study .[grepl("run-post", run_study, fixed = TRUE) & session == "ses-02",
:= "run-post_ses-02"] %>%
run_study setDT(.)
2.9.1.4 Prepare sequence decoding data
We prepare the sequence trial data that will be combined with the resting state data:
# create a list of all participants that are excluded from the resting state analysis:
unique(dt_events_seq$subject[
sub_exclude_rest =!(dt_events_seq$subject %in% dt_pred_rest_s1$id)])
# create a subset of the decoding data only including the sequence task data:
dt_pred %>%
dt_pred_seq = # create a subset of the decoding data only including the sequence task data:
filter(condition == "sequence" & class != "other" & mask == "cv" & stim != "cue") %>%
setDT(.) %>%
# add the serial position, change trial and target cue to the sequence data table:
.[, c("position", "change", "trial_cue", "trial_cue_position") := get_pos(
.(id, trial, class),
.SD, dt_events_seq), by = c("id", "trial", "class")] %>%
.SDcols = # remove columns not needed to align data table with resting state data:
mutate(change = NULL, trial_cue = NULL, trial_cue_position = NULL) %>%
# rename the speed condition to include the "ms" label and express in milliseconds:
transform(tITI = paste(as.character(as.numeric(tITI) * 1000), "ms")) %>%
# remove participants that should be excluded from resting state analysis:
filter(!(id %in% sub_exclude_rest)) %>%
setDT(.) %>%
# only select TRs of the forward and backward period
filter(seq_tr %in% seq(2, 13)) %>%
setDT(.) %>%
# check if the number of TRs for each participant is correct:
verify(.[, by = .(classification, id, tITI, trial_tITI, class), .(
num_trs = .N)]$num_trs == 12)
2.9.1.5 Combine sequence and resting data
We combine the sequence and resting state decoding data in one data table:
#c("run-pre_ses-01", "run-post_ses-01", "run-pre_ses-02", "run-post_ses-02")
c("run-pre_ses-02", "run-post_ses-02")
deselect_rest_run <- rbind(dt_pred_seq, dt_pred_rest_s1) %>%
dt_pred_all = # filter for pre-task resting state of session 1 only:
filter(!(run_study %in% deselect_rest_run)) %>%
# reorder the factor levels of the combined data table to sort in ascending order:
transform(tITI = factor(tITI, levels(as.factor(tITI))[c(3,5,1,4,2,6)])) %>%
# concatenate all session 1 pre-task resting state scans and add counter:
filter(tITI %in% c("32 ms", "2048 ms", "rest")) %>%
setDT(.) %>%
# adjust resting state grouping variable to accommodate multiple runs:
.[tITI == "rest", tITI := paste0(tITI, "_", run_study)] %>%
# filter out one-versus-rest classification trials only:
filter(classification == "ovr") %>%
setDT(.) %>%
# sort the data table:
setorder(id, condition, tITI, trial_tITI, seq_tr, position) %>%
setDT(.)
We use a short function to run some checks on the combined data table:
function(dt){
checkdata =library(data.table); library(tidyverse);
%>%
dt # check the number of trs in all resting state data:
verify(.[stringr::str_detect(tITI, "rest"), by = .(classification, id, tITI, class), .(
num_trs = .N
$num_trs == 233) %>%
)] # check the number of trs in all sequence trial data:
verify(.[!stringr::str_detect(tITI, "rest"), by = .(classification, id, tITI, trial_tITI, class), .(
num_trs = .N
$num_trs == 12) %>%
)] # check the number of unique trials for each sequence speed condition:
verify(.[!stringr::str_detect(tITI, "rest"), by = .(classification, id, tITI), .(
num_trials = length(unique(trial_tITI))
$num_trials == 15) %>%
)] # check the number of speed conditions in sequence trials
verify(.[!stringr::str_detect(tITI, "rest"), by = .(classification, id), .(
num_speeds = length(unique(tITI))
$num_speeds == 2) %>%
)] # check the number of speed conditions in resting state data:
verify(.[stringr::str_detect(tITI, "rest"), by = .(classification, id, tITI), .(
num_speeds = length(unique(tITI))
$num_speeds == 1) %>%
)] # check the mask name in resting state data:
verify(all(.[stringr::str_detect(tITI, "rest"), .(name_mask = unique(mask))]$name_mask == "union")) %>%
# check the mask name in sequence trial data:
verify(all(.[!stringr::str_detect(tITI, "rest"), .(name_mask = unique(mask))]$name_mask == "cv")) %>%
# check if the number of items per run study is as expected:
verify(all(.[!stringr::str_detect(tITI, "rest"), by = .(classification, id, tITI, trial_tITI, class), .(
num_trs = .N
$num_trs == 12))
)]
}# check the current data table:
::paged_table(checkdata(dt_pred_all)) rmarkdown
2.9.2 Decoding probabilities
We draw a random example participant used for plotting:
set.seed(3)
sample(unique(dt_pred_all$id), 1) example_id =
We concatenate all fast and slow sequence trials and resting state:
dt_pred_all %>%
dt_pred_conc = # we add a consecutive counter that will be used to concatenate data:
.[, by = .(classification, id, tITI, class), ":=" (t = seq(1, .N))] %>%
# omit the last TRs in the resting state data:
filter(!(stringr::str_detect(tITI, "rest") & seq_tr > 180)) %>%
# we will focus all further analyses on the one-versus-rest classifier:
filter(classification == "ovr") %>%
setDT(.) %>%
# verify that both resting and sequence trial data are restricted to 180 TRs:
verify(.[, by = .(classification, id, tITI, class, tITI), .(
num_trs = .N)]$num_trs == 180) %>%
setDT(.)
We plot the probabilities of all concatenated sequence trials and the session 1 resting state data (separately for each speed condition):
2.9.3 Calculating sequentiality
We create a function to determine whether a certain sequential combination of the task stimuli has been presented to the participants on sequence trials or not:
function(seq, dt_events_seq) {
did_you_see_that <- dt_events_seq %>%
dt_events_seq_sub <- #filter(subject == sub) %>%
setDT(.) %>%
# calculate if the input sequence was seen for every trial:
.[, by = .(subject, interval_time, trial), .(
num_events = .N,
seen = as.numeric(all(stim_label[serial_position] == seq))
%>%
)] # check if the number of events per sequence is 5:
verify(num_events == 5) %>%
# the number a seen sequences should be either 0 (not seen) or 5 (seen five)
# time for the five different speed conditions:
verify(.[, by = .(subject), .(sum_seen = sum(seen))]$sum_seen %in% c(0, 5)) %>%
# for each speed condition, the number should be 1 (seen) or 0 (not seen):
verify(.[, by = .(subject, interval_time), .(sum_seen = sum(seen))]$sum_seen %in% c(0, 1))
# return if sequence was seen (1) or not (0):
as.numeric(sum(dt_events_seq_sub$seen) == 5)
seen <-return(seen)
}
We verify that the function works properly:
permn(1:5, sort = TRUE)
sequences =# list of all possible stimuli:
c("cat", "chair", "face", "house", "shoe")
seq <-# select one example participant:
"sub-14"
sub =# determine if participant saw 15 combinations of the five stimuli:
sum(unlist(lapply(X = sequences, function(x)
sum_seq =did_you_see_that(seq = seq[x], subset(dt_events_seq, subject == sub)))))
verify(data.table(sum_seq), sum_seq == 15)
## sum_seq
## 1: 15
We calculate the regression slopes for every TR and assumed sequence in resting-state data:
# create an array with all the stimulus labels:
c("cat", "chair", "face", "house", "shoe")
stimuli <-# register the computation start time:
Sys.time()
start_time <-# calculate sd probability, slope for every permutation of a sequence:
dt_pred_conc %>%
dt_pred_conc_slope_rest = # filter for resting state analyses only:
filter(stringr::str_detect(tITI, "rest")) %>%
setDT(.) %>%
# calculate sd probability, slope and if sequence was seen or not:
.[, by = .(id, classification, tITI, trial_tITI, seq_tr), .(
# calculate the number of events per TR and the mean position:
num_events = .N,
mean_position = mean(position),
# calculate the standard deviation of the probabilities:
sd_prob = sd(probability),
# calculate the slope for each permuted sequence:
slope = unlist(lapply(sequences, function(x) coef(lm(probability ~ x))[2] * (-1))),
# determine if the permuted sequence was actually seen by the subject:
seen = unlist(lapply(sequences, function(x)
did_you_see_that(seq = stimuli[x], subset(dt_events_seq, subject == unique(id)))))
%>%
)] # add a counter for all sequential combinations:
.[, by = .(id, classification, tITI, trial_tITI, seq_tr), "sequence" := seq(1,.N)]
# stop the counter and print the calculation time:
Sys.time()
end_time =print(end_time - start_time)
We calculate the regression slopes for sequence data:
dt_pred_conc %>%
dt_pred_conc_slope_seq = # filter for sequence data only:
filter(!stringr::str_detect(tITI, "rest")) %>%
base::droplevels() %>%
setDT(.) %>%
# calculate the slope for every participant, speed and TR:
.[, by = .(id, classification, tITI, trial_tITI, seq_tr), .(
# calculate the number of events per TR and the mean position:
num_events = .N,
mean_position = mean(position),
# calculate the standard deviation of the probabilities:
sd_prob = sd(probability),
# calculate the slope for each permuted sequence:
slope = coef(lm(probability ~ position))[2] * (-1)
%>%
)] # add variables if sequence was seen or not and sequence counter:
.[, "seen" := 1] %>%
.[, "sequence" := 1]
We combine the resting state and sequence trial slope data:
rbind(dt_pred_conc_slope_seq, dt_pred_conc_slope_rest) %>%
dt_pred_conc_slope = # verify that there are 5 events per TR and their mean position is 5:
verify(all(num_events == 5)) %>%
verify(all(mean_position == 3)) %>%
# sort the columns by classification approach, id, speed, trial and TR:
setorder(classification, id, tITI, trial_tITI, seq_tr) %>%
# add trial counter for every speed condition (and sequence):
.[stringr::str_detect(tITI, "rest"),
.(classification, id, tITI, sequence), ":=" (t = seq(1, .N))] %>%
by = .[!stringr::str_detect(tITI, "rest"),
.(classification, id, tITI), ":=" (t = seq(1, .N))] %>%
by = # group by classification, id, speed and trial and calculate step sizes:
.[!stringr::str_detect(tITI, "rest"), by = .(classification, id, tITI),
:= trial_tITI - shift(trial_tITI)] %>%
trial_start # create a variable that signifies the trial starts for sequence trials:
transform(trial_start = ifelse(
is.na(trial_start) & !stringr::str_detect(tITI, "rest"), 1, trial_start)) %>%
# create new variable names for resting state data:
.[tITI == "rest_run-pre_ses-01", tITI := "Rest Pre S1"] %>%
.[tITI == "rest_run-post_ses-01", tITI := "Rest Post S1"] %>%
.[tITI == "rest_run-pre_ses-02", tITI := "Rest Pre S2"] %>%
.[tITI == "rest_run-post_ses-02", tITI := "Rest Post S2"] %>%
# sort the factor levels of the speed conditions:
transform(tITI = factor(tITI, levels = c(
"32 ms", "2048 ms", "Rest Pre S1", "Rest Post S1",
"Rest Pre S2", "Rest Post S2"))) %>%
# rename the factor for seen (1) and unseen (0) sequences:
.[, seen := ifelse(seen == 1, "More frequent", "Less frequent")] %>%
setDT(.)
We save the regression slope data during manual execution, save it to the repository and reload it for all following analyses:
save(dt_pred_conc_slope, file = file.path(path_root, "data", "tmp", "dt_pred_conc_slope.Rdata"))
load(file.path(path_root, "data", "tmp", "dt_pred_conc_slope.Rdata"))
2.9.4 Mean slope coefficients
2.9.4.1 Fixed event order
We calculate the mean slope coefficients assuming a fixed ordering of sequence events for sequence trials:
# calculate the mean slope across participants for each time point:
dt_pred_conc_slope %>%
dt_pred_conc_slope_mean <- filter(sequence == 1) %>%
setDT(.) %>%
.[, by = .(classification, tITI, t, trial_start), .(
num_subs = .N,
mean_slope = mean(slope),
sem_upper = mean(slope) + (sd(slope)/sqrt(.N)),
sem_lower = mean(slope) - (sd(slope)/sqrt(.N))
%>%
)] verify(all(num_subs == 32)) %>%
filter(classification == "ovr") %>%
# sort the factor levels of the speed conditions:
transform(tITI = factor(tITI, levels = c(
"2048 ms", "32 ms", "Rest Pre S1", "Rest Post S1",
"Rest Pre S2", "Rest Post S2"))) %>%
setDT(.)
2.9.4.2 Figure 5c
We plot the mean regression coefficient separately for all sequence speed conditions and the pre-task resting state data. Trial boundaries (for sequence trials) are shown as gray vertical lines.
2.9.4.3 Source Data File Fig. 5c
%>% filter(tITI != "Rest Post S1") %>%
dt_pred_conc_slope_mean select(-num_subs, -classification) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5c.csv"),
row.names = FALSE)
We calculate the mean regression slopes and compare sequence and rest:
# calculate mean regression slopes for fast and slow sequence trials and rest:
dt_pred_conc_slope %>%
dt_pred_conc_slope_stat = filter(sequence == 1) %>%
setDT(.) %>%
.[, by = .(classification, id, tITI), .(
num_trs = .N,
mean_abs_slope = mean(abs(slope)),
abs_mean_slope = abs(mean(slope)),
mean_sd_prob = mean(sd_prob)
%>% verify(all(num_trs == 180)) %>%
)] filter(classification == "ovr") %>%
transform(tITI = factor(tITI, levels = c(
"32 ms", "2048 ms", "Rest Pre S1", "Rest Post S1",
"Rest Pre S2", "Rest Post S2"))) %>%
setDT(.)
# compare mean probabilities between resting state and fast sequence data:
subset(dt_pred_conc_slope_stat, tITI == "Rest Pre S1")$mean_sd_prob
mean_sd_rest_pre = subset(dt_pred_conc_slope_stat, tITI == "Rest Post S1")$mean_sd_prob
mean_sd_rest_post = subset(dt_pred_conc_slope_stat, tITI == "32 ms")$mean_sd_prob
mean_sd_fast = subset(dt_pred_conc_slope_stat, tITI == "2048 ms")$mean_sd_prob
mean_sd_slow = t.test(x = mean_sd_rest_pre, y = mean_sd_fast, alternative = "two.sided", paired = TRUE)
fast_vs_rest_pre = t.test(x = mean_sd_rest_post, y = mean_sd_fast, alternative = "two.sided", paired = TRUE)
fast_vs_rest_post = t.test(x = mean_sd_slow, y = mean_sd_fast, alternative = "two.sided", paired = TRUE)
fast_vs_slow = t.test(x = mean_sd_rest_pre, y = mean_sd_slow, alternative = "two.sided", paired = TRUE)
slow_vs_rest_pre = t.test(x = mean_sd_rest_pre, y = mean_sd_rest_post, alternative = "two.sided", paired = TRUE)
rest_pre_vs_rest_post =print(round(c(mean(mean_sd_rest_pre), mean(mean_sd_fast), mean(mean_sd_slow)), 2))
round_pvalues(p.adjust(
c(fast_vs_rest_pre$p.value, fast_vs_slow$p.value, slow_vs_rest_pre$p.value,
$p.value, fast_vs_rest_post$p.value), method = "fdr"))
rest_pre_vs_rest_post round((mean(mean_sd_rest_pre) - mean(mean_sd_fast)) / sd(mean_sd_rest_pre - mean_sd_fast), 2)
cohensd_rest = round((mean(mean_sd_slow) - mean(mean_sd_fast)) / sd(mean_sd_slow - mean_sd_fast), 2)
cohensd_slow =print(cohensd_rest); print(cohensd_slow)
fast_vs_rest_pre; fast_vs_slow
## [1] 0.23 0.20 0.22
## [1] "p < .001" "p < .001" "p = .19" "p = .18" "p = .002"
## [1] 0.88
## [1] 0.74
##
## Paired t-test
##
## data: mean_sd_rest_pre and mean_sd_fast
## t = 4.9859, df = 31, p-value = 2.236e-05
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.01724796 0.04112626
## sample estimates:
## mean of the differences
## 0.02918711
##
##
## Paired t-test
##
## data: mean_sd_slow and mean_sd_fast
## t = 4.1704, df = 31, p-value = 0.000227
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.009556449 0.027850046
## sample estimates:
## mean of the differences
## 0.01870325
2.9.4.4 Figure 5a / 5b
We plot the mean absolute regression slope for all speed conditions and pre-task resting state data:
function(data, variable, ylabel, ylim){
plot_means =#colors = c(hcl.colors(5, "Cividis")[c(1)], "gold", "gray")
c(hcl.colors(5, "Cividis")[c(1)], "gold", grays(4))
colors = ggplot(
fig_mean_beta =data = data %>% filter(tITI != "Rest Post S1"),
aes(
y = get(variable), x = tITI, fill = tITI)) +
geom_bar(stat = "summary", fun = "mean", position = position_dodge(0.9)) +
geom_point(position = position_jitter(width = 0.1, height = 0, seed = 2),
alpha = 1, inherit.aes = TRUE, pch = 21,
color = "white") +
stat_summary(fun.data = mean_se, geom = "errorbar",
position = position_dodge(0.9), width = 0, color = "black") +
xlab("Data") + ylab(ylabel) +
coord_capped_cart(left = "both", expand = TRUE, ylim = ylim) +
scale_fill_manual(values = colors, guide = FALSE) +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank(),
axis.title.x = element_blank()) +
theme(axis.text.x = element_text(angle = 45, hjust = 0.9)) +
theme(panel.border = element_blank(), axis.line.y = element_line()) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank())
} plot_means(data = dt_pred_conc_slope_stat, variable = "abs_mean_slope",
fig_mean_beta =ylabel = "Absolute mean regression slope", ylim = c(0, 0.02))
plot_means(data = dt_pred_conc_slope_stat, variable = "mean_abs_slope",
fig_mean_beta_abs =ylabel = "Regression slope (abs)", ylim = c(0, 0.1))
plot_means(data = dt_pred_conc_slope_stat, variable = "mean_sd_prob",
fig_sd_prob =ylabel = "SD of probability", ylim = c(0, 0.4))
fig_mean_beta; fig_mean_beta_abs; fig_sd_prob;
2.9.4.5 Source Data File Fig. 5a / 5b
%>%
dt_pred_conc_slope_stat select(-classification, -num_trs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5a.csv"),
row.names = FALSE)
%>%
dt_pred_conc_slope_stat select(-classification, -num_trs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5b.csv"),
row.names = FALSE)
2.9.4.6 All possible event orders
We calculate the mean slope for every time point (for every TR) depending on if a sequence was actually seen or not (relevant for resting state data). Note, that by this definition, all sequences on sequence trials were “seen”.
# calculate the mean slope across participants for each time point:
dt_pred_conc_slope %>%
dt_pred_conc_slope_mean_seen <- # average slopes for each participant and each time point:
.[, by = .(id, classification, tITI, t, trial_start, seen), .(
num_seq = .N,
mean_slope_sub = mean(slope)
%>%
)] # number of sequences will be 1 (sequence conditions), 15 (seen permuted
# sequence condition), or 105 (not seen permuted sequence conditions):
verify(num_seq %in% c(1, 15, 105)) %>%
# calculate the mean slope across participants (including SEM):
.[, by = .(classification, tITI, t, trial_start, seen), .(
num_subs = .N,
mean_slope = mean(mean_slope_sub),
sem_upper = mean(mean_slope_sub) + (sd(mean_slope_sub)/sqrt(.N)),
sem_lower = mean(mean_slope_sub) - (sd(mean_slope_sub)/sqrt(.N))
%>%
)] # verify that data was averaged across the correct number of participants:
verify(all(num_subs == 32)) %>%
filter(classification == "ovr") %>%
# sort factor levels of the speed condition:
transform(tITI = factor(tITI, levels = c(
"2048 ms", "32 ms", "Rest Pre S1", "Rest Post S1",
"Rest Pre S2", "Rest Post S2"))) %>%
setDT(.)
We calculate the mean absolute and non-absolute slopes for sequence and rest trials depending on whether the permuted sequence was actually seen or not:
# calculate mean regression slopes for fast and slow sequence trials and rest:
dt_pred_conc_slope %>%
dt_pred_conc_slope_stat_seen = .[, by = .(classification, id, tITI, seen), .(
num_trs = .N,
mean_slope = mean(slope),
mean_abs_slope = mean(abs(slope)),
abs_mean_slope = abs(mean(slope)),
abs_pos_slope = mean(abs(slope[slope > 0])),
abs_neg_slope = mean(abs(slope[slope < 0])),
mean_sd_prob = mean(sd_prob)
%>%
)] # check if the average slope was calculated across the correct number of TRs
# which should be 180 for sequence trials and 180 times number of sequences
# not seen (105) and 180 times sequences seen (15) in permuted rest data:
verify(all(num_trs %in% c(180, 180 * (120 - 15), 180 * 15))) %>%
filter(classification == "ovr") %>%
setDT(.)
function(data, variable, ylabel, ylim){
plot_means_seen =#colors = c(hcl.colors(5, "Cividis")[c(1)], "gold", "gray")
colorRampPalette(c("lightgray", "black"))
grays = c(hcl.colors(5, "Cividis")[c(1)], "gold", grays(4))
colors = ggplot(data = data, aes(
fig_mean_beta =y = get(variable), x = tITI, fill = tITI)) +
facet_wrap(~ seen) +
geom_bar(stat = "summary", fun = "mean", position = position_dodge(0.9)) +
geom_point(position = position_jitter(width = 0.1, height = 0, seed = 2),
alpha = 1, inherit.aes = TRUE, pch = 21,
color = "white") +
stat_summary(fun.data = mean_se, geom = "errorbar",
position = position_dodge(0.9), width = 0, color = "black") +
xlab("Data") + ylab(ylabel) +
coord_capped_cart(left = "both", expand = TRUE, ylim = ylim) +
scale_fill_manual(values = colors, guide = FALSE) +
theme(axis.ticks.x = element_blank(), axis.line.x = element_blank(),
axis.title.x = element_blank()) +
theme(axis.text.x = element_text(angle = 45, hjust = 0.9)) +
theme(panel.border = element_blank(), axis.line.y = element_line()) +
theme(axis.line.y = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank())
} plot_means_seen(
fig_mean_beta_seen =data = dt_pred_conc_slope_stat_seen, variable = "abs_mean_slope",
ylabel = "Absolute mean regression slope", ylim = c(-0.01, 0.02))
plot_means_seen(
fig_mean_beta_abs_seen =data = dt_pred_conc_slope_stat_seen, variable = "mean_abs_slope",
ylabel = "Mean absolute regression slope", ylim = c(0, 0.1))
plot_means_seen(
fig_sd_prob_seen =data = dt_pred_conc_slope_stat_seen, variable = "mean_sd_prob",
ylabel = "SD of probability", ylim = c(0, 0.4))
plot_means_seen(
fig_mean_pos_abs_seen =data = dt_pred_conc_slope_stat_seen, variable = "abs_pos_slope",
ylabel = "Mean absolute regression slope", ylim = c(0, 0.1))
plot_means_seen(
fig_mean_neg_abs_seen =data = dt_pred_conc_slope_stat_seen, variable = "abs_neg_slope",
ylabel = "Mean absolute regression slope", ylim = c(0, 0.1))
plot_means_seen(
fig_mean_slope_seen =data = dt_pred_conc_slope_stat_seen, variable = "mean_slope",
ylabel = "Absolute mean regression slope", ylim = c(-0.01, 0.02))
fig_mean_beta_seen; fig_mean_beta_abs_seen; fig_sd_prob_seen;
fig_mean_pos_abs_seen; fig_mean_neg_abs_seen; fig_mean_slope_seen
2.9.5 Frequency spectrum analyses
2.9.5.1 Fixed event order
We create a data table with frequency expectations:
1 / 5.26
fitfreq = 0.1
stim_dur = 5
num_seq_stim =# calculate the delta
fitfreq / (1 + fitfreq * (
delta_freq_fast =* stim_dur + (num_seq_stim - 1) * 0.032))
num_seq_stim fitfreq / (1 + fitfreq * (
delta_freq_slow =* stim_dur + (num_seq_stim - 1) * 2.048))
num_seq_stim 0.01
delta_freq_rest =# create a data table with the expected frequencies:
data.table(xintercept = c(
frequency_expectation =rep(delta_freq_rest, 4))) delta_freq_fast, delta_freq_slow,
We calculate the frequency spectra based on the time courses of regression slope coefficients:
library(lomb)
# create a time vector with random gaps:
as.vector(sapply(0:14, FUN = function(x) seq(0, 1*11, by = 1) + x*100 + round(runif(1)*50)))
time_concat =# get the frequency spectrum of fast and slow trials:
dt_pred_conc_slope %>%
dt_pred_rest_lsp = filter(sequence == 1) %>%
setDT(.) %>%
.[, by = .(classification, id, tITI), {
lsp(x = slope, times = time_concat, from = 0, to = 0.3, plot = FALSE)
frequency_spectrum =list(num_trs = .N, freq_bins = frequency_spectrum$scanned,
filter_width = round(0.01/mean(diff(frequency_spectrum$scanned))),
power = frequency_spectrum$power
%>% verify(all(num_trs == 180)) %>% verify(length(unique(filter_width)) == 1) %>%
)}] # smooth the frequency spectra and frequency bins:
.[, by = .(classification, id, tITI), ":=" (
power_smooth = stats::filter(power, rep(1/unique(filter_width), unique(filter_width))),
freq_bins_smooth = stats::filter(freq_bins, rep(1/unique(filter_width), unique(filter_width)))
)]
# calculate the mean frequency profile across participants:
dt_pred_rest_lsp %>%
dt_pred_rest_lsp_mean = # filter out NA frequency spectrum:
filter(!is.na(power_smooth)) %>%
setDT(.) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins:
.[, by = .(classification, tITI, freq_bins_smooth), .(
num_subs = .N,
mean_spec = mean(power_smooth),
sem_upper = mean(power_smooth) + (sd(power_smooth)/sqrt(.N)),
sem_lower = mean(power_smooth) - (sd(power_smooth)/sqrt(.N))
%>%
)] verify(all(num_subs == 32))
2.9.5.2 Figure 5d
#colors = c(hcl.colors(5, "Cividis")[c(1)], "gold", "gray")
c(hcl.colors(5, "Cividis")[c(1)], "gold", grays(1), "black", grays(2))
colors = ggplot(
fig_freq_lsp =data = dt_pred_rest_lsp_mean %>% filter(tITI != "Rest Post S1"),
aes(
color = tITI, y = as.numeric(mean_spec), x = as.numeric(freq_bins_smooth))) +
geom_vline(data = frequency_expectation, aes(xintercept = xintercept),
linetype = "dashed", color = colors) +
geom_line() +
geom_ribbon(aes(fill = tITI, ymin = sem_lower, ymax = sem_upper),
alpha = 0.3, color = NA) +
geom_text(data = frequency_expectation, aes(
x = xintercept + 0.02, y = 0.1, label = paste(round(xintercept, 2))),
color = colors) +
xlab("Frequency") + ylab("Power") +
scale_color_manual(name = "", values = colors) +
scale_fill_manual(name = "", values = colors) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE,
ylim = c(0, 3), xlim = c(0, 0.3)) +
guides(color = guide_legend(nrow = 1)) +
scale_x_continuous(labels = label_fill(seq(0, 0.3, by = 0.05), mod = 1),
breaks = seq(0, 0.3, by = 0.05)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_freq_lsp
2.9.5.3 Source Data File Fig. 5d
%>% filter(tITI != "Rest Post S1") %>%
dt_pred_rest_lsp_mean select(- classification, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5d.csv"),
row.names = FALSE)
2.9.5.4 All possible event orders
We calculate the frequency spectrum for all permuted resting state sequences and sequence trials:
# create a time vector with random gaps:
as.vector(sapply(0:14, FUN = function(x) seq(0, 1*11, by = 1) + x*100 + round(runif(1)*50)))
time_concat =# get the frequency spectrum of fast and slow trials:
dt_pred_conc_slope %>%
dt_pred_rest_lsp_seen = # calculate the frequency spectrum for each permuted sequence and participant:
.[, by = .(classification, id, tITI, sequence, seen), {
lomb::lsp(
frequency_spectrum =x = slope, times = time_concat, from = 0, to = 0.3, plot = FALSE)
list(num_trs = .N, freq_bins = frequency_spectrum$scanned,
filter_width = round(0.01/mean(diff(frequency_spectrum$scanned))),
power = frequency_spectrum$power
%>%
)}] # the number of TRs for each stretch of activation patterns should be 180:
verify(all(num_trs == 180)) %>%
# there should only be one filtering width per sequence:
verify(length(unique(filter_width)) == 1) %>%
# there should be 120 sequence permutations in the resting condition:
verify(.[, by = .(classification, id, tITI), .(
num_seq = length(unique(sequence)))]$num_seq %in% c(1, 120)) %>%
# smooth the frequency spectra and frequency bins:
.[, by = .(classification, id, tITI, sequence, seen), ":=" (
power_smooth = stats::filter(power, rep(1/unique(filter_width), unique(filter_width))),
freq_bins_smooth = stats::filter(freq_bins, rep(1/unique(filter_width), unique(filter_width)))
)]
We calculate the mean frequency spectra for each participant, classification approach, speed condition, smoothed frequency bin and if the (permuted) rest sequence was actually seen during the task or not:
# calculate the mean frequency profile across participants:
dt_pred_rest_lsp_seen %>%
dt_pred_rest_lsp_mean_seen = # filter out NA frequency spectrum:
filter(!is.na(power_smooth)) %>%
setDT(.) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins and sequences:
.[, by = .(classification, id, tITI, freq_bins_smooth), .(
num_seqs = .N,
power_smooth = mean(power_smooth)
%>%
)] # the number of sequences should be 1 for sequence trials, 15 for seen
# permuted rest repetitions and 105 for unseen sequence repetitions:
verify(num_seqs %in% c(1, 120)) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins:
.[, by = .(classification, tITI, freq_bins_smooth), .(
num_subs = .N,
mean_spec = mean(power_smooth),
sem_upper = mean(power_smooth) + (sd(power_smooth)/sqrt(.N)),
sem_lower = mean(power_smooth) - (sd(power_smooth)/sqrt(.N))
%>%
)] # verify if data was averaged across the expected number of participants:
verify(all(num_subs == 32))
function(speed_s) {
calc_delta <-# input speed_s: sequence speed (ISI), in seconds
1 / 5.26
fitfreq = 0.1
stim_dur = 5
num_seq_stim = fitfreq / (1 + fitfreq * (num_seq_stim * stim_dur + (num_seq_stim - 1) * speed_s))
delta =return(delta)
}# create a data table with the expected frequencies:
data.table(xintercept = c(
frequency_expectation =calc_delta(0.032), calc_delta(2.048), 0.01),
tITI = c("32 ms", "2048 ms", "Rest Pre S1"))
2.9.5.5 Figure 6a
2.9.5.6 Source Data File Fig. 6a
%>%
dt_pred_rest_lsp_mean_seen filter(tITI %in% c("Rest Pre S1", "Rest Post S1")) %>%
select(-classification, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_6a.csv"),
row.names = FALSE)
We calculate the relative power (difference between pre- and post task resting state data) across the entire frequency spectrum:
# calculate the mean frequency profile across participants:
dt_pred_rest_lsp_seen %>%
dt_pred_rest_lsp_mean_rel_seen = filter(tITI %in% c("Rest Pre S1", "Rest Post S1")) %>%
transform(tITI = ifelse(tITI == "Rest Pre S1", "rest_pre_s1", "rest_post_s1")) %>%
# filter out NA frequency spectrum:
filter(!is.na(power_smooth)) %>%
setDT(.) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins and sequences:
.[, by = .(classification, id, tITI, freq_bins_smooth), .(
num_seqs = .N,
power_smooth = mean(power_smooth)
%>%
)] # the number of sequences should be 1 for sequence trials, 15 for seen
# permuted rest repetitions and 105 for unseen sequence repetitions:
verify(num_seqs %in% c(1, 120)) %>%
# transform resting state data from long to wide to calculate relative power:
spread(data = ., key = tITI, value = power_smooth) %>%
mutate(rel_power = rest_post_s1 - rest_pre_s1) %>%
setDT(.) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins:
.[, by = .(classification, freq_bins_smooth), .(
num_subs = .N,
mean_spec = mean(rel_power),
sem_upper = mean(rel_power) + (sd(rel_power)/sqrt(.N)),
sem_lower = mean(rel_power) - (sd(rel_power)/sqrt(.N))
%>%
)] # verify if data was averaged across the expected number of participants:
verify(all(num_subs == 32))
We determine in which frequency range the peak in difference between pre- and post-task rest is:
round(dt_pred_rest_lsp_mean_rel_seen$freq_bins_smooth[which.max(dt_pred_rest_lsp_mean_rel_seen$mean_spec)], 2)
increase_slow = round(dt_pred_rest_lsp_mean_rel_seen$freq_bins_smooth[dt_pred_rest_lsp_mean_rel_seen$freq_bins_smooth > 0.07][which.max(dt_pred_rest_lsp_mean_rel_seen[dt_pred_rest_lsp_mean_rel_seen$freq_bins_smooth > 0.07]$mean_spec)], 2)
increase_fast = increase_slow; increase_fast;
## [1] 0.04
## [1] 0.17
2.9.5.7 Figure 6b
2.9.5.8 Source Data File Fig. 6b
%>%
dt_pred_rest_lsp_mean_rel_seen select(-classification, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_6b.csv"),
row.names = FALSE)
2.9.5.9 Frequency spectra of frequent vs. less frequent sequences
We calculate the mean frequency spectra depending on whether the assumed sequences occured more frequency or less frequently during the task:
# calculate the mean frequency profile across participants:
dt_pred_rest_lsp_seen %>%
dt_pred_rest_lsp_mean_seen = # filter out NA frequency spectrum:
filter(!is.na(power_smooth)) %>%
setDT(.) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins and sequences:
.[, by = .(classification, id, tITI, freq_bins_smooth, seen), .(
num_seqs = .N,
power_smooth = mean(power_smooth)
%>%
)] # the number of sequences should be 1 for sequence trials, 15 for seen
# permuted rest repetitions and 105 for unseen sequence repetitions:
verify(num_seqs %in% c(1, 15, 105)) %>%
# calculate the mean smoothed frequency spectrum across all frequency bins:
.[, by = .(classification, tITI, freq_bins_smooth, seen), .(
num_subs = .N,
mean_spec = mean(power_smooth),
sem_upper = mean(power_smooth) + (sd(power_smooth)/sqrt(.N)),
sem_lower = mean(power_smooth) - (sd(power_smooth)/sqrt(.N))
%>%
)] # verify if data was averaged across the expected number of participants:
verify(all(num_subs == 32))
2.9.6 Relative frequency spectra (fixed order)
We compare the power of the expected frequencies of resting state, fast, and slow sequence trial data in data from resting state, fast, and slow sequence trial data.
1 / 5.26
fitfreq = 0.1
stim_dur = 5
num_seq_stim =# calculate the delta
fitfreq / (1 + fitfreq * (num_seq_stim * stim_dur + (num_seq_stim - 1) * 0.032))
delta_freq_fast = fitfreq / (1 + fitfreq * (num_seq_stim * stim_dur + (num_seq_stim - 1) * 2.048))
delta_freq_slow = 0.01
delta_freq_rest =# create a data table with the expected frequencies:
data.table(xintercept = c(
frequency_expectation =rep(delta_freq_rest, 4))) delta_freq_fast, delta_freq_slow,
dt_pred_rest_lsp %>%
dt_pred_rest_lsp_exp = .[, by = .(classification, id, tITI), .(
power_index_fast = power_smooth[which.min(abs(freq_bins_smooth - delta_freq_fast))],
power_index_slow = power_smooth[which.min(abs(freq_bins_smooth - delta_freq_slow))],
power_index_rest = power_smooth[which.min(abs(freq_bins_smooth - delta_freq_rest))]
%>%
)] # melt all index variables into one column:
gather(grep("power", names(.), fixed = TRUE), key = "index", value = "power") %>% setDT(.) %>%
.[grepl("index_fast", index), label := paste0("Fast (", round(delta_freq_fast, 2), ")")] %>%
.[grepl("index_slow", index), label := paste0("Slow (", round(delta_freq_slow, 2), ")")] %>%
.[grepl("index_rest", index), label := paste0("Rest (", round(delta_freq_rest, 2), ")")] %>%
setorder(., classification, id, tITI) %>%
filter(classification == "ovr") %>%
setDT(.)
2.9.6.1 Figure 5e
c(hcl.colors(5, "Cividis")[c(1)], "gold", "gray", "black")
colors = ggplot(
fig_freq_exp =data = dt_pred_rest_lsp_exp %>% filter(tITI != "Rest Post S1"),
aes(y = power, x = fct_rev(label), fill = tITI)) +
geom_bar(stat = "summary", fun = "mean", position = position_dodge(0.9),
width = 0.8) +
geom_point(position = position_jitterdodge(
jitter.width = 0.1, jitter.height = 0, seed = 2, dodge.width = 0.9),
alpha = 1, inherit.aes = TRUE, pch = 21,
color = "white") +
stat_summary(fun.data = "mean_se", geom = "errorbar",
position = position_dodge(0.9), width = 0, color = "black") +
xlab("Predicted frequency") + ylab("Power") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 5)) +
scale_fill_manual(values = colors, name = "") +
theme(axis.ticks.x = element_line(color = "white"), axis.line.x = element_line(color = "white")) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_freq_exp
lmerTest::lmer(
lme_power <-~ tITI + (1 | id),
power data = dt_pred_rest_lsp_exp %>%
filter(tITI != "Rest Post S1" & index == "power_index_fast"),
control = lcctrl)
summary(lme_power)
anova(lme_power)
emmeans(lme_power, list(pairwise ~ tITI))
emmeans_results = emmeans_results
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: power ~ tITI + (1 | id)
## Data: dt_pred_rest_lsp_exp %>% filter(tITI != "Rest Post S1" & index ==
## "power_index_fast")
## Control: lcctrl
##
## REML criterion at convergence: 86.8
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.5706 -0.6071 -0.1167 0.3281 4.1123
##
## Random effects:
## Groups Name Variance Std.Dev.
## id (Intercept) 0.02222 0.1491
## Residual 0.11422 0.3380
## Number of obs: 96, groups: id, 32
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 1.24805 0.06530 88.31374 19.113 < 2e-16 ***
## tITI2048 ms -0.27643 0.08449 62.00000 -3.272 0.001749 **
## tITIRest Pre S1 -0.31311 0.08449 62.00000 -3.706 0.000451 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) tITI2m
## tITI2048 ms -0.647
## tITIRstPrS1 -0.647 0.500
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## tITI 1.8752 0.93759 2 62 8.2088 0.0006876 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## $`emmeans of tITI`
## tITI emmean SE df lower.CL upper.CL
## 32 ms 1.248 0.0653 88.3 1.118 1.38
## 2048 ms 0.972 0.0653 88.3 0.842 1.10
## Rest Pre S1 0.935 0.0653 88.3 0.805 1.06
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of tITI`
## contrast estimate SE df t.ratio p.value
## 32 ms - 2048 ms 0.2764 0.0845 62 3.272 0.0049
## 32 ms - Rest Pre S1 0.3131 0.0845 62 3.706 0.0013
## 2048 ms - Rest Pre S1 0.0367 0.0845 62 0.434 0.9015
##
## Degrees-of-freedom method: kenward-roger
## P value adjustment: tukey method for comparing a family of 3 estimates
# get the power spectrum of the expected fast frequency in fast data:
subset(dt_pred_rest_lsp_exp, index == "power_index_fast" & tITI == "32 ms")$power
power_fast =# get the power spectrum of the expected fast frequency in slow data:
subset(dt_pred_rest_lsp_exp, index == "power_index_fast" & tITI == "2048 ms")$power
power_slow =# get the power spectrum of the expected fast frequency in rest data:
subset(dt_pred_rest_lsp_exp, index == "power_index_fast" & tITI == "Rest Pre S1")$power
power_rest_pre =# compare fast vs. slow and fast vs. rest:
t.test(power_fast, power_slow, paired = TRUE)
ttest_power_fastvsslow = t.test(power_fast, power_rest_pre, paired = TRUE)
ttest_power_fastvsrestpre = t.test(power_slow, power_rest_pre, paired = TRUE)
ttest_power_slowvsrestpre =round_pvalues(p.adjust(c(ttest_power_fastvsslow$p.value,
$p.value,
ttest_power_fastvsrestpre$p.value), method = "fdr")) ttest_power_slowvsrestpre
## [1] "p = .006" "p = .005" "p = .61"
ttest_power_fastvsslow; ttest_power_fastvsrestpre; ttest_power_slowvsrestpre
##
## Paired t-test
##
## data: power_fast and power_slow
## t = 3.109, df = 31, p-value = 0.004004
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.09509112 0.45776947
## sample estimates:
## mean of the differences
## 0.2764303
##
## Paired t-test
##
## data: power_fast and power_rest_pre
## t = 3.4086, df = 31, p-value = 0.001829
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.1257631 0.5004613
## sample estimates:
## mean of the differences
## 0.3131122
##
## Paired t-test
##
## data: power_slow and power_rest_pre
## t = 0.51505, df = 31, p-value = 0.6102
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## -0.1085718 0.1819355
## sample estimates:
## mean of the differences
## 0.03668188
2.9.6.2 Source Data File Fig. 5e
%>% filter(tITI != "Rest Post S1") %>%
dt_pred_rest_lsp_exp select(-classification, -index) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5e.csv"),
row.names = FALSE)
2.9.7 Relative frequency spectra (all orders)
dt_pred_rest_lsp_seen %>%
dt_pred_rest_lsp_exp_seen = .[, by = .(classification, id, tITI, sequence, seen), .(
power_index_fast = power_smooth[which.min(abs(freq_bins_smooth - delta_freq_fast))],
power_index_slow = power_smooth[which.min(abs(freq_bins_smooth - delta_freq_slow))],
power_index_rest = power_smooth[which.min(abs(freq_bins_smooth - delta_freq_rest))]
%>%
)] # melt all index variables into one column:
gather(grep("power", names(.), fixed = TRUE), key = "index", value = "power") %>%
setDT(.) %>%
.[grepl("index_fast", index), label := paste0("Fast (", round(delta_freq_fast, 2), ")")] %>%
.[grepl("index_slow", index), label := paste0("Slow (", round(delta_freq_slow, 2), ")")] %>%
.[grepl("index_rest", index), label := paste0("Rest (", round(delta_freq_rest, 2), ")")] %>%
# average across sequences for seen and unseen sequences:
.[, by = .(classification, id, tITI, seen, index, label), .(
num_seqs = .N,
power = mean(power)
%>%
)] # check if the number of sequences matches for sequence (1), seen (15) and
# unseen (105) sequences in permuted resting state sequences:
verify(num_seqs %in% c(1, 15, 105)) %>%
setorder(., classification, id, tITI) %>%
filter(classification == "ovr") %>%
setDT(.)
c(hcl.colors(5, "Cividis")[c(1)], "gold", "gray", "black")
colors = ggplot(data = dt_pred_rest_lsp_exp_seen, aes(
fig_freq_exp_seen =y = power, x = fct_rev(label), fill = tITI)) +
geom_bar(stat = "summary", fun = "mean", position = position_dodge(0.9),
width = 0.8) +
geom_point(position = position_jitterdodge(
jitter.width = 0.1, jitter.height = 0, seed = 2, dodge.width = 0.9),
alpha = 1, inherit.aes = TRUE, pch = 21,
color = "white") +
facet_wrap(~ seen) +
stat_summary(fun.data = "mean_se", geom = "errorbar",
position = position_dodge(0.9), width = 0, color = "black") +
xlab("Predicted frequency") + ylab("Power") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(0, 5)) +
scale_fill_manual(values = colors, name = "") +
theme(axis.ticks.x = element_line(color = "white"), axis.line.x = element_line(color = "white")) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_freq_exp_seen
We examine the effect of resting-state session (pre- vs. post-task rest) and sequence frequency (less frequent vs. more frequent) on the power in the high frequency range (predicted by our model) indicative of fast sequential reactivation:
lmerTest::lmer(
lme_power <-~ tITI * seen + (tITI + seen | id),
power data = subset(dt_pred_rest_lsp_exp_seen, index == "power_index_fast" &
stringr::str_detect(tITI, "Rest")),
control = lcctrl)
summary(lme_power)
anova(lme_power)
emmeans(lme_power, list(pairwise ~ tITI | seen))
emmeans_results = emmeans_results
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: power ~ tITI * seen + (tITI + seen | id)
## Data: subset(dt_pred_rest_lsp_exp_seen, index == "power_index_fast" &
## stringr::str_detect(tITI, "Rest"))
## Control: lcctrl
##
## REML criterion at convergence: -130
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.68077 -0.52070 0.01264 0.50310 1.59810
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 0.0653144 0.25557
## tITIRest Post S1 0.0575852 0.23997 -0.59
## seenMore frequent 0.0003235 0.01799 -1.00 0.51
## Residual 0.0033971 0.05828
## Number of obs: 128, groups: id, 32
##
## Fixed effects:
## Estimate Std. Error df t value
## (Intercept) 0.93509 0.04634 31.77642 20.180
## tITIRest Post S1 0.06523 0.04485 34.49637 1.454
## seenMore frequent -0.01262 0.01491 61.87659 -0.846
## tITIRest Post S1:seenMore frequent 0.01053 0.02061 62.00000 0.511
## Pr(>|t|)
## (Intercept) <2e-16 ***
## tITIRest Post S1 0.155
## seenMore frequent 0.401
## tITIRest Post S1:seenMore frequent 0.611
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) tITIPS snMrfr
## tITIRstPsS1 -0.597
## seenMrfrqnt -0.361 0.262
## tITIRPS1:Mf 0.111 -0.230 -0.691
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## tITI 0.0088584 0.0088584 1 31.000 2.6076 0.1165
## seen 0.0015829 0.0015829 1 52.988 0.4660 0.4978
## tITI:seen 0.0008864 0.0008864 1 62.000 0.2609 0.6113
## $`emmeans of tITI | seen`
## seen = Less frequent:
## tITI emmean SE df lower.CL upper.CL
## Rest Pre S1 0.935 0.0463 31.8 0.841 1.03
## Rest Post S1 1.000 0.0410 32.0 0.917 1.08
##
## seen = More frequent:
## tITI emmean SE df lower.CL upper.CL
## Rest Pre S1 0.922 0.0433 31.9 0.834 1.01
## Rest Post S1 0.998 0.0393 32.1 0.918 1.08
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of tITI | seen`
## seen = Less frequent:
## contrast estimate SE df t.ratio p.value
## Rest Pre S1 - Rest Post S1 -0.0652 0.0449 34.4 -1.454 0.1549
##
## seen = More frequent:
## contrast estimate SE df t.ratio p.value
## Rest Pre S1 - Rest Post S1 -0.0758 0.0449 34.4 -1.689 0.1003
##
## Degrees-of-freedom method: kenward-roger
lmerTest::lmer(
lme_power <-~ tITI + (seen | id),
power data = subset(dt_pred_rest_lsp_exp_seen, index == "power_index_fast" &
stringr::str_detect(tITI, "Rest")),
control = lcctrl)
summary(lme_power)
anova(lme_power)
emmeans(lme_power, list(pairwise ~ tITI))
emmeans_results = emmeans_results
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: power ~ tITI + (seen | id)
## Data: subset(dt_pred_rest_lsp_exp_seen, index == "power_index_fast" &
## stringr::str_detect(tITI, "Rest"))
## Control: lcctrl
##
## REML criterion at convergence: -52.1
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.9246 -0.6701 -0.1003 0.5636 2.1896
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 0.0382407 0.19555
## seenMore frequent 0.0002275 0.01508 -1.00
## Residual 0.0221629 0.14887
## Number of obs: 128, groups: id, 32
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 0.92784 0.03794 39.88504 24.453 < 2e-16 ***
## tITIRest Post S1 0.07049 0.02632 94.99805 2.679 0.00871 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## tITIRstPsS1 -0.347
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## tITI 0.15902 0.15902 1 94.998 7.1751 0.008712 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## $`emmeans of tITI`
## tITI emmean SE df lower.CL upper.CL
## Rest Pre S1 0.928 0.039 39.7 0.849 1.01
## Rest Post S1 0.998 0.039 39.7 0.919 1.08
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of tITI`
## contrast estimate SE df t.ratio p.value
## Rest Pre S1 - Rest Post S1 -0.0705 0.0263 63 -2.679 0.0094
##
## Degrees-of-freedom method: kenward-roger
lmerTest::lmer(
lme_power <-~ seen + (tITI | id),
power data = subset(dt_pred_rest_lsp_exp_seen, index == "power_index_fast" &
stringr::str_detect(tITI, "Rest")), control = lcctrl)
summary(lme_power)
anova(lme_power)
emmeans(lme_power, list(pairwise ~ seen))
emmeans_results = emmeans_results
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: power ~ seen + (tITI | id)
## Data: subset(dt_pred_rest_lsp_exp_seen, index == "power_index_fast" &
## stringr::str_detect(tITI, "Rest"))
## Control: lcctrl
##
## REML criterion at convergence: -134.7
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.69910 -0.48395 0.02828 0.46603 1.76489
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## id (Intercept) 0.061879 0.2488
## tITIRest Post S1 0.060529 0.2460 -0.60
## Residual 0.003517 0.0593
## Number of obs: 128, groups: id, 32
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 0.975077 0.035840 32.362281 27.207 <2e-16 ***
## seenMore frequent -0.007361 0.010483 62.999999 -0.702 0.485
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## seenMrfrqnt -0.146
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## seen 0.0017337 0.0017337 1 63 0.493 0.4852
## $`emmeans of seen`
## seen emmean SE df lower.CL upper.CL
## Less frequent 0.975 0.037 32.4 0.900 1.05
## More frequent 0.968 0.037 32.4 0.892 1.04
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
##
## $`pairwise differences of seen`
## contrast estimate SE df t.ratio p.value
## Less frequent - More frequent 0.00736 0.0105 63 0.702 0.4852
##
## Degrees-of-freedom method: kenward-roger
We compare the power in the fast frequency spectrum between pre- and post-task resting-state data:
%>%
dt_pred_rest_lsp_exp_seen # select resting state data and power in the fast frequency range:
filter(stringr::str_detect(tITI, "Rest") & index == "power_index_fast") %>%
group_by(index, seen) %>%
do(broom::tidy(t.test(formula = power ~ tITI, data = ., paired = TRUE))) %>%
setDT(.) %>%
.[, "p.value_adjust" := p.adjust(p.value, method = "fdr")] %>%
.[, "p.value_round" := round_pvalues(p.value)] %>%
.[, "p.value_adjust_round" := round_pvalues(p.value_adjust)] %>%
# check if the degrees-of-freedom are n - 1:
verify(parameter == 32 - 1) %>%
rmarkdown::paged_table(.)
2.9.7.1 Figure 6c
dt_pred_rest_lsp_exp_seen %>%
plot_data <- filter(tITI %in% c("Rest Pre S1", "Rest Post S1")) %>%
filter(index == "power_index_fast")
c(brewer.pal(n = 8, name = "RdBu")[6], hcl.colors(5, "Cividis")[c(1)])
colors = ggplot(data = plot_data, aes(
fig_freq_exp_post =y = power, x = tITI, fill = seen)) +
geom_bar(stat = "summary", fun = "mean", position = position_dodge(0.9),
width = 0.8) +
geom_point(position = position_jitterdodge(
jitter.width = 0.1, jitter.height = 0, seed = 2, dodge.width = 0.9),
alpha = 1, inherit.aes = TRUE, pch = 21,
color = "white") +
stat_summary(fun.data = "mean_se", geom = "errorbar",
position = position_dodge(0.9), width = 0, color = "black") +
xlab("Resting-state data") + ylab("Power at fast frequency (0.17)") +
#ggtitle("Predicted fast frequency (0.17 Hz)") +
theme(plot.title = element_text(hjust = 1)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE) +
scale_fill_manual(values = colors, name = "Sequences") +
theme(axis.ticks.x = element_line(color = "white"), axis.line.x = element_line(color = "white")) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
guides(fill = guide_legend(title.position = "top", title.hjust = 0.5)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
#theme(legend.title = element_text(size = 10),
# legend.text = element_text(size = 10))
fig_freq_exp_post
2.9.7.2 Source Data File Fig. 6c
%>%
dt_pred_rest_lsp_exp_seen filter(tITI %in% c("Rest Pre S1", "Rest Post S1")) %>%
filter(index == "power_index_fast") %>%
select(-classification, -index, -label, -num_seqs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_6c.csv"),
row.names = FALSE)
2.9.8 Figure 6
plot_grid(fig_freq_post, fig_freq_post_rel, fig_freq_exp_post, nrow = 1, ncol = 3,
labels = c("a", "b", "c"), rel_widths = c(3, 3, 3))
ggsave(filename = "highspeed_plot_decoding_resting_post.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 9, height = 3)
ggsave(filename = "wittkuhn_schuck_figure_6.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 9, height = 3)
2.9.9 Inserting sequence trials into rest
We create a function to sample sequence trials:
function(sequence, seq_size = 12, seq_num = 15) {
subseqsample =# initialize an empty array for the resulting indices:
rep(NA, length(sequence))
index =# calculate how much space would be allocated to gaps:
length(sequence) - seq_size * seq_num
total_gaps =# calculate random gaps
diff(sort(c(0, sample(1:(total_gaps + seq_num), seq_num), total_gaps + seq_num + 1)))
random_gaps =# we subtract 1 for each element of seq_num we added above:
random_gaps - 1
random_gaps =# make sure that the sum of random gaps matches the total gaps:
if (sum(random_gaps) != total_gaps) {warning("sum of gaps does not match!")}
# generate a random order of subsequences:
sample(1:seq_num, seq_num)
seq_order =# initialize the first starting point of potential sequences:
0
cstart =# loop through all subsequences:
for (cseq in seq(1, seq_num)) {
# select the first random gap:
random_gaps[cseq]
cgap =# take the starting point and add the current random gap:
cstart + cgap + 1 # +1 because gap can be 0
cstart =# calculate the current subsequence index:
seq(cstart, (cstart + seq_size - 1))
cidx =# insert the random sequence index into the index array:
seq_order[cseq]
index[cidx] =# define a new starting position for the next subsequence:
max(cidx)
cstart =
}# return the indices:
return(index)
}subseqsample(sequence = seq(1, 233))
## [1] NA NA 14 14 14 14 14 14 14 14 14 14 14 14 NA NA 5 5 5 5 5 5 5 5 5
## [26] 5 5 5 NA NA NA 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6
## [51] 6 6 6 6 6 8 8 8 8 8 8 8 8 8 8 8 8 NA NA 10 10 10 10 10 10
## [76] 10 10 10 10 10 10 4 4 4 4 4 4 4 4 4 4 4 4 NA NA NA NA NA 1 1
## [101] 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 NA 12 12
## [126] 12 12 12 12 12 12 12 12 12 12 NA NA NA 9 9 9 9 9 9 9 9 9 9 9 9
## [151] NA NA NA NA NA NA NA NA NA NA 11 11 11 11 11 11 11 11 11 11 11 11 NA NA NA
## [176] NA NA NA NA NA NA NA 2 2 2 2 2 2 2 2 2 2 2 2 NA NA NA NA NA NA
## [201] NA NA 15 15 15 15 15 15 15 15 15 15 15 15 NA NA NA NA 13 13 13 13 13 13 13
## [226] 13 13 13 13 13 NA NA NA
We randomly sample 15 sequences of 12 TRs each from resting state data and compare the mean frequency spectrum across those trials between resting state, fast and slow sequence trials.
dt_pred_conc_slope %>%
dt_pred_rest_freq = # we deselect post-task resting state data and assume on fixed ordering:
filter(!(tITI == "Rest Post S1") & sequence == 1) %>%
# filter for TRs in the forward and backward period only in sequence trials:
filter(!(tITI %in% c("32 ms", "2048 ms") & !(seq_tr %in% seq(2, 13)))) %>%
setDT(.) %>%
# select random chunks of TRs in the resting state data
.[tITI == "Rest Pre S1", by = .(classification, id), ":=" (trial_tITI = subseqsample(seq_tr))] %>%
# filter out all NA trials on resting state data
filter(!(is.na(trial_tITI) & tITI == "Rest Pre S1")) %>%
setDT(.) %>%
# assert that all the three conditions (two sequence trials and rest data) are present:
assert(in_set("32 ms", "2048 ms", "Rest Pre S1"), tITI) %>%
# calculate the frequency spectrum for every
.[, by = .(classification, id, tITI, trial_tITI), {
stats::spectrum(
frequency_spectrum =x = scale(slope), plot = FALSE, detrend = TRUE, demean = FALSE, spans = 5)
list(
num_trs = .N,
freq_bins = frequency_spectrum$freq,
spectrum = frequency_spectrum$spec
%>% verify(all(num_trs %in% c(12, 6))) %>%
)}] # normalize the frequencies per participant and trial to remove mean differences:
.[, by = .(classification, id, tITI, trial_tITI), ":=" (spectrum = spectrum/sum(spectrum))] %>%
# multiply the frequency levels by 1/TR:
transform(freq_bins = freq_bins * (1/1.25)) %>% setDT(.) %>%
# calculate the mean frequency spectrum per participant and condition:
.[, by = .(classification, id, tITI, freq_bins), .(
num_trials = .N,
mean_spec = mean(spectrum)
%>% verify(all(num_trials == 15)) %>%
)] # calculate the average frequency spectrum across all participants:
.[, by = .(classification, tITI, freq_bins), .(
num_subs = .N,
mean_spec = mean(mean_spec),
sem_upper = mean(mean_spec) + (sd(mean_spec)/sqrt(.N)),
sem_lower = mean(mean_spec) - (sd(mean_spec)/sqrt(.N))
%>%
)] verify(all(num_subs == 32))
We insert a number of events with probabilities scaled at different SNR levels:
dt_pred_all %>%
dt_pred_conc = # we add a consecutive counter that will be used to concatenate data:
.[, by = .(classification, id, tITI, class), ":=" (t = seq(1, .N))] %>%
# we will focus all further analyses on the one-versus-rest classifier:
filter(classification == "ovr") %>% setDT(.)
# create separate dataframes for resting state data and sequence trial data:
subset(dt_pred_conc, tITI == "rest_run-pre_ses-01")
dt_pred_rest_s1 = subset(dt_pred_conc, tITI %in% c("32 ms", "2048 ms"))
dt_pred_seq_cut = unique(dt_pred_seq_cut$tITI)
speed_levels = length(speed_levels)
num_speeds =# define the number of SNR levels and sequence inserts:
c(4/5, 1/2, 1/4, 1/8, 0)
snr_levels = c("4/5", "1/2", "1/4", "1/8", "0")
snr_levels_char = length(snr_levels)
num_snrs = 6
num_inserts = 12
insert_length =# get the number of TRs during resting state and warn if incorrect:
max(dt_pred_rest_s1$t)
num_rest_trs =if (num_rest_trs != 233) {warning("number of resting state TRs incorrect")}
max(dt_pred_seq_cut$trial_tITI)
num_seq_trials =if (num_seq_trials != 15) {warning("number of sequence trials incorrect")}
length(unique(dt_pred_rest_s1$id))
num_subs =if (num_subs != 32) {warning("number of participants incorrect")}
# create a vector of seeds to make computations reproducible:
#seeds = seq(1, num_snrs * num_inserts * num_subs * num_speeds)
list()
dt_pred_rest_insert <- 1
count =for (sub in unique(dt_pred_rest_s1$id)) {
# sample random sequence trials to be cut out depending on the number of inserts:
sort(sample(1:num_seq_trials, num_inserts, replace = FALSE))
cutout_trials =# sample random insert sequences depending on the number of inserts (no overlap):
subseqsample(sequence = seq(1, num_rest_trs), seq_size = insert_length, seq_num = num_inserts * 2)
rest_seq = sort(sapply(X = seq(1, num_inserts * 2), FUN = function(x){which(rest_seq == x)[1]}))
subsequence_starts = sort(sample(subsequence_starts, num_inserts, replace = FALSE))
insert_starts = sort(insert_starts + insert_length - 1)
insert_stops = sort(setdiff(subsequence_starts, insert_starts))
blend_starts = sort(blend_starts + insert_length - 1)
blend_stops =for (ninserts in seq(1, num_inserts)) {
0
snr_index =for (snr in snr_levels) {
snr_index + 1
snr_index =for (speed in speed_levels) {
# create a resting state data subset for the current settings:
dt_pred_rest_s1 %>%
dt_pred_rest_tmp = filter(id == sub) %>% setorder(classification, id, t, class) %>%
mutate(snr = snr) %>% mutate(insert = ninserts) %>% mutate(position_insert = position) %>%
mutate(speed = speed) %>% mutate(snr_char = snr_levels_char[snr_index]) %>%
mutate(prob_insert = probability) %>% mutate(prob_insert_blend = probability) %>%
mutate(insert_index = NA) %>% mutate(insert_start = NA) %>% setDT(.) %>%
verify(.[, by = .(classification, id, position), .(num_trs = .N)]$num_trs == num_rest_trs)
# loop through all events to cutout and insert fast sequences:
for (cevent in 1:ninserts) {
# create the sequence for sequence inserting:
seq(insert_starts[cevent], insert_stops[cevent])
insert_seq =# create the sequence for the blending rest trials:
seq(blend_starts[cevent], blend_stops[cevent])
blend_seq =# select the sequence from fast trials:
dt_pred_seq_cut %>%
dt_pred_seq_cutout = # filter for specififc participant and specific trial:
filter(id == sub & trial_tITI == cutout_trials[cevent]) %>%
# filter for specific speed and specific trs:
filter(tITI == speed & seq_tr %in% seq(2, insert_length + 1)) %>%
# sort by classification, id, timepoint and class (as rest data, see above):
setorder(classification, id, t, class) %>%
# multiply the probability with the current SNR level:
mutate(prob_snr = probability * snr)
# insert the snr-adjusted sequence trial probabilities:
$prob_insert[dt_pred_rest_tmp$t %in% insert_seq] = dt_pred_seq_cutout$prob_snr
dt_pred_rest_tmp# keep the position indices of the inserted sequence trials:
$position_insert[dt_pred_rest_tmp$t %in% insert_seq] = dt_pred_seq_cutout$position
dt_pred_rest_tmp# blend the snr-adjusted sequence trials with data from snr-adjusted res trials:
$prob_insert_blend[dt_pred_rest_tmp$t %in% insert_seq] = dt_pred_seq_cutout$prob_snr +
dt_pred_rest_tmp dt_pred_rest_tmp$probability[dt_pred_rest_tmp$t %in% blend_seq] * (1 - snr)
# create a new column with the (SNR-adjusted) sequence probabilities:
$prob_seq[dt_pred_rest_tmp$t %in% insert_seq] = dt_pred_seq_cutout$probability
dt_pred_rest_tmp$prob_seq_snr[dt_pred_rest_tmp$t %in% insert_seq] = dt_pred_seq_cutout$prob_snr
dt_pred_rest_tmp# include indices that indicate where data was inserted:
$insert_index[dt_pred_rest_tmp$t %in% insert_seq] = "inserted"
dt_pred_rest_tmp$insert_num[dt_pred_rest_tmp$t %in% insert_seq] = cevent
dt_pred_rest_tmp$insert_start[dt_pred_rest_tmp$t %in% min(insert_seq)] = 1
dt_pred_rest_tmp
} dt_pred_rest_tmp
dt_pred_rest_insert[[count]] = count + 1
count =
}
}
}
}# combine data in one datatable:
do.call("rbind", dt_pred_rest_insert)
dt_pred_rest_insert <-# calculate the differences in probability:
$prob_diff = dt_pred_rest_insert$probability - dt_pred_rest_insert$prob_insert_blend dt_pred_rest_insert
Plot differences between inserted sequence speeds at different SNR levels:
colorRampPalette(c('#4890F7', '#EB3C2D'), space = 'Lab')(5)
cols_order = subset(dt_pred_rest_insert, id == example_id) %>%
plot_data = filter(insert_index == "inserted") %>% filter(speed == "32 ms") %>%
filter(insert == 1) %>%
transform(snr_char = factor(as.factor(snr_char), levels = c("4/5", "1/2", "1/4", "1/8", "0"))) %>% setDT(.)
ggplot(data = plot_data, aes(
fig_prob_diff_snr =x = as.factor(t), y = prob_seq_snr, color = snr_char, group = snr_char)) +
geom_line() + geom_point(alpha = 0.3) +
facet_wrap(~ class) + ylim(c(0,1)) +
scale_color_manual(values = cols_order, name = "SNR level") +
ylab("Probability (%)") + xlab("Time (TRs)") +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE) +
theme(legend.position = c(1, 0), legend.justification = c(1, 0)) +
scale_x_discrete(labels = label_fill(seq(1, 12, 1), mod = 11),
breaks = seq(min(plot_data$t), max(plot_data$t), 1)) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_prob_diff_snr
Plot differences in probability between true and augmented rest data.
subset(dt_pred_rest_insert, id == example_id & speed == "32 ms") %>%
plot_data = filter(snr_char %in% c("4/5", "1/4") & insert == c(2, 6)) %>%
mutate(snr_char = paste("SNR level:", snr_char)) %>%
mutate(insert = ifelse(insert == 1, paste(insert, "insert"), paste(insert, "inserts")))
# create data frame to plot rectangles where trials were inserted:
plot_data %>%
plot_rectangles = filter(insert_start == 1) %>%
mutate(xmin = t) %>%
mutate(xmax = ifelse(speed == "32 ms", xmin + 11, xmin + 11)) %>%
mutate(ymin = -100, ymax = 100)
ggplot(data = plot_data, mapping = aes(
fig_inserts =x = as.numeric(t), y = as.numeric(prob_diff * 100), group = as.factor(position))) +
geom_hline(aes(yintercept = 0), color = "gray") +
geom_line(mapping = aes(color = as.factor(position))) +
facet_grid(vars(as.factor(insert)), vars(fct_rev(as.factor(snr_char)))) +
geom_rect(data = plot_rectangles, aes(
xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
alpha = 0.2, inherit.aes = FALSE, show.legend = FALSE, fill = "gray") +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
xlab("Time (TRs)") + ylab("Difference in probability (%)\nof sequence-free vs. -inserted rest") +
scale_colour_manual(name = "Serial position", values = color_events) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE,
xlim = c(0, 250), ylim = c(-100, 100)) +
guides(color = guide_legend(nrow = 1)) +
scale_x_continuous(labels = label_fill(seq(0, 250, 25), mod = 5),
breaks = seq(0, 250, 25)) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(panel.border = element_blank(), axis.line = element_line()) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_inserts
We calculate the slopes for each speed, snr and insert level.
Attention: This takes some time to run because a lot of linear models are estimated… About 13 minutes, OMG.
%>%
dt_pred_rest_insert verify(.[position != position_insert,]$insert_index == "inserted")
## pred_label stim tr seq_tr stim_tr trial run_study session
## 1: other 1 1 NA 1 run-pre_ses-01 ses-01
## 2: other 1 1 NA 1 run-pre_ses-01 ses-01
## 3: other 1 1 NA 1 run-pre_ses-01 ses-01
## 4: house 1 1 NA 1 run-pre_ses-01 ses-01
## 5: other 1 1 NA 1 run-pre_ses-01 ses-01
## ---
## 2236796: other 233 233 NA 1 run-pre_ses-01 ses-01
## 2236797: other 233 233 NA 1 run-pre_ses-01 ses-01
## 2236798: other 233 233 NA 1 run-pre_ses-01 ses-01
## 2236799: other 233 233 NA 1 run-pre_ses-01 ses-01
## 2236800: other 233 233 NA 1 run-pre_ses-01 ses-01
## tITI id test_set classifier mask class
## 1: rest_run-pre_ses-01 sub-05 rest cat union cat
## 2: rest_run-pre_ses-01 sub-05 rest chair union chair
## 3: rest_run-pre_ses-01 sub-05 rest face union face
## 4: rest_run-pre_ses-01 sub-05 rest house union house
## 5: rest_run-pre_ses-01 sub-05 rest shoe union shoe
## ---
## 2236796: rest_run-pre_ses-01 sub-39 rest cat union cat
## 2236797: rest_run-pre_ses-01 sub-39 rest chair union chair
## 2236798: rest_run-pre_ses-01 sub-39 rest face union face
## 2236799: rest_run-pre_ses-01 sub-39 rest house union house
## 2236800: rest_run-pre_ses-01 sub-39 rest shoe union shoe
## probability classification condition trial_tITI probability_norm
## 1: 9.102896e-06 ovr rest 2 3.948250e-07
## 2: 2.443620e-02 ovr rest 2 6.931213e-04
## 3: 1.498180e-04 ovr rest 2 7.819401e-06
## 4: 9.465221e-01 ovr rest 2 3.597524e-02
## 5: 1.363089e-04 ovr rest 2 6.181498e-06
## ---
## 2236796: 7.106316e-07 ovr rest 2 3.988884e-08
## 2236797: 4.465901e-01 ovr rest 2 1.992650e-02
## 2236798: 1.003260e-03 ovr rest 2 4.685165e-05
## 2236799: 1.633766e-04 ovr rest 2 5.921457e-06
## 2236800: 4.423494e-02 ovr rest 2 1.814782e-03
## probability_zscore probability_cum position t snr insert
## 1: -0.4442724 3.948250e-07 5 1 0.8 1
## 2: -0.4585512 6.931213e-04 3 1 0.8 1
## 3: -0.4102144 7.819401e-06 4 1 0.8 1
## 4: 3.2804418 3.597524e-02 2 1 0.8 1
## 5: -0.4589078 6.181498e-06 1 1 0.8 1
## ---
## 2236796: -0.4322017 1.000000e+00 3 233 0.0 6
## 2236797: 1.6172451 1.000000e+00 4 233 0.0 6
## 2236798: -0.4657577 1.000000e+00 1 233 0.0 6
## 2236799: -0.4777543 1.000000e+00 5 233 0.0 6
## 2236800: -0.2521398 1.000000e+00 2 233 0.0 6
## position_insert speed snr_char prob_insert prob_insert_blend
## 1: 5 32 ms 4/5 9.102896e-06 9.102896e-06
## 2: 3 32 ms 4/5 2.443620e-02 2.443620e-02
## 3: 4 32 ms 4/5 1.498180e-04 1.498180e-04
## 4: 2 32 ms 4/5 9.465221e-01 9.465221e-01
## 5: 1 32 ms 4/5 1.363089e-04 1.363089e-04
## ---
## 2236796: 2 2048 ms 0 0.000000e+00 9.089871e-03
## 2236797: 4 2048 ms 0 0.000000e+00 2.291008e-04
## 2236798: 1 2048 ms 0 0.000000e+00 5.754922e-03
## 2236799: 5 2048 ms 0 0.000000e+00 4.862898e-05
## 2236800: 3 2048 ms 0 0.000000e+00 3.918135e-01
## insert_index insert_start prob_seq prob_seq_snr insert_num
## 1: <NA> NA NA NA NA
## 2: <NA> NA NA NA NA
## 3: <NA> NA NA NA NA
## 4: <NA> NA NA NA NA
## 5: <NA> NA NA NA NA
## ---
## 2236796: inserted NA 9.808940e-07 0 6
## 2236797: inserted NA 1.296404e-03 0 6
## 2236798: inserted NA 1.477866e-04 0 6
## 2236799: inserted NA 9.650072e-01 0 6
## 2236800: inserted NA 1.884389e-01 0 6
## prob_diff
## 1: 0.0000000000
## 2: 0.0000000000
## 3: 0.0000000000
## 4: 0.0000000000
## 5: 0.0000000000
## ---
## 2236796: -0.0090891599
## 2236797: 0.4463610056
## 2236798: -0.0047516613
## 2236799: 0.0001147476
## 2236800: -0.3475785457
Sys.time()
start_time =# calculate the slope for each inserted and snr-adjusted rest data segment:
dt_pred_rest_insert %>% setDT(.) %>%
dt_pred_rest_insert_slope = # calculate the regression slopes for each id, trial, speed, snr, insert and TR:
.[, by = .(id, classification, speed, trial_tITI, insert, snr, snr_char, t, insert_num), .(
num_events = .N,
mean_position = mean(position),
mean_position_insert = mean(position_insert),
slope_rest = coef(lm(probability ~ position))[2] * (-1),
slope_insert = coef(lm(prob_insert_blend ~ position_insert))[2] * (-1),
sd_prob_rest = sd(probability),
sd_prob_insert = sd(prob_insert_blend)
# verify that the slope was calculated across five events with mean position of 3:
%>% verify(all(num_events == 5)) %>% verify(all(mean_position == 3)) %>%
)] verify(all(mean_position_insert == 3)) %>%
# sort the data table by speed, trial, number of inserts, snr level and time:
setorder(id, speed, trial_tITI, insert, snr, snr_char, t) %>%
# filter for one-versus-rest classification only:
filter(classification == "ovr") %>% setDT(.)
Sys.time()
end_time =print(end_time - start_time)
## Time difference of 15.81017 mins
Plot slopes with inserts:
c(hcl.colors(5, "Cividis")[c(5,1)], "gray")
colors = subset(dt_pred_rest_insert_slope, id == example_id) %>%
plot_rest = filter(snr_char %in% c("0", "4/5") & insert == c(6)) %>%
mutate(snr_char = paste("SNR level:", snr_char)) %>%
mutate(insert = ifelse(insert == 1, paste(insert, "insert"), paste(insert, "inserts")))
# create data frame to plot rectangles where trials were inserted:
plot_rest %>%
plot_rectangles = filter(!is.na(insert_num)) %>% setDT(.) %>%
.[, by = .(id, classification, speed, insert, snr, snr_char, insert_num), .(
xmin = min(t), xmax = max(t), ymin = -0.25, ymax = 0.25
)] plot_rest %>%
plot_inserts = filter(!is.na(insert_num)) %>% setDT(.)
# plot the
ggplot(data = plot_rest, mapping = aes(
fig_inserts_slopes =x = as.numeric(t), y = as.numeric(slope_rest), color = fct_rev(speed))) +
geom_hline(aes(yintercept = 0), color = "black") +
geom_line(aes(color = "rest")) +
facet_grid(vars(as.factor(insert)), vars(as.factor(snr_char))) +
#facet_wrap(~ speed, nrow = 2, ncol = 1) +
facet_grid(vars(fct_rev(as.factor(speed))), vars(fct_rev(as.factor(snr_char)))) +
geom_rect(data = plot_rectangles, aes(
xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = speed),
alpha = 0.2, inherit.aes = FALSE, show.legend = FALSE) +
geom_line(data = plot_inserts, aes(
y = slope_insert, color = speed, group = interaction(insert_num, speed))) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
xlab("Time (TRs)") + ylab("Regression slope") +
scale_colour_manual(name = "Speed", values = colors) +
scale_fill_manual(name = "Speed", values = colors) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, xlim = c(0, 250)) +
guides(color = guide_legend(nrow = 1)) +
scale_x_continuous(labels = label_fill(seq(0, 250, 25), mod = 5), breaks = seq(0, 250, 25)) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_inserts_slopes
# select variable: either slopes or standard deviation:
function(dt, variable) {
insert_stat_func = dt %>%
dt_pred_rest_insert_slope_stat = # calculate mean slope coefficients across all augmented rest TRs:
.[, by = .(id, classification, speed, insert, snr, snr_char), .(
num_trs = .N,
mean_slope_rest = mean(abs(slope_rest)),
mean_slope_insert = mean(abs(slope_insert)),
mean_sd_prob_rest = mean(sd_prob_rest),
mean_sd_prob_insert = mean(sd_prob_insert)
%>% verify(all(num_trs == num_rest_trs)) %>%
)] # calculate the difference between the inserted and sequence-free rest data:
mutate(mean_slope_diff = mean_slope_insert - mean_slope_rest) %>%
mutate(mean_sd_diff = mean_sd_prob_insert - mean_sd_prob_rest) %>% setDT(.) %>%
# concuct a t-test comparing sequence-free vs. sequence-including rests:
.[, by = .(classification, speed, insert, snr, snr_char), {
t.test(get(variable), mu = 0, alternative = "two.sided")
ttest_results =list(
num_subs = .N,
mean_variable_diff = mean(get(variable)),
pvalue = ttest_results$p.value,
tvalue = round(ttest_results$statistic, 2),
df = ttest_results$parameter,
sem_upper = mean(get(variable)) + (sd(get(variable))/sqrt(.N)),
sem_lower = mean(get(variable)) - (sd(get(variable))/sqrt(.N))
%>%
)}] verify(all(num_subs == 32)) %>%
verify(all((num_subs - df) == 1)) %>%
# adjust p-values for multiple comparisons:
# check if the number of comparisons matches expectations:
.[, by = .(classification, speed), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] verify(all(num_comp == 30, na.rm = TRUE)) %>%
# round the original p-values according to the apa standard:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# create new variable indicating significance below 0.05
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", "")) %>%
# log-transform the p-values:
mutate(pvalue_log = log(pvalue_adjust, base = 20)) %>%
# adjust the ordering of the snr levels:
transform(snr_char = factor(snr_char, levels = c("0", "1/8", "1/4", "1/2", "4/5"))) %>%
# set order:
setorder(classification, speed, insert, snr) %>% setDT(.)
return(dt_pred_rest_insert_slope_stat)
} insert_stat_func(dt = dt_pred_rest_insert_slope, variable = "mean_slope_diff")
snr_insert_stat_slope = insert_stat_func(dt = dt_pred_rest_insert_slope, variable = "mean_sd_diff")
snr_insert_stat_sd =%>% filter(pvalue_adjust < 0.05) snr_insert_stat_slope
## classification speed insert snr snr_char num_subs mean_variable_diff
## 1: ovr 2048 ms 1 0.250 1/4 32 -0.0006783505
## 2: ovr 2048 ms 1 0.500 1/2 32 -0.0005850498
## 3: ovr 2048 ms 2 0.125 1/8 32 -0.0012887714
## 4: ovr 2048 ms 2 0.250 1/4 32 -0.0015316188
## 5: ovr 2048 ms 2 0.500 1/2 32 -0.0015767316
## 6: ovr 2048 ms 3 0.125 1/8 32 -0.0015243604
## 7: ovr 2048 ms 3 0.250 1/4 32 -0.0019845733
## 8: ovr 2048 ms 3 0.500 1/2 32 -0.0022700075
## 9: ovr 2048 ms 4 0.125 1/8 32 -0.0018234399
## 10: ovr 2048 ms 4 0.250 1/4 32 -0.0025182348
## 11: ovr 2048 ms 4 0.500 1/2 32 -0.0030412334
## 12: ovr 2048 ms 4 0.800 4/5 32 -0.0017798178
## 13: ovr 2048 ms 5 0.125 1/8 32 -0.0023494214
## 14: ovr 2048 ms 5 0.250 1/4 32 -0.0032929623
## 15: ovr 2048 ms 5 0.500 1/2 32 -0.0041300272
## 16: ovr 2048 ms 5 0.800 4/5 32 -0.0026710542
## 17: ovr 2048 ms 6 0.125 1/8 32 -0.0028749268
## 18: ovr 2048 ms 6 0.250 1/4 32 -0.0040228464
## 19: ovr 2048 ms 6 0.500 1/2 32 -0.0050540671
## 20: ovr 2048 ms 6 0.800 4/5 32 -0.0033655876
## 21: ovr 32 ms 1 0.500 1/2 32 -0.0007120565
## 22: ovr 32 ms 1 0.800 4/5 32 -0.0006295727
## 23: ovr 32 ms 2 0.250 1/4 32 -0.0010584563
## 24: ovr 32 ms 2 0.500 1/2 32 -0.0015186540
## 25: ovr 32 ms 2 0.800 4/5 32 -0.0010774877
## 26: ovr 32 ms 3 0.250 1/4 32 -0.0015133809
## 27: ovr 32 ms 3 0.500 1/2 32 -0.0023493493
## 28: ovr 32 ms 3 0.800 4/5 32 -0.0017277111
## 29: ovr 32 ms 4 0.250 1/4 32 -0.0021098618
## 30: ovr 32 ms 4 0.500 1/2 32 -0.0032714726
## 31: ovr 32 ms 4 0.800 4/5 32 -0.0024898273
## 32: ovr 32 ms 5 0.125 1/8 32 -0.0016698843
## 33: ovr 32 ms 5 0.250 1/4 32 -0.0030286574
## 34: ovr 32 ms 5 0.500 1/2 32 -0.0045963901
## 35: ovr 32 ms 5 0.800 4/5 32 -0.0037752136
## 36: ovr 32 ms 6 0.125 1/8 32 -0.0021160060
## 37: ovr 32 ms 6 0.250 1/4 32 -0.0037482496
## 38: ovr 32 ms 6 0.500 1/2 32 -0.0056558378
## 39: ovr 32 ms 6 0.800 4/5 32 -0.0047157062
## classification speed insert snr snr_char num_subs mean_variable_diff
## pvalue tvalue df sem_upper sem_lower num_comp pvalue_adjust
## 1: 1.478176e-02 -2.58 31 -0.0004155991 -0.0009411019 30 2.463626e-02
## 2: 2.292923e-02 -2.39 31 -0.0003406151 -0.0008294845 30 3.439385e-02
## 3: 7.930664e-03 -2.84 31 -0.0008347125 -0.0017428303 30 1.486999e-02
## 4: 1.702289e-03 -3.44 31 -0.0010858070 -0.0019774306 30 3.928359e-03
## 5: 1.104530e-03 -3.60 31 -0.0011383525 -0.0020151108 30 2.975730e-03
## 6: 3.320638e-03 -3.18 31 -0.0010452262 -0.0020034945 30 6.641276e-03
## 7: 2.107488e-04 -4.20 31 -0.0015117028 -0.0024574437 30 7.903081e-04
## 8: 7.514246e-05 -4.56 31 -0.0017723750 -0.0027676400 30 3.757123e-04
## 9: 9.618887e-03 -2.76 31 -0.0011627596 -0.0024841202 30 1.697451e-02
## 10: 4.244833e-04 -3.95 31 -0.0018800452 -0.0031564244 30 1.414944e-03
## 11: 4.352357e-05 -4.75 31 -0.0024014025 -0.0036810644 30 2.611414e-04
## 12: 2.228288e-02 -2.41 31 -0.0010400625 -0.0025195732 30 3.439385e-02
## 13: 6.690586e-04 -3.78 31 -0.0017280208 -0.0029708220 30 2.007176e-03
## 14: 1.003238e-05 -5.27 31 -0.0026675704 -0.0039183541 30 7.524285e-05
## 15: 1.482092e-06 -5.93 31 -0.0034339971 -0.0048260574 30 1.482092e-05
## 16: 3.183047e-03 -3.20 31 -0.0018357737 -0.0035063347 30 6.641276e-03
## 17: 1.843268e-04 -4.24 31 -0.0021976012 -0.0035522523 30 7.899718e-04
## 18: 1.353303e-06 -5.97 31 -0.0033485066 -0.0046971863 30 1.482092e-05
## 19: 1.700336e-07 -6.70 31 -0.0042998034 -0.0058083308 30 5.101009e-06
## 20: 1.190292e-03 -3.57 31 -0.0024225906 -0.0043085846 30 2.975730e-03
## 21: 1.432149e-02 -2.60 31 -0.0004376641 -0.0009864490 30 2.864298e-02
## 22: 2.436143e-02 -2.37 31 -0.0003635874 -0.0008955580 30 4.299076e-02
## 23: 2.852956e-02 -2.30 31 -0.0005976709 -0.0015192416 30 4.693891e-02
## 24: 1.918252e-03 -3.39 31 -0.0010707586 -0.0019665494 30 5.231596e-03
## 25: 1.792355e-02 -2.50 31 -0.0006464789 -0.0015084965 30 3.360666e-02
## 26: 1.025117e-02 -2.73 31 -0.0009598135 -0.0020669484 30 2.365655e-02
## 27: 1.229776e-04 -4.39 31 -0.0018139422 -0.0028847564 30 5.270468e-04
## 28: 1.243207e-03 -3.55 31 -0.0012414282 -0.0022139941 30 3.729620e-03
## 29: 2.160017e-03 -3.35 31 -0.0014792606 -0.0027404630 30 5.400044e-03
## 30: 1.122824e-05 -5.23 31 -0.0026454949 -0.0038974502 30 8.421178e-05
## 31: 2.282997e-04 -4.17 31 -0.0018925106 -0.0030871439 30 7.880881e-04
## 32: 2.972798e-02 -2.28 31 -0.0009370586 -0.0024027101 30 4.693891e-02
## 33: 2.364264e-04 -4.16 31 -0.0022998922 -0.0037574227 30 7.880881e-04
## 34: 1.224176e-06 -6.00 31 -0.0038304320 -0.0053623483 30 1.836264e-05
## 35: 2.204612e-05 -4.99 31 -0.0030187922 -0.0045316350 30 1.322767e-04
## 36: 1.382206e-02 -2.61 31 -0.0013052532 -0.0029267589 30 2.864298e-02
## 37: 3.885401e-05 -4.79 31 -0.0029662079 -0.0045302913 30 1.942701e-04
## 38: 8.904749e-08 -6.93 31 -0.0048400824 -0.0064715932 30 2.671425e-06
## 39: 5.570983e-06 -5.47 31 -0.0038536909 -0.0055777215 30 5.570983e-05
## pvalue tvalue df sem_upper sem_lower num_comp pvalue_adjust
## pvalue_round pvalue_adjust_round significance pvalue_log
## 1: p = .01 p = .02 * -1.236271
## 2: p = .02 p = .03 * -1.124893
## 3: p = .008 p = .01 * -1.404802
## 4: p = .002 p = .004 * -1.849142
## 5: p = .001 p = .003 * -1.941851
## 6: p = .003 p = .007 * -1.673865
## 7: p < .001 p < .001 * -2.384421
## 8: p < .001 p < .001 * -2.632641
## 9: p = .01 p = .02 * -1.360616
## 10: p < .001 p = .001 * -2.190004
## 11: p < .001 p < .001 * -2.754067
## 12: p = .02 p = .03 * -1.124893
## 13: p < .001 p = .002 * -2.073292
## 14: p < .001 p < .001 * -3.169439
## 15: p < .001 p < .001 * -3.711770
## 16: p = .003 p = .007 * -1.673865
## 17: p < .001 p < .001 * -2.384563
## 18: p < .001 p < .001 * -3.711770
## 19: p < .001 p < .001 * -4.067811
## 20: p = .001 p = .003 * -1.941851
## 21: p = .01 p = .03 * -1.185969
## 22: p = .02 p = .04 * -1.050418
## 23: p = .03 p = .05 * -1.021089
## 24: p = .002 p = .005 * -1.753507
## 25: p = .02 p = .03 * -1.132622
## 26: p = .01 p = .02 * -1.249816
## 27: p < .001 p < .001 * -2.519658
## 28: p = .001 p = .004 * -1.866472
## 29: p = .002 p = .005 * -1.742929
## 30: p < .001 p < .001 * -3.131847
## 31: p < .001 p < .001 * -2.385360
## 32: p = .03 p = .05 * -1.021089
## 33: p < .001 p < .001 * -2.385360
## 34: p < .001 p < .001 * -3.640243
## 35: p < .001 p < .001 * -2.981112
## 36: p = .01 p = .03 * -1.185969
## 37: p < .001 p < .001 * -2.852812
## 38: p < .001 p < .001 * -4.283727
## 39: p < .001 p < .001 * -3.269769
## pvalue_round pvalue_adjust_round significance pvalue_log
%>% filter(pvalue_adjust < 0.05) snr_insert_stat_sd
## classification speed insert snr snr_char num_subs mean_variable_diff
## 1: ovr 2048 ms 1 0.125 1/8 32 -0.001588456
## 2: ovr 2048 ms 1 0.250 1/4 32 -0.002161338
## 3: ovr 2048 ms 1 0.500 1/2 32 -0.002258520
## 4: ovr 2048 ms 2 0.125 1/8 32 -0.003392104
## 5: ovr 2048 ms 2 0.250 1/4 32 -0.004782021
## 6: ovr 2048 ms 2 0.500 1/2 32 -0.005477527
## 7: ovr 2048 ms 2 0.800 4/5 32 -0.002757742
## 8: ovr 2048 ms 3 0.125 1/8 32 -0.004351740
## 9: ovr 2048 ms 3 0.250 1/4 32 -0.006663250
## 10: ovr 2048 ms 3 0.500 1/2 32 -0.008337364
## 11: ovr 2048 ms 3 0.800 4/5 32 -0.005147277
## 12: ovr 2048 ms 4 0.125 1/8 32 -0.005312833
## 13: ovr 2048 ms 4 0.250 1/4 32 -0.008638720
## 14: ovr 2048 ms 4 0.500 1/2 32 -0.011345572
## 15: ovr 2048 ms 4 0.800 4/5 32 -0.007497486
## 16: ovr 2048 ms 5 0.125 1/8 32 -0.006762108
## 17: ovr 2048 ms 5 0.250 1/4 32 -0.010915281
## 18: ovr 2048 ms 5 0.500 1/2 32 -0.014435764
## 19: ovr 2048 ms 5 0.800 4/5 32 -0.010009554
## 20: ovr 2048 ms 6 0.125 1/8 32 -0.008342938
## 21: ovr 2048 ms 6 0.250 1/4 32 -0.013434359
## 22: ovr 2048 ms 6 0.500 1/2 32 -0.017926167
## 23: ovr 2048 ms 6 0.800 4/5 32 -0.012992737
## 24: ovr 32 ms 1 0.125 1/8 32 -0.001693297
## 25: ovr 32 ms 1 0.250 1/4 32 -0.002389709
## 26: ovr 32 ms 1 0.500 1/2 32 -0.002896940
## 27: ovr 32 ms 1 0.800 4/5 32 -0.002068804
## 28: ovr 32 ms 2 0.125 1/8 32 -0.003393000
## 29: ovr 32 ms 2 0.250 1/4 32 -0.004841657
## 30: ovr 32 ms 2 0.500 1/2 32 -0.005886753
## 31: ovr 32 ms 2 0.800 4/5 32 -0.003974918
## 32: ovr 32 ms 3 0.125 1/8 32 -0.004377856
## 33: ovr 32 ms 3 0.250 1/4 32 -0.006749073
## 34: ovr 32 ms 3 0.500 1/2 32 -0.008711215
## 35: ovr 32 ms 3 0.800 4/5 32 -0.006138349
## 36: ovr 32 ms 4 0.125 1/8 32 -0.005385562
## 37: ovr 32 ms 4 0.250 1/4 32 -0.008867752
## 38: ovr 32 ms 4 0.500 1/2 32 -0.012150993
## 39: ovr 32 ms 4 0.800 4/5 32 -0.009346154
## 40: ovr 32 ms 5 0.125 1/8 32 -0.006948115
## 41: ovr 32 ms 5 0.250 1/4 32 -0.011398804
## 42: ovr 32 ms 5 0.500 1/2 32 -0.015812024
## 43: ovr 32 ms 5 0.800 4/5 32 -0.012707678
## 44: ovr 32 ms 6 0.125 1/8 32 -0.008484621
## 45: ovr 32 ms 6 0.250 1/4 32 -0.013894101
## 46: ovr 32 ms 6 0.500 1/2 32 -0.019429020
## 47: ovr 32 ms 6 0.800 4/5 32 -0.016059415
## classification speed insert snr snr_char num_subs mean_variable_diff
## pvalue tvalue df sem_upper sem_lower num_comp pvalue_adjust
## 1: 3.368521e-02 -2.22 31 -0.0008737121 -0.002303200 30 4.393723e-02
## 2: 3.367757e-03 -3.18 31 -0.0014808274 -0.002841848 30 4.811081e-03
## 3: 1.303357e-03 -3.54 31 -0.0016196782 -0.002897363 30 2.300043e-03
## 4: 1.686447e-03 -3.44 31 -0.0024057638 -0.004378444 30 2.810746e-03
## 5: 2.997079e-05 -4.88 31 -0.0038028331 -0.005761210 30 7.492698e-05
## 6: 8.392514e-06 -5.33 31 -0.0044493967 -0.006505657 30 2.797505e-05
## 7: 2.341071e-02 -2.38 31 -0.0016011629 -0.003914320 30 3.192370e-02
## 8: 2.496835e-03 -3.29 31 -0.0030293279 -0.005674153 30 3.768319e-03
## 9: 1.830319e-05 -5.06 31 -0.0053453091 -0.007981192 30 4.991779e-05
## 10: 1.327036e-06 -5.97 31 -0.0069414033 -0.009733325 30 6.635179e-06
## 11: 2.512213e-03 -3.29 31 -0.0035820032 -0.006712550 30 3.768319e-03
## 12: 1.248916e-03 -3.55 31 -0.0038167624 -0.006808904 30 2.300043e-03
## 13: 2.603467e-06 -5.74 31 -0.0071327418 -0.010144698 30 1.115771e-05
## 14: 1.261535e-07 -6.81 31 -0.0096790083 -0.013012135 30 7.569209e-07
## 15: 6.513457e-04 -3.79 31 -0.0055195870 -0.009475384 30 1.302691e-03
## 16: 5.672893e-05 -4.66 31 -0.0053111141 -0.008213102 30 1.309129e-04
## 17: 3.308146e-08 -7.29 31 -0.0094186463 -0.012411917 30 2.481110e-07
## 18: 3.382055e-09 -8.14 31 -0.0126631113 -0.016208416 30 3.382055e-08
## 19: 7.890923e-05 -4.54 31 -0.0078069469 -0.012212161 30 1.690912e-04
## 20: 1.102961e-05 -5.23 31 -0.0067484624 -0.009937413 30 3.308882e-05
## 21: 1.826481e-09 -8.38 31 -0.0118310589 -0.015037659 30 2.739721e-08
## 22: 1.093311e-10 -9.49 31 -0.0160378511 -0.019814484 30 3.279932e-09
## 23: 5.540153e-06 -5.47 31 -0.0106185485 -0.015366926 30 2.077557e-05
## 24: 2.638296e-02 -2.33 31 -0.0009671234 -0.002419472 30 3.297870e-02
## 25: 1.852613e-03 -3.40 31 -0.0016876300 -0.003091788 30 2.646591e-03
## 26: 1.890219e-04 -4.24 31 -0.0022129877 -0.003580893 30 3.335680e-04
## 27: 7.136124e-03 -2.88 31 -0.0013506742 -0.002786933 30 9.307988e-03
## 28: 1.588893e-03 -3.46 31 -0.0024127652 -0.004373235 30 2.383339e-03
## 29: 2.058852e-05 -5.01 31 -0.0038761749 -0.005807139 30 4.411826e-05
## 30: 1.659514e-06 -5.89 31 -0.0048879858 -0.006885520 30 4.705985e-06
## 31: 6.730291e-04 -3.78 31 -0.0029229878 -0.005026848 30 1.121715e-03
## 32: 2.410296e-03 -3.30 31 -0.0030529137 -0.005702799 30 3.286767e-03
## 33: 1.437163e-05 -5.14 31 -0.0054360574 -0.008062088 30 3.316529e-05
## 34: 3.072465e-07 -6.49 31 -0.0073688533 -0.010053576 30 1.152175e-06
## 35: 1.220726e-04 -4.39 31 -0.0047402761 -0.007536422 30 2.288861e-04
## 36: 1.147917e-03 -3.58 31 -0.0038822514 -0.006888873 30 1.812500e-03
## 37: 1.451253e-06 -5.94 31 -0.0073751333 -0.010360370 30 4.705985e-06
## 38: 6.148641e-09 -7.92 31 -0.0106162964 -0.013685689 30 4.611481e-08
## 39: 1.725528e-06 -5.88 31 -0.0077567640 -0.010935545 30 4.705985e-06
## 40: 4.508704e-05 -4.74 31 -0.0054825232 -0.008413707 30 9.017408e-05
## 41: 1.438553e-08 -7.60 31 -0.0098989704 -0.012898638 30 8.631316e-08
## 42: 1.637226e-10 -9.33 31 -0.0141171669 -0.017506881 30 2.455840e-09
## 43: 1.962336e-07 -6.65 31 -0.0107965702 -0.014618785 30 8.410011e-07
## 44: 9.078517e-06 -5.30 31 -0.0068838300 -0.010085413 30 2.269629e-05
## 45: 9.410556e-10 -8.64 31 -0.0122852192 -0.015502982 30 9.410556e-09
## 46: 1.187518e-11 -10.42 31 -0.0175645175 -0.021293523 30 3.562555e-10
## 47: 3.197629e-08 -7.31 31 -0.0138611988 -0.018257631 30 1.598815e-07
## pvalue tvalue df sem_upper sem_lower num_comp pvalue_adjust
## pvalue_round pvalue_adjust_round significance pvalue_log
## 1: p = .03 p = .04 * -1.043148
## 2: p = .003 p = .005 * -1.781479
## 3: p = .001 p = .002 * -2.027827
## 4: p = .002 p = .003 * -1.960891
## 5: p < .001 p < .001 * -3.170843
## 6: p < .001 p < .001 * -3.499711
## 7: p = .02 p = .03 * -1.149771
## 8: p = .002 p = .004 * -1.863026
## 9: p < .001 p < .001 * -3.306415
## 10: p < .001 p < .001 * -3.980037
## 11: p = .003 p = .004 * -1.863026
## 12: p = .001 p = .002 * -2.027827
## 13: p < .001 p < .001 * -3.806542
## 14: p < .001 p < .001 * -4.704695
## 15: p < .001 p = .001 * -2.217596
## 16: p < .001 p < .001 * -2.984572
## 17: p < .001 p < .001 * -5.077019
## 18: p < .001 p < .001 * -5.742234
## 19: p < .001 p < .001 * -2.899148
## 20: p < .001 p < .001 * -3.443671
## 21: p < .001 p < .001 * -5.812544
## 22: p < .001 p < .001 * -6.521091
## 23: p < .001 p < .001 * -3.599031
## 24: p = .03 p = .03 * -1.138918
## 25: p = .002 p = .003 * -1.980979
## 26: p < .001 p < .001 * -2.672356
## 27: p = .007 p = .009 * -1.561182
## 28: p = .002 p = .002 * -2.015952
## 29: p < .001 p < .001 * -3.347641
## 30: p < .001 p < .001 * -4.094717
## 31: p < .001 p = .001 * -2.267525
## 32: p = .002 p = .003 * -1.908666
## 33: p < .001 p < .001 * -3.442900
## 34: p < .001 p < .001 * -4.564446
## 35: p < .001 p < .001 * -2.798076
## 36: p = .001 p = .002 * -2.107347
## 37: p < .001 p < .001 * -4.094717
## 38: p < .001 p < .001 * -5.638732
## 39: p < .001 p < .001 * -4.094717
## 40: p < .001 p < .001 * -3.109012
## 41: p < .001 p < .001 * -5.429485
## 42: p < .001 p < .001 * -6.617680
## 43: p < .001 p < .001 * -4.669534
## 44: p < .001 p < .001 * -3.569514
## 45: p < .001 p < .001 * -6.169254
## 46: p < .001 p < .001 * -7.262122
## 47: p < .001 p < .001 * -5.223709
## pvalue_round pvalue_adjust_round significance pvalue_log
2.9.9.1 Figure 5f / 5g
Plot average slope regression coefficients and p-values:
rev(colorRampPalette(c('#4890F7', '#EB3C2D'), space = 'Lab')(5))
cols_order = function(dt, variable) {
plot_snr_insert =if (variable == "pvalue_log") {
seq(-10, 0, 1)
yrange = "p-value\n(base-20 log-transformed)"
ylabel = -1
yintercept = 2
mod =$sem_upper = 10; dt$sem_lower = 10;
dt
}else if (variable == "mean_slope_diff") {
"mean_variable_diff"
variable = seq(-0.007, 0.003, 0.001)
yrange = 2
mod = "Regression slope\n(relative to pre-task rest)"
ylabel = 0
yintercept =
}else if (variable == "mean_sd_diff") {
"mean_variable_diff"
variable = seq(-0.03, 0.01, 0.01)
yrange = 1
mod = "SD of probability\n(relative to pre-task rest)"
ylabel = 0
yintercept =
}ggplot(data = dt, mapping = aes(
x = as.factor(insert), y = as.numeric(get(variable)), group = as.factor(snr_char),
color = as.factor(snr_char), fill = as.factor(snr_char))) +
facet_wrap(~ fct_rev(as.factor(speed))) +
geom_hline(aes(yintercept = yintercept), color = "black", linetype = "dashed") +
geom_ribbon(aes(ymin = sem_lower, ymax = sem_upper), alpha = 0.5, color = NA) +
geom_line() + geom_point(pch = 15) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
xlab("Number of inserted sequence trials") + ylab(ylabel) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(min(yrange), max(yrange))) +
guides(color = guide_legend(nrow = 1)) +
scale_color_manual(values = cols_order, name = "SNR level") +
scale_fill_manual(values = cols_order, name = "SNR level") +
scale_y_continuous(labels = label_fill(yrange, mod = mod), breaks = yrange) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
} plot_snr_insert(dt = snr_insert_stat_slope, variable = "pvalue_log")
fig_snr_pmat_slope = plot_snr_insert(dt = snr_insert_stat_slope, variable = "mean_slope_diff")
fig_snr_data_slope = plot_snr_insert(dt = snr_insert_stat_sd, variable = "pvalue_log")
fig_snr_pmat_sd = plot_snr_insert(dt = snr_insert_stat_sd, variable = "mean_sd_diff")
fig_snr_data_sd = fig_snr_pmat_slope; fig_snr_data_slope; fig_snr_pmat_sd; fig_snr_data_sd;
2.9.9.2 Source Data File Fig. 5f
%>%
snr_insert_stat_sd select(-num_subs, -classification, -pvalue, -tvalue, -df, -significance,
-num_comp, -pvalue_adjust, -pvalue_round, -pvalue_adjust_round) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5f.csv"),
row.names = FALSE)
2.9.9.3 Source Data File Fig. 5g
%>%
snr_insert_stat_sd select(-num_subs, -classification, -pvalue, -tvalue, -df, -significance,
-num_comp, -pvalue_adjust, -pvalue_round, -pvalue_adjust_round) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5g.csv"),
row.names = FALSE)
2.9.10 Frequency spectra (inserted)
2.9.10.1 Frequency spectra of augmented rest
We calculate the frequency spectra for sequence-free and sequence-inserted (fast and slow sequences) rest:
1
baseline =# get frequency spectrum of fast and slow sequence data:
dt_pred_rest_insert_slope %>%
dt_pred_rest_insert_freq = # calculate the lomb scargle frequency spectra for every snr and insert level
.[, by = .(classification, id, speed, insert, snr, snr_char), {
lsp(x = slope_insert, times = 1:.N, from = 0, to = 0.3, plot = FALSE, ofac = 2)
frequency_spectrum =list(num_trs = .N, freq_bins = frequency_spectrum$scanned, power = frequency_spectrum$power,
filter_width = round(0.05/mean(diff(frequency_spectrum$scanned)))
%>% verify(all(num_trs == 233)) %>% verify(length(unique(filter_width)) == 1) %>%
)}] # smooth the frequency spectra:
.[, by = .(classification, id, speed, insert, snr, snr_char), ":=" (
power_smooth = stats::filter(power, rep(1/unique(filter_width), unique(filter_width))),
freq_bins_smooth = stats::filter(freq_bins, rep(1/unique(filter_width), unique(filter_width)))
%>%
)] # add a counter for the frequency spectrum
.[, by = .(classification, id, speed, snr, snr_char, insert), ":=" (bin = 1:.N)] %>%
# verify that the number of frequency bins is the same for all participants, snr, and insert levels:
verify(length(unique(.[, by = .(classification, id, speed, insert, snr), .(
num_bins = length(unique(bin)))]$num_bins)) == 1) %>%
# calculate power of every snr level relative to the snr 0 level:
.[, by = .(classification, id, speed, insert), ":=" (
power_smooth_rel = as.vector(sapply(X = sort(unique(snr)), FUN = function(x){
== x]/power_smooth[snr == 0]}) - baseline)
power_smooth[snr
)]
# calculate the mean smoothed frequency for every snr and insert level across frequency bins:
dt_pred_rest_insert_freq %>%
dt_pred_rest_insert_freq_mean = .[, by = .(classification, speed, snr, snr_char, insert, freq_bins_smooth, bin), .(
num_subs = .N,
mean_power_smooth_rel = mean(power_smooth_rel),
sem_upper = mean(power_smooth_rel) + (sd(power_smooth_rel)/sqrt(.N)),
sem_lower = mean(power_smooth_rel) - (sd(power_smooth_rel)/sqrt(.N))
%>% verify(all(num_subs == 32)) )]
2.9.10.2 Figure 5h
rev(colorRampPalette(c('#4890F7', '#EB3C2D'), space = 'Lab')(5))
colors_snr = dt_pred_rest_insert_freq_mean %>%
plot_data = filter(!is.na(mean_power_smooth_rel)) %>%
filter(insert %in% c(2, 6)) %>%
mutate(insert = ifelse(insert == 1, paste(insert, "insert"), paste(insert, "inserts"))) %>%
# adjust the ordering of the snr levels:
transform(snr_char = factor(snr_char, levels = c("0", "1/8", "1/4", "1/2", "4/5"))) %>%
setDT(.)
0.01
freq_range = length(unique(plot_data$insert))
num_plot_inserts = length(unique(plot_data$speed))
num_speed_inserts =
frequency_expectation[1:3,]
frequency_expectation <- frequency_expectation[1:3,] %>%
frequency_expectation_snr = mutate(colors = c(hcl.colors(5, "Cividis")[c(1)], "gold", "gray")) %>%
.[rep(seq_len(nrow(.)), each = num_plot_inserts * num_speed_inserts), ] %>%
mutate(xmax = xintercept + freq_range, xmin = xintercept - freq_range) %>%
mutate(speed = rep(unique(plot_data$speed), num_plot_inserts * nrow(frequency_expectation))) %>%
mutate(insert = rep(rep(unique(plot_data$insert), each = 2), nrow(frequency_expectation))) %>%
transform(xintercept = ifelse(colors == "gray", 0.03, xintercept)) %>%
filter(colors != "gray")
ggplot(data = plot_data, aes(
fig_freq_lsp_insert =color = as.factor(snr_char), y = as.numeric(mean_power_smooth_rel), x = as.numeric(freq_bins_smooth))) +
geom_rect(data = frequency_expectation_snr, aes(
xmin = xmin, xmax = xmax, ymin = -0.2, ymax = 0.3),
alpha = 0.1, inherit.aes = FALSE, show.legend = FALSE, fill = frequency_expectation_snr$colors) +
#geom_vline(data = frequency_expectation_snr, aes(xintercept = xintercept),
# linetype = "dashed", color = frequency_expectation_snr$colors) +
geom_line() +
facet_grid(vars(insert), vars(fct_rev(as.factor(speed)))) +
geom_ribbon(aes(fill = snr_char, ymin = sem_lower, ymax = sem_upper),
alpha = 0.3, color = NA) +
#geom_text(data = frequency_expectation_snr, aes(
# x = xintercept + 0.03, y = -0.15, label = paste(round(xintercept, 2), "Hz")),
# color = frequency_expectation_snr$colors, size = 3) +
xlab("Frequency") + ylab("Power\n(relative to pre-task rest)") +
scale_color_manual(name = "SNR level", values = colors_snr, guide = FALSE) +
scale_fill_manual(name = "SNR level", values = colors_snr, guide = FALSE) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(0, 0, 0, 0),
legend.box.margin = margin(t = 0, r = 0, b = -5, l = 0)) +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE,
xlim = c(0, 0.3), ylim = c(-0.2, 0.3)) +
#guides(color = guide_legend(nrow = 1)) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_freq_lsp_insert
2.9.10.3 Source Data Fig. 5h
%>%
plot_data select(-classification, -bin, -num_subs) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5h.csv"),
row.names = FALSE)
2.9.10.4 Expected frequency ranges
dt_pred_rest_insert_freq %>%
dt_pred_rest_lsp_insert_exp = .[, by = .(classification, id, speed, snr, snr_char, insert), {
which.min(abs(freq_bins_smooth - delta_freq_fast + freq_range))
fast_min = which.min(abs(freq_bins_smooth - delta_freq_fast - freq_range))
fast_max = which.min(abs(freq_bins_smooth - delta_freq_slow + freq_range))
slow_min = which.min(abs(freq_bins_smooth - delta_freq_slow - freq_range))
slow_max = which.min(abs(freq_bins_smooth - 0.03 + freq_range))
rest_min = which.min(abs(freq_bins_smooth - 0.03 - freq_range))
rest_max =list(
fast_min = freq_bins_smooth[fast_min], fast_max = freq_bins_smooth[fast_max],
slow_min = freq_bins_smooth[slow_min], slow_max = freq_bins_smooth[slow_max],
power_index_fast = mean(power_smooth_rel[fast_min:fast_max]),
power_index_slow = mean(power_smooth_rel[slow_min:slow_max]),
power_index_rest = mean(power_smooth_rel[rest_min:rest_max])
%>%
)}] # melt all index variables into one column:
gather(grep("power", names(.), fixed = TRUE), key = "index", value = "power") %>% setDT(.) %>%
.[grepl("index_fast", index), label := paste0(round(delta_freq_fast, 2), "")] %>%
.[grepl("index_slow", index), label := paste0(round(delta_freq_slow, 2), "")] %>%
.[grepl("index_rest", index), label := paste0(round(delta_freq_rest, 2), "")] %>%
setorder(., classification, id, speed, snr, insert) %>%
# filter for specific inserts levels here:
filter(insert %in% c(2, 6)) %>% setDT(.)
dt_pred_rest_lsp_insert_exp %>%
dt_pred_rest_lsp_insert_exp_mean = # filter out the irrelvant rest power index:
filter(index != "power_index_rest") %>% setDT(.) %>%
# compare the mean power in expected frequnecy for every SNR and insert level:
.[, by = .(classification, speed, insert, snr, snr_char, index), {
t.test(power, mu = 0, alternative = "two.sided", paired = FALSE)
ttest_results =list(
num_subs = .N,
pvalue = ttest_results$p.value,
tvalue = ttest_results$statistic,
df = ttest_results$parameter,
cohens_d = (mean(power) - 0)/sd(power)
%>% verify(all(num_subs == 32)) %>% verify((num_subs - df) == 1) %>%
)}] # adjust p-values for multiple comparisons:
# check if the number of comparisons matches expectations:
.[, by = .(classification, speed), ":=" (
num_comp = .N,
pvalue_adjust = p.adjust(pvalue, method = "fdr", n = .N)
%>%
)] # round the original p-values according to the apa standard:
mutate(pvalue_round = round_pvalues(pvalue)) %>%
# round the adjusted p-value:
mutate(pvalue_adjust_round = round_pvalues(pvalue_adjust)) %>%
# create new variable indicating significance below 0.05
mutate(significance = ifelse(pvalue_adjust < 0.05, "*", ""))
# filter for all p-values below the significance level:
%>% filter(pvalue < 0.05) dt_pred_rest_lsp_insert_exp_mean
## classification speed insert snr snr_char index num_subs
## 1: ovr 2048 ms 2 0.125 1/8 power_index_slow 32
## 2: ovr 2048 ms 2 0.250 1/4 power_index_slow 32
## 3: ovr 2048 ms 2 0.500 1/2 power_index_slow 32
## 4: ovr 2048 ms 2 0.800 4/5 power_index_slow 32
## 5: ovr 2048 ms 6 0.800 4/5 power_index_slow 32
## 6: ovr 32 ms 2 0.800 4/5 power_index_fast 32
## 7: ovr 32 ms 2 0.800 4/5 power_index_slow 32
## 8: ovr 32 ms 6 0.800 4/5 power_index_fast 32
## pvalue tvalue df cohens_d num_comp pvalue_adjust pvalue_round
## 1: 0.037064838 2.178865 31 0.3851726 20 0.14825935 p = .04
## 2: 0.020549236 2.441117 31 0.4315327 20 0.10274618 p = .02
## 3: 0.008158507 2.826889 31 0.4997281 20 0.08158507 p = .008
## 4: 0.005626002 2.975570 31 0.5260115 20 0.08158507 p = .006
## 5: 0.014407380 2.592516 31 0.4582965 20 0.09604920 p = .01
## 6: 0.031706770 2.249760 31 0.3977051 20 0.26606660 p = .03
## 7: 0.039909990 2.144873 31 0.3791635 20 0.26606660 p = .04
## 8: 0.029710165 2.278960 31 0.4028671 20 0.26606660 p = .03
## pvalue_adjust_round significance
## 1: p = .15
## 2: p = .10
## 3: p = .08
## 4: p = .08
## 5: p = .10
## 6: p = .27
## 7: p = .27
## 8: p = .27
2.9.10.5 Figure 5i
rev(colorRampPalette(c('#4890F7', '#EB3C2D'), space = 'Lab')(5))
cols_order = dt_pred_rest_lsp_insert_exp %>%
plot_data = # add to inserts label:
mutate(insert = ifelse(insert == 1, paste(insert, "insert"), paste(insert, "inserts"))) %>%
# adjust the ordering of the snr levels:
transform(snr_char = factor(snr_char, levels = c("0", "1/4", "1/8", "1/2", "4/5"))) %>%
# filter out the irrelvant rest power index:
filter(index != "power_index_rest") %>% setDT(.)
ggplot(data = plot_data, aes(
fig_freq_exp_insert =y = power, x = fct_rev(label), fill = snr_char, color = snr_char, group = snr)) +
geom_bar(stat = "summary", fun = "mean", position = position_dodge(0.9),
width = 0.8) +
geom_point(position = position_jitterdodge(
jitter.width = 0.1, jitter.height = 0, seed = 2, dodge.width = 0.9),
alpha = 0.1, inherit.aes = TRUE, pch = 16) +
facet_grid(vars(insert), vars(fct_rev(as.factor(speed)))) +
stat_summary(fun.data = "mean_se", geom = "errorbar",
position = position_dodge(0.9), width = 0, color = "black") +
xlab("Predicted frequency") + ylab("Power\n(relative to pre-task rest)") +
coord_capped_cart(left = "both", bottom = "both", expand = TRUE, ylim = c(-0.1, 0.3)) +
scale_fill_manual(values = cols_order, name = "SNR level", guide = FALSE) +
scale_color_manual(values = cols_order, name = "SNR level", guide = FALSE) +
theme(axis.ticks.x = element_line(color = "white"), axis.line.x = element_line(color = "white")) +
theme(legend.position = "top", legend.direction = "horizontal",
legend.justification = "center", legend.margin = margin(t = 0, r = 0, b = 0, l = 0),
legend.box.margin = margin(t = 0, r = 0, b = 0, l = 0)) +
#guides(color = guide_legend(nrow = 1)) +
theme(strip.text = element_text(margin = margin(b = 2, t = 2, r = 2, l = 2))) +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
fig_freq_exp_insert
2.9.10.6 Source Data Fig. 5i
%>%
plot_data select(-classification, -fast_min, -fast_max, -slow_min, -slow_max) %>%
write.csv(., file = file.path(path_sourcedata, "source_data_figure_5i.csv"),
row.names = FALSE)
2.9.11 Figure 5
1 = plot_grid(fig_sd_prob, fig_mean_beta_abs, ncol = 2, labels = c("a", "b"))
panel_2 = plot_grid(fig_prob_slope, ncol = 1, labels = c("c"))
panel_3 = plot_grid(fig_freq_lsp, fig_freq_exp, ncol = 2, labels = c("d", "e"))
panel_4 = plot_grid(fig_snr_data_sd, fig_snr_pmat_sd, labels = c("f", "g"))
panel_5 = plot_grid(fig_freq_lsp_insert, fig_freq_exp_insert, labels = c("h", "i"))
panel_plot_grid(plot_grid(panel_1, panel_2, ncol = 2), panel_3, panel_4, panel_5, ncol = 1)
ggsave(filename = "highspeed_plot_decoding_rest.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 12)
ggsave(filename = "wittkuhn_schuck_figure_5.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 12)
2.10 Supplementary Figure
plot_grid(fig_prob_time_seq, fig_prob_diff_snr, fig_inserts, fig_inserts_slopes,
labels = "auto", ncol = 2, nrow = 3) fig_snr_pmat_slope, fig_snr_data_slope,
ggsave(filename = "highspeed_plot_decoding_rest_supplement.pdf",
plot = last_plot(), device = cairo_pdf, path = path_figures, scale = 1,
dpi = "retina", width = 10, height = 10)