Population inference: Confirmation study

The analysis in Subject analysis: Inference at a single coordinate suggested that a word generating task results in a relevant and statistical significant increase in BOLD effect in the occipital lobe:

../_images/population-inference-confirmative_figure2_1.png

In this section, we aim to confirm this finding in a population of right-handed, healthy subjects. For right-handed, healthy subjects (with a Waterloo Handedness Score above +20) from the population have been drawn, their FMRI have been analysed, and the results collected in the file tutorial.sample:

fmrisample -v tutorial.sample \
    --covariates-query 'waterloo > 20'

Load data and fit the population model

from fmristats import *
from fmristats.plot import *

population_sample = load(path_to_sample)

print("""
    Number of subjects in total:     {:d}
    Number of right-handed subjects: {:d}
    Number of  left-handed subjects: {:d}""".format(
        len(population_sample.covariates),
        (population_sample.covariates.waterloo >= 20).sum(),
        (population_sample.covariates.waterloo <= 20).sum()))
Number of subjects in total:     64
Number of right-handed subjects: 64
Number of  left-handed subjects: 0

We are aiming to confirm a finding that has been suggested by an analysis of a single subject. Hence, remove this initial subject from the sample.

sample = population_sample.filter(population_sample.covariates.id != 2)

First, we shall have a look at the sample size per voxel in the MNI152-template (the number of subjects which have valid entries at this voxel):

sample_size = Image(
    sample.vb.reference,
    np.isfinite(sample.statistics).all(axis=-2).sum(axis=-1))

picture(sample_size.mask(inplace=False),cmap=cm.viridis)
../_images/population-inference-confirmative_figure5_1.png

It appears that for some voxels close to the surface of the MNI152-template the population diffeomorphisms \(ψ_j\) did not fully map the template brain onto the respective subject brain. This is to be expected.

print('Sample size at the index of interest: {:.0f}'.format(
    sample_size.data[index]))
Sample size at the index of interest: 63

Let us now fit the population model (which is a random effects meta regression model in fmristats) to the sample of BOLD effects and their respective estimated standard errors:

popmod = PopulationModel(sample)
result = popmod.fit()
… not all points in the mask are identifiable.
… points with missing data along subject dimension.
… number of points to estimate: 902629
… perform a meta analysis

Extract the Knapp-Hartung adjusted t-statistics field

Extract the Knapp-Hartung adjusted t-statistics field that tests for non-zero, task related BOLD effects:

tstatistics = result.get_tstatistic()

picture(tstatistics)
../_images/population-inference-confirmative_figure8_1.png

Now, plot the respective slice through the t-statistics field that has shown the above BOLD effect in the initial subject:

pt.plot([index[0]], [index[1]], 'ko')
pt.plot([index[0]], [index[1]], 'w+')
picture(tstatistics,3,1,1,[index[-1]],
    interpolation='bilinear')
../_images/population-inference-confirmative_figure9_1.png

The t-statistic at these coordinates is:

print(tstatistics.data[index])
2.705125720068511

Create a forest plot of BOLD statistics

Extract the estimated BOLD effects and the respective standard errors of the estimated BOLD effects from the sample:

df = population_sample.at_index(index)
df.valid.all()
df.sort_values(by='waterloo', inplace=True)

dm = result.at_index(index)
dm.set_index('parameter', inplace=True)

Define the critical values for the plots:

from scipy.stats.distributions import t
from scipy.stats.distributions import norm

crt_subject = norm.ppf(q=.95)
crt_population = t.ppf(q=.95, df = dm.loc['Intercept', 'df'])

print(crt_subject, crt_population)
1.6448536269514722 1.669804162296528

Set some general options for the visualisation:

import matplotlib
matplotlib.rc('xtick', labelsize=8)
matplotlib.rc('ytick', labelsize=8)

import seaborn as sb
sb.set_style('whitegrid')
palette = sb.palettes.SEABORN_PALETTES['deep']

figw = 5.842
figh = 8.442

Create a forest plot in ascending order of handiness (left handed on the bottom, right handed on the top):

x = dm.loc['Intercept', 'point']
s = dm.loc['Intercept', 'stderr']

df['yvec'] = range(len(df.task))
df['waterloo'] = df.waterloo.astype(int)

fig = pt.figure(figsize=(figw,figh))
ax = pt.subplot(111)
ax.axvline(0,c='k',lw=.9, ls=':')
ax.errorbar(df.task[df.id!=2], df.yvec[df.id!=2],
    xerr=crt_subject*df.stderr[df.id!=2], fmt='o',
    label='Sample', c=palette[0])
ax.errorbar(df.task[df.id==2], df.yvec[df.id==2],
    xerr=crt_subject*df.stderr[df.id==2], fmt='o',
    label='Initial Subject', c=palette[1])
ax.errorbar(x, -1, xerr=crt_population*s, fmt='o',
    label='Population', c=palette[2])
ax.axhline(-.5,c='k',lw=.9, ls='-')
ax.set_xlabel(r'Task related BOLD effect at $x_0$ in ATI')
ax.set_ylabel('Waterloo')
ax.yaxis.set_ticks_position('none')
pt.box(False)
pt.yticks(np.hstack((df.yvec, -1)), list(df.waterloo) + ["Meta"] )
pt.legend(loc='lower center', bbox_to_anchor=(.5, -0.2), ncol=3)
../_images/population-inference-confirmative_figure15_1.png

Quite interestingly, there was even one subject in the sample that showed an even larger task related BOLD effect than the initial subject at theses coordinates.

Create a funnel plot of BOLD statistics

Create a funnel plot with the estimated BOLD effect on the x-axis and the reciprocal of the squared standard error on the y-axis:

../_images/population-inference-confirmative_figure18_1.png

Formal testing

Any prior knowledge had been removed prior to testing the hypothesis of whether there exists a task related BOLD effect at \(x\) in the sample. Hence, there is no need to adjust for multiple testing in the inference for the population, we there was, well, no multiple testing.

Point estimate, confidence interval, and p-value of BOLD effect of the word generating task at the point \(x\) in the population:

x = dm.loc['Intercept', 'point']
s = dm.loc['Intercept', 'stderr']

print("""
Point estimate of the BOLD effect: {:.2f}
Lower bound of a 95% confidence interval: {:.2f}
Upper bound of a 95% confidence interval: {:.2f}
p-value for ≠0:                           {:.4f}""".format(
    x, x-crt_population*s, x+crt_population*s,
    dm.loc['Intercept', 'pvalue']))
Point estimate of the BOLD effect: 5.97
Lower bound of a 95% confidence interval: 2.29
Upper bound of a 95% confidence interval: 9.66
p-value for ≠0:                           0.0044

We may reject the null hypothesis that there exists no BOLD effect at \(x\) at \(1\%\) level of significance.