Finger/Foot/Lips Motor Test-Retest Dataset From OpenNeuro
This is an example of running a mixed effects model on preprocessed fMRI data from a real task. The task is the test-retest dataset for motor functions, available at openneuro and described in this paper. We are grateful the authors for making this dataset available in this manner to facilitate development of these models.
The motor task is a block design task that consists of blocks of finger tapping, foot twitching, and presumably lip pursing interleaved with visual fixation. There were two occasions of measurement so that test-retest reliability coul be evaluated.
We include a few examples of different models that one might run.
Please Note All of the readargs files here are configured to generate SGE appropriate output with approximately 50 jobs. On my 28 core machine, this takes about an hour. Please modify the arguments to be appropriate for your environment.
Quick start.
Go into the directory example.fingerfootlips
.
cd example.fingerfootlips
You will need to download the data from OpenNeuro. To do this, run
./00downloadfiles
This will create three directories: fmri
, confounds
, and
brainmask
. The fmri
directory contains fMRI data processed using
FMRIPREP. The 00downloadfiles
script removes the first TR, a dummy scan, from these
files. (You can identify the dummy scans because they are bright and have average values much higher than the others). The confounds
directory contains the confounds (e.g. motion
parameters, signal from white matter, etc) created by the FMRIPREP
program. Note that we do NOT remove the first TR from these confound files; we
remove it later, when preparing the design matrix. The third directory
brainmask
contains brain masks for the fMRI data, indicating where
each subject has valid data.
We have created a brain mask for you called mask.nii.gz
using the
brainmasks in brainmask
. The script to do this is
00-Opt-makebrainmask
, which you do NOT need to run. We provide it as
an example for you if you wish to process your own data. It uses FSL
utilities to create a mask which includes only voxels for which every
subject has data.
We have also used FSL to convolve the explanatory variables (provided in task-fingerfootlips_events.tsv
with a gamma HRF to create task regressors, provided in task-fingerfootlips_neuropoint.txt
. If you don’t use FSL, or if you prefer to convolve with a different function, you could use the fmri
package from R.
You can create the setfilenames and the setlabels by running the following scripts:
./01createSetFilenames
./01createSetLabels
Creation of the set filenames is pretty straightforward; the script just createsa list of the test files as time 1, and the retest files as time 2. The set labels are created by merging the convolved explanatory variables with the confounds. We also add variables for the subject identifier, the TR, and the time (1 or 2). Finally, we remove the data for the first TR because it is a dummy scan.
Now run the example. This example is totally canned for you so that it reads all the command line flags from the readargs.R
file. So all you need to type is
npoint
Now wait for a bit. This will take a few minutes to complete. It is
reading in the fMRI files and covariates and splitting up the voxels
in the mask into 50 individual jobs in the directory sgedata
. This is
useful if your analysis will take a long time, because you can submit
the jobs in a cluster environment.
If you cd
into the directory sgedata
, you will see a bunch of
files with the extention .rds
and .nii.gz
. The RDS files are data
files, one per each job, that will be read in by R. These contain just
the fMRI data for the voxels in each small chunk of work. The
corresponding NiFTI file is a mask with the spatial information for
where these voxels are located in the brain.
To run the actual analysis will take more time. If you have access to an SGE cluster, you can type:
runme.sge
Alternatively, if you are on a multicore Linux or OSX machine, you can type:
runme.local
This will run the job on your local machine using 4 cores.
What it means.
This is a motor task that involves movement of a finger, foot, and lips. Different motor tasks activate different areas of the brain.
Data.
The data are downloaded from OpenNeuro into the directory fmri
in the download step described above.
Each file has a name of the following format (* is a wildcard):
sub-*-test_task*.nii.gz
or
sub-*-retest_task*.nii.gz
There are 10 subjects, numbered 01 through 10. Each subject has a test run and a retest run. For our purposes, we consider the test run to be timepoint 1 and the retest run to be timepoint 2.
Confounds.
The fMRI data that we use are pre-processed using FMRIPREP. You can see the description of the confounds in [FMRIPREP documentation] (https://fmriprep.readthedocs.io/en/stable/outputs.html).
Model.
The readargs.R
file specifies that the model we are running in this
example is nlmemodel.R
. The model code is all that you should ever need
to write. In it, you define a processVoxel
function that takes a voxel
number (v
), looks it up in a global data structure (voxeldat
), and
uses column names of the design matrix (formed from the setlabels and
the covariates) to do some calculation and return statistics.
The model we use tests for where the Beta coefficients of hand, foot
and lips are significantly different from zero, using the nlme
package. We include a random effect of intercept, which means we are
not generalizing the Beta coefficients that we estimate to the
population as a whole. Random effects are better expressed using the
lme4
package. However, the lme4
package does not allow one to
easily express the autocorrelated structure of the data. With nlme
,
we can specify an AR(1) model. It is possible, of course, to test whether this removes the autocorrelation structure of the residuals.
Below is a listing of nlmemodel.R
.
library(nlme)
processVoxel <-function(v) {
BRAIN <- voxeldat[,v]
e <- try(mod <- lme(BRAIN ~ Finger+Foot+Lips+WhiteMatter+X+Y+Z+RotX+RotY+RotZ, random=~1|idnum, method=c("ML"), na.action=na.omit, corr=corAR1(form=~1|idnum), control=lmeControl(returnObject=TRUE,singular.ok=TRUE)))
if(inherits(e, "try-error")) {
mod <- NULL
}
if(!is.null(mod)) {
#finger contrast
contr <- c(0, 1, 0, 0, 0, 0,0,0,0,0,0)
out <- anova(mod,L=contr)
finger.t.stat <- (t(contr)%*%mod$coefficients$fixed)/sqrt(t(contr)%*%vcov(mod)%*%contr)
finger.p<-1-out$"p-value"
#foot contrast
contr <- c(0, 0, 1, 0, 0, 0,0,0,0,0,0)
out <- anova(mod,L=contr)
foot.t.stat <- (t(contr)%*%mod$coefficients$fixed)/sqrt(t(contr)%*%vcov(mod)%*%contr)
foot.p <- 1-out$"p-value"
#lips contrast
contr <- c(0, 0, 0, 1, 0, 0,0,0,0,0,0)
out <- anova(mod,L=contr)
lips.t.stat <- (t(contr)%*%mod$coefficients$fixed)/sqrt(t(contr)%*%vcov(mod)%*%contr)
lips.p <- 1-out$"p-value"
retvals <- list(finger.t.stat, finger.p, foot.t.stat, foot.p, lips.t.stat, lips.p)
} else {
# If we are returning 4 dimensional data, we need to be specify how long
# the array will be in case of errors
retvals <- list(999,999,999,999,999,999)
}
names(retvals) <- c("finger.tstat", "finger.p", "foot.tstat", "foot.p", "lips.tstat", "lips.p")
retvals
}
Prewhitening.
FMRI data is autocorrelated. If we run models with wild abandon without consideration of the autocorrelation structure of our data, we will generally get innaccurate results from our statistics. This is something that fMRI software generally does for you, in a process called prewhitening.
I am not positive what the best way to do prewhitening in this context should look like. A major goal is to keep the R models looking fairly simple so that people can read them and understand them easily. So here we use AFNI to prewhiten the data before modeling.
In the example.fingerfootlips
directory, run this script:
03prewhiten
This takes some time to run and is not parallelized; I suggest starting it before you leave for the day. This will create the directory fmri.prewhiten
. Data
here are prewhitened using AFNI 3dREMLfit
with a default column matrix of 1s as described in this posting. There may be better ways to do this; for example, see the procedure that
Mandy Mejia recommends.
However, the data created in fmri.prewhiten
are used in subsequent models to produce what I think should be more realistic parameter estimates.
Using lme4.
This example uses the lmer
function from the lme4
library to do the same analysis as above. However, this model uses prewhitened data and accurately models random slopes, so it is the closest approximation to what FSL and other packages do.
Note that lmer
does not generate p statistics. They are provided by
the library lmerTest
, which overrides the lmer
command and
provides the contest
function to evaluate the significance of
contrasts.
To run this example, link the file readargs.lmermodel.prewhitened.R
to be just readargs.R
:
bash
rm readargs.R
ln -s readargs.lmermodel.prewhitened.R readargs.R
If you run npoint
, this will create the output directory lmer.prewhitened. As always, go into that directory and execute the appropriate run script for your environment. If you are curious, there is also an argument file called readargs.lmermodel.notprewhitened.R
that you could run for comparison, to see the difference that prewhitening makes.
The model is listed below.
library(lme4)
library(lmerTest)
processVoxel <-function(v) {
BRAIN <- voxeldat[,v]
e <- try(mod<-lmer(BRAIN ~ Finger+Foot+Lips+WhiteMatter+X+Y+Z+RotX+RotY+RotZ + (1 + Finger+Foot+Lips|idnum) + (1|time), REML = FALSE))
if(inherits(e, "try-error")) {
mod <- NULL
}
if(!is.null(mod)) {
# finger contrast
contr <- contest(mod, L=c(0,1,0,0,0,0,0,0,0,0,0), joint=FALSE)
finger.t.stat <- contr$"t value"
finger.p <- 1- contr$"Pr(>|t|)"
# foot contrast
contr <- contest(mod, L=c(0,0,1,0,0,0,0,0,0,0,0), joint=FALSE)
foot.t.stat <- contr$"t value"
foot.p <- 1-contr$"Pr(>|t|)"
# lips contrast
contr <- contest(mod, L=c(0,0,0,1,0,0,0,0,0,0,0), joint=FALSE)
lips.t.stat <- contr$"t value"
lips.p <- 1-contr$"Pr(>|t|)"
retvals <- list(finger.t.stat, finger.p, foot.t.stat, foot.p, lips.t.stat, lips.p)
} else {
retvals <- rep(999,6)
}
names(retvals) <- c("finger.tstat", "finger.p", "foot.tstat", "foot.p", "lips.tstat", "lips.p")
retvals
}
An Intraclass Correlation.
This is an example that calculates the ICC as the within-subject variance divided by the total variance. Note that this example illustrates one of the
library(lme4)
library(lmerTest)
processVoxel <-function(v) {
BRAIN <- voxeldat[,v]
e <- try(mod1_crossed <-lmer(BRAIN ~ Finger+Foot+Lips+WhiteMatter+X+Y+Z+RotX+RotY+RotZ + (1 + Finger+Foot+Lips|idnum) + (1|time), REML = FALSE))
if(inherits(e, "try-error")) {
mod1_crossed <- NULL
}
if(!is.null(mod1_crossed)) {
# get variance components
vc <- as.data.frame(VarCorr(mod1_crossed))
# calculate total random variance not explained by fixed effects
totalRV <- sum(vc$vcov)
# ICC is the within-subject variance
ssICC <- vc$vcov[1]/totalRV
retvals <- list(ssICC)
} else {
retvals <- list(999)
}
names(retvals) <- c("ICC")
retvals
}