How to use confounds in the second_level Oasis VBM example

Hi

I am interested in regressing out confounds on the second level, such as total intercranial volume (TIV). As a start I wanted to check how confounds work, and started by trying to use ‘age’ as a confound when investigating the effect of ‘age’. I know that seems strange, but it is just a way to see that adding the confound changes the data. However it doesn’t, so obviously I’m using it wrong. I am not able to find anyone that have asked a similar question and found a solution how to use it on the second level like this.

When I calculate TIV it makes no difference either, so I must be using confounds correctly.

If i run the .fit
with and without confounds it gives the same output.

second_level_model.fit(
    gray_matter_map_filenames,
    #confounds=confounds, 
    design_matrix=design_matrix,
)

I have modified the example on the Oasis dataset here: Voxel-Based Morphometry on OASIS dataset - Nilearn

n_subjects = 100  # more subjects requires more memory'
from nilearn import datasets
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from nilearn.image import resample_to_img
from nilearn import plotting
from nilearn.glm.second_level import SecondLevelModel
from nilearn.glm import threshold_stats_img
from nilearn.reporting import make_glm_report


oasis_dataset = datasets.fetch_oasis_vbm(
    n_subjects=n_subjects,
    legacy_format=False)


gray_matter_map_filenames = oasis_dataset.gray_matter_maps
age = oasis_dataset.ext_vars["age"].astype(float)
sex = oasis_dataset.ext_vars["mf"] == "F"

oasis_dataset.ext_vars["subject_label"] = [str(s) for s in range(0,len(oasis_dataset.ext_vars))]

confounds = pd.DataFrame(oasis_dataset.ext_vars[["age","subject_label"]])
confounds["confound"] = confounds["age"].astype(float)

gm_mask = datasets.fetch_icbm152_brain_gm_mask()


mask_img = resample_to_img(
    gm_mask,
    gray_matter_map_filenames[0],
    interpolation="nearest",
)


intercept = np.ones(n_subjects)
design_matrix = pd.DataFrame(
    np.vstack((age, sex, intercept)).T,
    columns=["age", "sex", "intercept"],
)


second_level_model = SecondLevelModel(smoothing_fwhm=2.0, mask_img=mask_img)
second_level_model.fit(
    gray_matter_map_filenames,
    confounds=confounds, # <- this line makes no difference.
    design_matrix=design_matrix,
)


z_map = second_level_model.compute_contrast(
    second_level_contrast="age",
    output_type="z_score",
)



_, threshold = threshold_stats_img(z_map, alpha=0.05, height_control="fdr")
print(f"The FDR=.05-corrected threshold is: {threshold:03g}")

display = plotting.plot_stat_map(
    z_map,
    threshold=threshold,
    colorbar=True,
    display_mode="z",
    cut_coords=[-4, 26],
    title="age effect on gray matter density (FDR = .05)",
)
plotting.show()

Hi @timokvamme and welcome to neurostars!

The confound argument is ignored if a design matrix is supplied in the model fitting function, since it is presumed the design matrix includes all confounds.

Also there is no way to test the effect of age when including age as a confound, as that would require having two age identical columns in the design matrix which would make it deficient.

Best,
Steven

Hi @Steven Thank you so much.
Appreciate it.

Okay so adding confounds is just about adding it in the design matrix.
So that means that in the example the age effect is actually the age effect unconfounded by sex.

Thank you for clearing this up

Yup! The [1 0 0 ] in the example contrast code means to take the main effect of the first column (in this case, age) while controlling for other columns (sex and intercept).