10 RSA in searchlight peak with different threshold
This section is to address a reviewer comment about whether the results of this analysis differ when running it with a different threshold (p<0.001 rather than p<0.01) to define the ROI based on the same-sequence searchlight. The code below is thus copied from the original script and modified to include the different p-threshold.
To test whether within- and across-sequence representations overlap, we defined an ROI based on the within-sequence searchlight analysis. Specifically, voxels belonging to the cluster around the peak voxel, thresholded at p<0.01 uncorrected within our small volume correction mask, were included. The analysis of representational change was then carried out as described for the other ROIs above. The results observed using a threshold of p<0.001 were not statistically different from those obtained with a threshold of p<0.01 (t27=-0.95, p=0.338; test against 0 using the ROI resulting from the p<0.001 threshold: t27=-1.98, p=0.056).
10.1 Prepare RSA in searchlight peak
To show that overlapping clusters of voxels drive both the within- and across sequence effects, we will run the across-sequence analysis in the cluster of voxels that show the within-day effect. Because these are independent comparisons, we can use the within-day searchlight to define a region of interest for the across-day analysis.
Create the ROI mask
The first steps are to prepare the ROI mask we want to use. Thus, we need to threshold the ROI from the main analysis in MNI space and move the resulting mask to each participant’s functional space for the analysis.
<- c("uncorrp001lh") threshs
for (i_thresh in threshs){
# threshold the searchlight results
if (i_thresh == "corrp05" | i_thresh == "corrp05lh"){
<- file.path(dirs$data4analysis, "searchlight_results", "same_day_vir_time",
in_fn "same_day_vir_time_randomise_svc_fwhm3_tfce_corrp_tstat1.nii.gz")
else{
} <- file.path(dirs$data4analysis, "searchlight_results", "same_day_vir_time",
in_fn "same_day_vir_time_randomise_svc_fwhm3_tfce_p_tstat1.nii.gz")
}
<- file.path(dirs$mask_dir, "svc", "svc_mask.nii.gz")
svc_fn <- here("data", "mri", "rois", "mni_masks", sprintf("searchlight_same-day_svc_%s.nii.gz", i_thresh))
bin_fn fsl_thresh(file = in_fn, outfile = bin_fn, thresh = 0.95, opts = sprintf("-bin -mas %s", svc_fn),
verbose = FALSE, retimg = FALSE)
# peak cluster is in left hemisphere, so don't include any voxels in right hemisphere
if (i_thresh == "uncorrp001lh" | i_thresh == "corrp05lh"){
<- here("data", "mri", "rois", "mni_masks", "left_hemi.nii.gz")
lh_fn fsl_maths(file=bin_fn, outfile = lh_fn, opts = "-mul 0 -add 1 -roi 91 182 0 218 0 182 0 1",
retimg = FALSE, verbose = FALSE)
fsl_mul(file=bin_fn, file2=lh_fn, outfile=bin_fn,
retimg = FALSE, verbose = FALSE)
}
# let's have a look at the mask we created
<- readNIfTI2(mni_fname("1"))
mni_nii <- readNIfTI2(bin_fn)
roi_nii <- c(statip::mfv1(which(roi_nii==1, arr.ind = TRUE)[,1]),
coords ::mfv1(which(roi_nii==1, arr.ind = TRUE)[,2]),
statip::mfv1(which(roi_nii==1, arr.ind = TRUE)[,3]))
statiportho2(mni_nii, y = roi_nii, xyz = coords, add.orient = TRUE)
# check the number of voxels in this ROI
sum(c(roi_nii))
}
The resulting ROI mask is now coregistered from MNI 1mm space to the analysis space of the wholebrain functional sequence. Finally, it is thresholded at a probability of 0.5.
for (i_thresh in threshs){
<- here("data", "mri", "rois", "same-day_searchlight", "samespace")
samespace_dir if (!dir.exists(samespace_dir)){dir.create(samespace_dir, recursive = TRUE)}
# name of transformation matrix file to move from highres to functional space
<- here("data", "mri", "processed", "wholebrain",
standard2func paste0("VIRTEM_P", subjects, ".feat"),
"reg", "standard2example_func.mat")
# use the mean EPI of wholebrain image as a reference
<- here("data", "mri", "processed", "samespace", paste0("VIRTEM_P", subjects),
mean_epi paste0("VIRTEM_P", subjects, "_wholebrain.nii.gz"))
# define output files in samespace (ss) = analysis space based on the wholebrain EPI
<- file.path(samespace_dir, sprintf("P%s_%s_%s_ss.nii.gz",
roi_ss "same-day_searchlight", i_thresh))
subjects,
# apply FSL flirt to move ROI from standard to wholebrain functional space
invisible(mapply(flirt_apply, infile = bin_fn, reffile = mean_epi,
initmat = standard2func, outfile = roi_ss,
verbose = FALSE, retimg = FALSE))
# use fslmaths to binarize the masked ROIs using a threshold of 0.5
<- mapply(fsl_thresh, file = roi_ss, outfile = roi_ss, thresh = 0.5, opts = "-bin",
out verbose = FALSE, retimg = FALSE)
}
Calculating the correlation matrices
The following steps are analogous to the preparation of the functional data in the ROI and searchlight analyses. For the main searchlight analyses we already cleaned the voxel-wise time series and extracted the volumes relevant to RSA. Thus, to calculate the correlation matrices and to calculate pattern similarity changes, we fall back onto the scripts from the main ROI analyses and the searchlight.
for (i_thresh in threshs){
# for all 10x10 comparisons we will be averaging all comparisons apart from the diagonal
# to exclude same_block comparisons
<- matrix(data = TRUE, nrow=10, ncol = 10)
no_diag diag(no_diag)<- FALSE
<- here("data", "mri","rsa","correlation_matrices", "same-day_searchlight")
out_dir if (!dir.exists(out_dir)){dir.create(out_dir, recursive = TRUE)}
for (i_sub in subjects){
# load the ROI based on the searchlight
<-file.path(samespace_dir, sprintf("P%s_%s_%s_ss.nii.gz",
roi_fn "same-day_searchlight", i_thresh))
i_sub, <- readNIfTI(roi_fn, reorient=FALSE)
roi_nii
# array indices of ROI voxels
<- which(roi_nii == 1, arr.ind=TRUE)
roi_vox sprintf("%s: %d voxels\n", i_sub, nrow(roi_vox))
for (i_run in 1:n_runs){
# load the relevant functional volumes
<- file.path(dirs$searchlight, "rel_vols_4D",
rel_vol_fn sprintf("%s_run%02d_rel_vols.nii.gz", i_sub, i_run))
<- readNIfTI(rel_vol_fn, reorient=FALSE)
func_nii
# get the ROI voxels
<- array(NA, c(n_pics*n_blocks, nrow(roi_vox))); # images in rows (ROI voxels), voxels in columns
rel_dat for (i in 1:nrow(roi_vox)) { # i <- 1
<- func_nii[roi_vox[i,1], roi_vox[i,2], roi_vox[i,3],]
curr_vox if (sd(curr_vox) > 0) {rel_dat[,i] <- curr_vox} else { stop("zero variance voxel")}
}
# data is in repetition (row) by voxel (col) format, so we transpose
# to get a voxel x repetition format
<- t(rel_dat)
rel_dat
# calculate correlation matrix (trial by trial) for pre and post run
<- cor(rel_dat, rel_dat)
cor_mat_trial
# initialize condition by condition correlation matrix for pre and post run
<- matrix(nrow = 20, ncol = 20)
corr_mat
# loop over all picture comparisons
for(i_pic1 in 1:20){
for(i_pic2 in 1:20){
# extract the current 10x10 correlation matrix
<- (1+(i_pic1-1)*10):(i_pic1*10)
i1 <- (1+(i_pic2-1)*10):(i_pic2*10)
i2 <- cor_mat_trial[i1, i2]
curr_mat
# average the correlations while excluding diagonal (same block comparisons)
<- mean(curr_mat[no_diag])
corr_mat[i_pic1, i_pic2]
}
}
# save the correlation matrix
<- file.path(out_dir, sprintf("%s_%s_%s_%s_corr_mat.txt",
fn "same-day_searchlight", i_thresh, runs[i_run]))
i_sub, write.table(corr_mat, fn, append = FALSE, sep = ",",
dec = ".", row.names = FALSE, col.names = FALSE)
}
} }
Calculate Pattern Similarity Change
In the next step, we calculate how the correlation between patterns change from the first to the second picture viewing task, i.e. through the day learning task. To do this, we load both correlation matrices and reduce them to the upper triangle, excluding the diagonal. Then, the correlations are Fisher Z-transformed and the similarity values from the pre-learning picture viewing task are subtracted from those of the post-learning picture viewing task to isolate pattern similarity change. The correlations and their changes are then saved together with information about the pictures that were compared.
This part is based on the corresponding script from the main ROI analyses.
<- here("data", "mri","rsa","correlation_matrices", "same-day_searchlight")
in_dir <- here("data", "mri","rsa","pattern_similarity_change", "same-day_searchlight")
out_dir if (!dir.exists(out_dir)){dir.create(out_dir, recursive = TRUE)}
for (i_thresh in threshs){
for(i_sub in subjects){
# load pre correlation matrix
<- file.path(in_dir, sprintf("%s_%s_%s_pre_corr_mat.txt",
fn "same-day_searchlight", i_thresh))
i_sub, <- read.table(fn, sep = ",", dec = ".")
corr_mat_pre
# load post correlation matrix
<- file.path(in_dir, sprintf("%s_%s_%s_post_corr_mat.txt",
fn "same-day_searchlight", i_thresh))
i_sub, <- read.table(fn, sep = ",", dec = ".")
corr_mat_post
# reduce to upper triangle of correlations (without diagonal)
<- corr_mat_pre[upper.tri(corr_mat_pre, diag = FALSE)]
pre_corrs <- corr_mat_post[upper.tri(corr_mat_post, diag = FALSE)]
post_corrs
# create a tibble with the correlations
<- which(upper.tri(corr_mat_post), arr.ind=TRUE)
pics <- tibble(pic1 = pics[,1], pic2 = pics[,2], pre_corrs, post_corrs)
corr_df
# Fisher Z transform correlations and calculate pattern similarity change
# by subtracting the pre-correlations from the post-correlations
$ps_change <- FisherZ(corr_df$post_corrs) - FisherZ(corr_df$pre_corrs)
corr_df
# write to file
<- file.path(out_dir, sprintf("%s_%s_%s_pattern_similarity_change.txt",
fn "same-day_searchlight", i_thresh))
i_sub, write.table(corr_df, fn, append = FALSE, sep = ",",
dec = ".", row.names = FALSE, col.names = TRUE)
} }
Combine behavioral and fMRI data for RSA
To create input for the RSA the next step is to combine the similarity change data with the behavioral data, so that we can do meaningful analyses. First the behavioral data is loaded and brought into the same pair-wise format as the similarity change data. Both datasets are combined for each subject and then written to disk.
This part is based on the corresponding script from the main ROI analyses.
<- here("data", "mri","rsa","pattern_similarity_change", "same-day_searchlight")
in_dir <- here("data", "mri","rsa","data_for_rsa")
out_dir if (!dir.exists(out_dir)){dir.create(out_dir, recursive = TRUE)}
for (i_thresh in threshs){
for (i_sub in subjects){
# load the behavioral data
<- file.path(dirs$timeline_dat_dir, sprintf("%s_behavior_tbl_timeline.txt", i_sub))
fn <- cols_only(sub_id = col_character(), day = col_factor(),
col_types_list event = col_integer(), pic = col_integer(),
virtual_time = col_double(), real_time = col_double(),
memory_time = col_double(), memory_order = col_double(),
sorted_day = col_integer())
<- read_csv(fn, col_types = col_types_list)
beh_dat
# sort behavioral data according to picture identity
<- beh_dat[order(beh_dat$pic),]
beh_dat_ordered
# find the order of comparisons
<- which(upper.tri(matrix(nrow = 20, ncol = 20)), arr.ind=TRUE)
pairs
# extract the data for the first and second picture in each pair from the behavioral data
<- beh_dat_ordered[pairs[,1],]
pic1_dat <- beh_dat_ordered[pairs[,2],]
pic2_dat colnames(pic1_dat) <- paste0(colnames(pic1_dat), "1")
colnames(pic2_dat) <- paste0(colnames(pic2_dat), "2")
# combine the two tibbles
<- cbind(pic1_dat, pic2_dat)
pair_dat
# reorder the tibble columns to make a bit more sense
<- pair_dat %>%
pair_dat select(sub_id1,
day1, day2,
pic1, pic2,
event1, event2,
virtual_time1, virtual_time2,
real_time1, real_time2,
memory_order1, memory_order2,
memory_time1, memory_time2, %>%
sorted_day1, sorted_day2) rename(sub_id = sub_id1)
<- tibble()
rsa_dat
# load the pattern similarity change data for this ROI
<- file.path(in_dir, sprintf("%s_%s_%s_pattern_similarity_change.txt",
fn "same-day_searchlight", i_thresh))
i_sub, <- read.csv(fn)
ps_change_dat
# make sure files have the same order
::are_equal(c(pair_dat$pic1, pair_dat$pic2), c(ps_change_dat$pic1, ps_change_dat$pic2))
assertthat
# add column with ROI name
<- add_column(ps_change_dat, roi = paste0("same-day_searchlight_", i_thresh))
ps_change_dat
# collect the data from this ROI and merge into long data frame
<- cbind(pair_dat, ps_change_dat[,3:6])
roi_dat <- rbind(rsa_dat, roi_dat)
rsa_dat
# write to file
<- file.path(dirs$rsa_dat_dir, sprintf("%s_data_for_rsa_same-day_searchlight_%s.txt", i_sub, i_thresh))
fn write.table(rsa_dat, fn, append = FALSE, sep = ",",
dec = ".", row.names = FALSE, col.names = TRUE)
} }
Finally, the datasets are combined across subjects.
# set up a dataframe to collect the data
= tibble()
rsa_dat for (i_thresh in threshs){
for (i_sub in subjects){
# load data from CSV
<- file.path(out_dir, sprintf("%s_data_for_rsa_same-day_searchlight_%s.txt",i_sub, i_thresh))
fn <- cols_only(
col_types_list sub_id = col_character(),
day1 = col_integer(), day2 = col_integer(),
event1 = col_integer(), event2 = col_integer(),
pic1 = col_integer(), pic2 = col_integer(),
virtual_time1 = col_double(), virtual_time2 = col_double(),
real_time1 = col_double(), real_time2 = col_double(),
memory_time1 = col_double(), memory_time2 = col_double(),
memory_order1 = col_double(), memory_order2 = col_double(),
sorted_day1 = col_integer(), sorted_day2 = col_integer(),
pre_corrs = col_double(), post_corrs = col_double(),
ps_change = col_double(), roi = col_factor()
)<- as_tibble(read_csv(fn, col_types = col_types_list))
sub_dat
# append to table with data from all subjects
<- bind_rows(sub_dat, rsa_dat)
rsa_dat
}
# sort the data
<- rsa_dat[with(rsa_dat, order(sub_id, day1, day2, event1, event2)),]
rsa_dat
# write to file
<- file.path(dirs$data4analysis, sprintf("rsa_data_in_same-seq_searchlight_peak_%s.txt", i_thresh))
fn write.table(rsa_dat, fn, append = FALSE, sep = ",",
dec = ".", row.names = FALSE, col.names = TRUE)
}
10.2 RSA
We use permutation-based linear model to analyze the data with a summary statistics approach.
# define function that calculates z-value for permutation
<- function(in_dat = df, lm_formula = lm_formula, nsim = 1000){
lm_perm_jb2
# run the model for original data and store observed t-values
<- lm(formula = lm_formula, data=in_dat)
lm_fit <- coef(summary(lm_fit))[,"t value"]
obs
# extract the dependent variable from the formula
<- str_extract(lm_formula, "[^~]+")
dv <- str_replace_all(dv, fixed(" "), "")
dv if(!(dv %in% colnames(in_dat))){stop("Cannot find dependent variable in input data");}
# initialize df for permutation
<- in_dat
data_perm
# set aside space for results
<- matrix(nrow = nsim, ncol = length(obs))
res
for (i in 1:nsim) {
# scramble response value
<- sample(nrow(in_dat))
perm <- in_dat[dv]
dv_dat <- dv_dat[perm,]
data_perm[dv]
# compute linear model and store the t-value of predictor
<- lm(formula = lm_formula, data=data_perm)
lm_fit_perm <- coef(summary(lm_fit_perm))[,"t value"]
res[i,]
}
# append the observed value to the list of results
<- rbind(res,obs)
res
# calculate p-value for each coefficient and transform to z
<- rep(0,length(obs))
p <- rep(0,length(obs))
z for (i_coef in 1:length(obs)){
<- sum(res[,i_coef] <= obs[i_coef])/nrow(res)
p[i_coef] <- -1*qnorm(1-p[i_coef])
z[i_coef]
}return(z)
}
Now run the linear model for each participant and do group-level stats.
for (i_thresh in threshs){
# load the data
<- cols_only(
col_types_list sub_id = col_character(),
day1 = col_integer(), day2 = col_integer(),
virtual_time1 = col_double(), virtual_time2 = col_double(),
ps_change = col_double(), roi = col_factor())
<- file.path(dirs$data4analysis, sprintf("rsa_data_in_same-seq_searchlight_peak_%s.txt", i_thresh))
fn <- as_tibble(read_csv(fn, col_types = col_types_list))
rsa_dat_within_cluster
## PREAPARE RSA
# create predictors for RSA
<- rsa_dat_within_cluster %>%
rsa_dat_within_cluster mutate(
# pair of events from say or different day (dv = deviation code)
same_day = day1 == day2,
same_day_dv = plyr::mapvalues(same_day, from = c(FALSE, TRUE), to = c(-1, 1)),
# absolute difference in time metrics
vir_time_diff = abs(virtual_time1 - virtual_time2)) %>%
# z-score the time metric predictors (within each subject)
group_by(sub_id) %>%
mutate_at(c("vir_time_diff"), scale, scale = TRUE) %>%
ungroup()
##### First-level RSA {-}
set.seed(179) # set seed for reproducibility
# do RSA using linear model and calculate z-score for model fit based on permutations
<- rsa_dat_within_cluster %>% group_by(sub_id, same_day) %>%
rsa_fit_within_cluster2 # run the linear model
do(z = lm_perm_jb2(in_dat = ., lm_formula = "ps_change ~ vir_time_diff", nsim = n_perm)) %>%
mutate(z = list(setNames(z, c("z_intercept", "z_virtual_time")))) %>%
unnest_wider(z)
# add group column used for plotting
$group <- factor(1)
rsa_fit_within_cluster2
# add a factor with character labels for within/across days and one to later color control in facets
<- rsa_fit_within_cluster2 %>%
rsa_fit_within_cluster2 mutate(same_day_char = plyr::mapvalues(same_day,
from = c(0, 1),
to = c("across days", "within days"),
warn_missing = FALSE),
same_day_char = factor(same_day_char, levels = c("within days", "across days")))
# run a group-level t-test on the different sequence RSA fits in the within-searchlight cluster
<- rsa_fit_within_cluster2 %>%
stats filter(same_day == FALSE) %>%
select(z_virtual_time) %>%
paired_t_perm_jb (., n_perm = n_perm)
# Cohen's d with Hedges' correction for one sample using non-central t-distribution for CI
<-cohen.d(d=(rsa_fit_within_cluster2 %>% filter(same_day == FALSE))$z_virtual_time,
df=NA, paired=TRUE, hedges.correction=TRUE, noncentral=TRUE)
$d <- d$estimate
stats$dCI_low <- d$conf.int[[1]]
stats$dCI_high <- d$conf.int[[2]]
stats
# print results
huxtable(stats) %>% theme_article()
}
Summary Statistics: t-test against 0 for virtual time across sequences in within-sequence searchlight peak (thresholded at p<0.001)
t27=-1.98, p=0.056, d=-0.36, 95% CI [-0.77, 0.01]
Compare the results using the two different thresholds with a permutation-based t-test on the difference.
# add column for cluster
<- rsa_fit_within_cluster2 %>%
rsa_fit_within_cluster2 mutate(thresh = "v2")
<- rsa_fit_within_cluster %>%
rsa_fit_within_cluster mutate(thresh = "v1")
# merge data frames and select across-sequence values
<- rbind(rsa_fit_within_cluster, rsa_fit_within_cluster2) %>%
rsa_fit_within_cluster_comp filter(same_day == FALSE)
# run permutation-based t-test on the difference between the two sets of results
<- rsa_fit_within_cluster_comp %>%
stats select(-z_intercept) %>%
pivot_wider(names_from = thresh, values_from = z_virtual_time) %>%
mutate(thresh_diff = v1 - v2) %>%
select(thresh_diff) %>%
paired_t_perm_jb (., n_perm = n_perm)
# print results
huxtable(stats) %>% theme_article()
estimate | statistic | p.value | p_perm | parameter | conf.low | conf.high | method | alternative |
---|---|---|---|---|---|---|---|---|
-0.088 | -0.947 | 0.352 | 0.338 | 27 | -0.279 | 0.103 | One Sample t-test | two.sided |
Summary Statistics: t-test between RSA results in searchlight cluster defined on different thresholds (p<0.01 vs. p<0.001) t27=-0.95, p=0.338