nimare 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchmarks/__init__.py +0 -0
- benchmarks/bench_cbma.py +57 -0
- nimare/__init__.py +45 -0
- nimare/_version.py +21 -0
- nimare/annotate/__init__.py +21 -0
- nimare/annotate/cogat.py +213 -0
- nimare/annotate/gclda.py +924 -0
- nimare/annotate/lda.py +147 -0
- nimare/annotate/text.py +75 -0
- nimare/annotate/utils.py +87 -0
- nimare/base.py +217 -0
- nimare/cli.py +124 -0
- nimare/correct.py +462 -0
- nimare/dataset.py +685 -0
- nimare/decode/__init__.py +33 -0
- nimare/decode/base.py +115 -0
- nimare/decode/continuous.py +462 -0
- nimare/decode/discrete.py +753 -0
- nimare/decode/encode.py +110 -0
- nimare/decode/utils.py +44 -0
- nimare/diagnostics.py +510 -0
- nimare/estimator.py +139 -0
- nimare/extract/__init__.py +19 -0
- nimare/extract/extract.py +466 -0
- nimare/extract/utils.py +295 -0
- nimare/generate.py +331 -0
- nimare/io.py +667 -0
- nimare/meta/__init__.py +39 -0
- nimare/meta/cbma/__init__.py +6 -0
- nimare/meta/cbma/ale.py +951 -0
- nimare/meta/cbma/base.py +947 -0
- nimare/meta/cbma/mkda.py +1361 -0
- nimare/meta/cbmr.py +970 -0
- nimare/meta/ibma.py +1683 -0
- nimare/meta/kernel.py +501 -0
- nimare/meta/models.py +1199 -0
- nimare/meta/utils.py +494 -0
- nimare/nimads.py +492 -0
- nimare/reports/__init__.py +24 -0
- nimare/reports/base.py +664 -0
- nimare/reports/default.yml +123 -0
- nimare/reports/figures.py +651 -0
- nimare/reports/report.tpl +160 -0
- nimare/resources/__init__.py +1 -0
- nimare/resources/atlases/Harvard-Oxford-LICENSE +93 -0
- nimare/resources/atlases/HarvardOxford-cort-maxprob-thr25-2mm.nii.gz +0 -0
- nimare/resources/database_file_manifest.json +142 -0
- nimare/resources/english_spellings.csv +1738 -0
- nimare/resources/filenames.json +32 -0
- nimare/resources/neurosynth_laird_studies.json +58773 -0
- nimare/resources/neurosynth_stoplist.txt +396 -0
- nimare/resources/nidm_pain_dset.json +1349 -0
- nimare/resources/references.bib +541 -0
- nimare/resources/semantic_knowledge_children.txt +325 -0
- nimare/resources/semantic_relatedness_children.txt +249 -0
- nimare/resources/templates/MNI152_2x2x2_brainmask.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-01_T1w.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-01_desc-brain_mask.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-02_T1w.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-02_desc-brain_mask.nii.gz +0 -0
- nimare/results.py +225 -0
- nimare/stats.py +276 -0
- nimare/tests/__init__.py +1 -0
- nimare/tests/conftest.py +229 -0
- nimare/tests/data/amygdala_roi.nii.gz +0 -0
- nimare/tests/data/data-neurosynth_version-7_coordinates.tsv.gz +0 -0
- nimare/tests/data/data-neurosynth_version-7_metadata.tsv.gz +0 -0
- nimare/tests/data/data-neurosynth_version-7_vocab-terms_source-abstract_type-tfidf_features.npz +0 -0
- nimare/tests/data/data-neurosynth_version-7_vocab-terms_vocabulary.txt +100 -0
- nimare/tests/data/neurosynth_dset.json +2868 -0
- nimare/tests/data/neurosynth_laird_studies.json +58773 -0
- nimare/tests/data/nidm_pain_dset.json +1349 -0
- nimare/tests/data/nimads_annotation.json +1 -0
- nimare/tests/data/nimads_studyset.json +1 -0
- nimare/tests/data/test_baseline.txt +2 -0
- nimare/tests/data/test_pain_dataset.json +1278 -0
- nimare/tests/data/test_pain_dataset_multiple_contrasts.json +1242 -0
- nimare/tests/data/test_sleuth_file.txt +18 -0
- nimare/tests/data/test_sleuth_file2.txt +10 -0
- nimare/tests/data/test_sleuth_file3.txt +5 -0
- nimare/tests/data/test_sleuth_file4.txt +5 -0
- nimare/tests/data/test_sleuth_file5.txt +5 -0
- nimare/tests/test_annotate_cogat.py +32 -0
- nimare/tests/test_annotate_gclda.py +86 -0
- nimare/tests/test_annotate_lda.py +27 -0
- nimare/tests/test_dataset.py +99 -0
- nimare/tests/test_decode_continuous.py +132 -0
- nimare/tests/test_decode_discrete.py +92 -0
- nimare/tests/test_diagnostics.py +168 -0
- nimare/tests/test_estimator_performance.py +385 -0
- nimare/tests/test_extract.py +46 -0
- nimare/tests/test_generate.py +247 -0
- nimare/tests/test_io.py +294 -0
- nimare/tests/test_meta_ale.py +298 -0
- nimare/tests/test_meta_cbmr.py +295 -0
- nimare/tests/test_meta_ibma.py +240 -0
- nimare/tests/test_meta_kernel.py +209 -0
- nimare/tests/test_meta_mkda.py +234 -0
- nimare/tests/test_nimads.py +21 -0
- nimare/tests/test_reports.py +110 -0
- nimare/tests/test_stats.py +101 -0
- nimare/tests/test_transforms.py +272 -0
- nimare/tests/test_utils.py +200 -0
- nimare/tests/test_workflows.py +221 -0
- nimare/tests/utils.py +126 -0
- nimare/transforms.py +907 -0
- nimare/utils.py +1367 -0
- nimare/workflows/__init__.py +14 -0
- nimare/workflows/base.py +189 -0
- nimare/workflows/cbma.py +165 -0
- nimare/workflows/ibma.py +108 -0
- nimare/workflows/macm.py +77 -0
- nimare/workflows/misc.py +65 -0
- nimare-0.4.2.dist-info/LICENSE +21 -0
- nimare-0.4.2.dist-info/METADATA +124 -0
- nimare-0.4.2.dist-info/RECORD +119 -0
- nimare-0.4.2.dist-info/WHEEL +5 -0
- nimare-0.4.2.dist-info/entry_points.txt +2 -0
- nimare-0.4.2.dist-info/top_level.txt +2 -0
nimare/meta/cbma/mkda.py
ADDED
@@ -0,0 +1,1361 @@
|
|
1
|
+
"""CBMA methods from the multilevel kernel density analysis (MKDA) family."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import nibabel as nib
|
6
|
+
import numpy as np
|
7
|
+
import sparse
|
8
|
+
from joblib import Memory, Parallel, delayed
|
9
|
+
from pymare.stats import fdr
|
10
|
+
from scipy import ndimage
|
11
|
+
from scipy.stats import chi2
|
12
|
+
from tqdm.auto import tqdm
|
13
|
+
|
14
|
+
from nimare import _version
|
15
|
+
from nimare.meta.cbma.base import CBMAEstimator, PairwiseCBMAEstimator
|
16
|
+
from nimare.meta.kernel import KDAKernel, MKDAKernel
|
17
|
+
from nimare.meta.utils import _calculate_cluster_measures
|
18
|
+
from nimare.stats import null_to_p, one_way, two_way
|
19
|
+
from nimare.transforms import p_to_z
|
20
|
+
from nimare.utils import _check_ncores, vox2mm
|
21
|
+
|
22
|
+
LGR = logging.getLogger(__name__)
|
23
|
+
__version__ = _version.get_versions()["version"]
|
24
|
+
|
25
|
+
|
26
|
+
class MKDADensity(CBMAEstimator):
|
27
|
+
r"""Multilevel kernel density analysis- Density analysis.
|
28
|
+
|
29
|
+
The MKDA density method was originally introduced in :footcite:t:`wager2007meta`.
|
30
|
+
|
31
|
+
.. versionchanged:: 0.2.1
|
32
|
+
|
33
|
+
- New parameters: ``memory`` and ``memory_level`` for memory caching.
|
34
|
+
|
35
|
+
.. versionchanged:: 0.0.12
|
36
|
+
|
37
|
+
- Use a 4D sparse array for modeled activation maps.
|
38
|
+
|
39
|
+
Parameters
|
40
|
+
----------
|
41
|
+
kernel_transformer : :obj:`~nimare.meta.kernel.KernelTransformer`, optional
|
42
|
+
Kernel with which to convolve coordinates from dataset. Default is
|
43
|
+
:class:`~nimare.meta.kernel.MKDAKernel`.
|
44
|
+
null_method : {"approximate", "montecarlo"}, optional
|
45
|
+
Method by which to determine uncorrected p-values. The available options are
|
46
|
+
|
47
|
+
======================= =================================================================
|
48
|
+
"approximate" (default) Build a histogram of summary-statistic values and their
|
49
|
+
expected frequencies under the assumption of random spatial
|
50
|
+
associated between studies, via a weighted convolution.
|
51
|
+
|
52
|
+
This method is much faster, but slightly less accurate.
|
53
|
+
"montecarlo" Perform a large number of permutations, in which the coordinates
|
54
|
+
in the studies are randomly drawn from the Estimator's brain mask
|
55
|
+
and the full set of resulting summary-statistic values are
|
56
|
+
incorporated into a null distribution (stored as a histogram for
|
57
|
+
memory reasons).
|
58
|
+
|
59
|
+
This method is must slower, and is only slightly more accurate.
|
60
|
+
======================= =================================================================
|
61
|
+
|
62
|
+
n_iters : int, default=5000
|
63
|
+
Number of iterations to use to define the null distribution.
|
64
|
+
This is only used if ``null_method=="montecarlo"``.
|
65
|
+
Default is 5000.
|
66
|
+
memory : instance of :class:`joblib.Memory`, :obj:`str`, or :class:`pathlib.Path`
|
67
|
+
Used to cache the output of a function. By default, no caching is done.
|
68
|
+
If a :obj:`str` is given, it is the path to the caching directory.
|
69
|
+
memory_level : :obj:`int`, default=0
|
70
|
+
Rough estimator of the amount of memory used by caching.
|
71
|
+
Higher value means more memory for caching. Zero means no caching.
|
72
|
+
n_cores : :obj:`int`, optional
|
73
|
+
Number of cores to use for parallelization.
|
74
|
+
This is only used if ``null_method=="montecarlo"``.
|
75
|
+
If <=0, defaults to using all available cores.
|
76
|
+
Default is 1.
|
77
|
+
**kwargs
|
78
|
+
Keyword arguments. Arguments for the kernel_transformer can be assigned
|
79
|
+
here, with the prefix '\kernel__' in the variable name.
|
80
|
+
|
81
|
+
Attributes
|
82
|
+
----------
|
83
|
+
masker : :class:`~nilearn.input_data.NiftiMasker` or similar
|
84
|
+
Masker object.
|
85
|
+
inputs_ : :obj:`dict`
|
86
|
+
Inputs to the Estimator. For CBMA estimators, there is only one key: coordinates.
|
87
|
+
This is an edited version of the dataset's coordinates DataFrame.
|
88
|
+
null_distributions_ : :obj:`dict` of :class:`numpy.ndarray`
|
89
|
+
Null distributions for the uncorrected summary-statistic-to-p-value conversion and any
|
90
|
+
multiple-comparisons correction methods.
|
91
|
+
Entries are added to this attribute if and when the corresponding method is applied.
|
92
|
+
|
93
|
+
If ``null_method == "approximate"``:
|
94
|
+
|
95
|
+
- ``histogram_means``: Array of mean value per experiment.
|
96
|
+
- ``histogram_bins``: Array of bin centers for the null distribution histogram,
|
97
|
+
ranging from zero to the maximum possible summary statistic value for the Dataset.
|
98
|
+
- ``histweights_corr-none_method-approximate``: Array of weights for the null
|
99
|
+
distribution histogram, with one value for each bin in ``histogram_bins``.
|
100
|
+
|
101
|
+
If ``null_method == "montecarlo"``:
|
102
|
+
|
103
|
+
- ``histogram_bins``: Array of bin centers for the null distribution histogram,
|
104
|
+
ranging from zero to the maximum possible summary statistic value for the Dataset.
|
105
|
+
- ``histweights_corr-none_method-montecarlo``: Array of weights for the null
|
106
|
+
distribution histogram, with one value for each bin in ``histogram_bins``.
|
107
|
+
These values are derived from the full set of summary statistics from each
|
108
|
+
iteration of the Monte Carlo procedure.
|
109
|
+
- ``histweights_level-voxel_corr-fwe_method-montecarlo``: Array of weights for the
|
110
|
+
voxel-level FWE-correction null distribution, with one value for each bin in
|
111
|
+
``histogram_bins``. These values are derived from the maximum summary statistic
|
112
|
+
from each iteration of the Monte Carlo procedure.
|
113
|
+
|
114
|
+
If :meth:`correct_fwe_montecarlo` is applied:
|
115
|
+
|
116
|
+
- ``values_level-voxel_corr-fwe_method-montecarlo``: The maximum summary statistic
|
117
|
+
value from each Monte Carlo iteration. An array of shape (n_iters,).
|
118
|
+
- ``values_desc-size_level-cluster_corr-fwe_method-montecarlo``: The maximum cluster
|
119
|
+
size from each Monte Carlo iteration. An array of shape (n_iters,).
|
120
|
+
- ``values_desc-mass_level-cluster_corr-fwe_method-montecarlo``: The maximum cluster
|
121
|
+
mass from each Monte Carlo iteration. An array of shape (n_iters,).
|
122
|
+
|
123
|
+
Notes
|
124
|
+
-----
|
125
|
+
The MKDA density algorithm is also implemented in MATLAB at
|
126
|
+
https://github.com/canlab/Canlab_MKDA_MetaAnalysis.
|
127
|
+
|
128
|
+
Available correction methods: :func:`MKDADensity.correct_fwe_montecarlo`
|
129
|
+
|
130
|
+
References
|
131
|
+
----------
|
132
|
+
.. footbibliography::
|
133
|
+
"""
|
134
|
+
|
135
|
+
def __init__(
|
136
|
+
self,
|
137
|
+
kernel_transformer=MKDAKernel,
|
138
|
+
null_method="approximate",
|
139
|
+
n_iters=5000,
|
140
|
+
memory=Memory(location=None, verbose=0),
|
141
|
+
memory_level=0,
|
142
|
+
n_cores=1,
|
143
|
+
**kwargs,
|
144
|
+
):
|
145
|
+
if not (isinstance(kernel_transformer, MKDAKernel) or kernel_transformer == MKDAKernel):
|
146
|
+
LGR.warning(
|
147
|
+
f"The KernelTransformer being used ({kernel_transformer}) is not optimized "
|
148
|
+
f"for the {type(self).__name__} algorithm. "
|
149
|
+
"Expect suboptimal performance and beware bugs."
|
150
|
+
)
|
151
|
+
|
152
|
+
# Add kernel transformer attribute and process keyword arguments
|
153
|
+
super().__init__(
|
154
|
+
kernel_transformer=kernel_transformer,
|
155
|
+
memory=memory,
|
156
|
+
memory_level=memory_level,
|
157
|
+
**kwargs,
|
158
|
+
)
|
159
|
+
self.null_method = null_method
|
160
|
+
self.n_iters = None if null_method == "approximate" else n_iters or 5000
|
161
|
+
self.n_cores = _check_ncores(n_cores)
|
162
|
+
self.dataset = None
|
163
|
+
|
164
|
+
def _generate_description(self):
|
165
|
+
"""Generate a description of the fitted Estimator.
|
166
|
+
|
167
|
+
Returns
|
168
|
+
-------
|
169
|
+
str
|
170
|
+
Description of the Estimator.
|
171
|
+
"""
|
172
|
+
if self.null_method == "montecarlo":
|
173
|
+
null_method_str = (
|
174
|
+
"a Monte Carlo-based null distribution, in which dataset coordinates were "
|
175
|
+
"randomly drawn from the analysis mask and the full set of ALE values were "
|
176
|
+
f"retained, using {self.n_iters} iterations"
|
177
|
+
)
|
178
|
+
else:
|
179
|
+
null_method_str = "an approximate null distribution"
|
180
|
+
|
181
|
+
description = (
|
182
|
+
"A multilevel kernel density (MKDA) meta-analysis \\citep{wager2007meta} was "
|
183
|
+
"performed was performed with NiMARE "
|
184
|
+
f"{__version__} "
|
185
|
+
"(RRID:SCR_017398; \\citealt{Salo2023}), using a(n) "
|
186
|
+
f"{self.kernel_transformer.__class__.__name__.replace('Kernel', '')} kernel. "
|
187
|
+
f"{self.kernel_transformer._generate_description()} "
|
188
|
+
f"Summary statistics (OF values) were converted to p-values using {null_method_str}. "
|
189
|
+
f"The input dataset included {self.inputs_['coordinates'].shape[0]} foci from "
|
190
|
+
f"{len(self.inputs_['id'])} experiments."
|
191
|
+
)
|
192
|
+
return description
|
193
|
+
|
194
|
+
def _compute_weights(self, ma_values):
|
195
|
+
"""Determine experiment-wise weights per the conventional MKDA approach."""
|
196
|
+
# TODO: Incorporate sample-size and inference metadata extraction and
|
197
|
+
# merging into df.
|
198
|
+
# This will need to be distinct from the kernel_transformer-based kind
|
199
|
+
# done in CBMAEstimator._preprocess_input
|
200
|
+
ids_df = self.inputs_["coordinates"].groupby("id").first()
|
201
|
+
|
202
|
+
n_exp = len(ids_df)
|
203
|
+
|
204
|
+
# Default to unit weighting for missing inference or sample size
|
205
|
+
if "inference" not in ids_df.columns:
|
206
|
+
ids_df["inference"] = "rfx"
|
207
|
+
if "sample_size" not in ids_df.columns:
|
208
|
+
ids_df["sample_size"] = 1.0
|
209
|
+
|
210
|
+
n = ids_df["sample_size"].astype(float).values
|
211
|
+
inf = ids_df["inference"].map({"ffx": 0.75, "rfx": 1.0}).values
|
212
|
+
|
213
|
+
weight_vec = n_exp * ((np.sqrt(n) * inf) / np.sum(np.sqrt(n) * inf))
|
214
|
+
weight_vec = weight_vec[:, None]
|
215
|
+
|
216
|
+
assert weight_vec.shape[0] == ma_values.shape[0]
|
217
|
+
return weight_vec
|
218
|
+
|
219
|
+
def _compute_summarystat_est(self, ma_values):
|
220
|
+
ma_values = ma_values.reshape((ma_values.shape[0], -1))
|
221
|
+
stat_values = ma_values.T.dot(self.weight_vec_)
|
222
|
+
|
223
|
+
if isinstance(ma_values, sparse._coo.core.COO):
|
224
|
+
# NOTE: This may not work correctly with a non-NiftiMasker.
|
225
|
+
mask_data = self.masker.mask_img.get_fdata().astype(bool)
|
226
|
+
|
227
|
+
stat_values = stat_values[mask_data.reshape(-1)].ravel()
|
228
|
+
# This is used by _compute_null_approximate
|
229
|
+
self.__n_mask_voxels = stat_values.shape[0]
|
230
|
+
else:
|
231
|
+
# np.array type is used by _compute_null_reduced_montecarlo
|
232
|
+
stat_values = stat_values.ravel()
|
233
|
+
|
234
|
+
return stat_values
|
235
|
+
|
236
|
+
def _determine_histogram_bins(self, ma_maps):
|
237
|
+
"""Determine histogram bins for null distribution methods.
|
238
|
+
|
239
|
+
Parameters
|
240
|
+
----------
|
241
|
+
ma_maps : :obj:`sparse._coo.core.COO`
|
242
|
+
MA maps.
|
243
|
+
The ma_maps can be a 4d sparse array of MA maps,
|
244
|
+
|
245
|
+
Notes
|
246
|
+
-----
|
247
|
+
This method adds two entries to the null_distributions_ dict attribute: "histogram_bins",
|
248
|
+
and "histogram_means" only if ``null_method == "approximate"``.
|
249
|
+
"""
|
250
|
+
if not isinstance(ma_maps, sparse._coo.core.COO):
|
251
|
+
raise ValueError(f"Unsupported data type '{type(ma_maps)}'")
|
252
|
+
|
253
|
+
n_exp = ma_maps.shape[0]
|
254
|
+
prop_active = np.zeros(n_exp)
|
255
|
+
data = ma_maps.data
|
256
|
+
coords = ma_maps.coords
|
257
|
+
for exp_idx in range(n_exp):
|
258
|
+
# The first column of coords is the fourth dimension of the dense array
|
259
|
+
study_ma_values = data[coords[0, :] == exp_idx]
|
260
|
+
|
261
|
+
n_nonzero_voxels = study_ma_values.shape[0]
|
262
|
+
n_zero_voxels = self.__n_mask_voxels - n_nonzero_voxels
|
263
|
+
|
264
|
+
prop_active[exp_idx] = np.mean(np.hstack([study_ma_values, np.zeros(n_zero_voxels)]))
|
265
|
+
|
266
|
+
self.null_distributions_["histogram_bins"] = np.arange(len(prop_active) + 1, step=1)
|
267
|
+
|
268
|
+
if self.null_method.startswith("approximate"):
|
269
|
+
# To speed things up in _compute_null_approximate, we save the means too,
|
270
|
+
self.null_distributions_["histogram_means"] = prop_active
|
271
|
+
|
272
|
+
def _compute_null_approximate(self, ma_maps):
|
273
|
+
"""Compute uncorrected null distribution using approximate solution.
|
274
|
+
|
275
|
+
Parameters
|
276
|
+
----------
|
277
|
+
ma_maps
|
278
|
+
Modeled activation maps. Unused for this estimator.
|
279
|
+
|
280
|
+
Notes
|
281
|
+
-----
|
282
|
+
This method adds one entry to the null_distributions_ dict attribute: "histogram_weights".
|
283
|
+
"""
|
284
|
+
assert "histogram_means" in self.null_distributions_.keys()
|
285
|
+
|
286
|
+
# MKDA maps are binary, so we only have k + 1 bins in the final
|
287
|
+
# histogram, where k is the number of studies. We can analytically
|
288
|
+
# compute the null distribution by convolution.
|
289
|
+
# prop_active contains the mean value per experiment
|
290
|
+
prop_active = self.null_distributions_["histogram_means"]
|
291
|
+
|
292
|
+
ss_hist = 1.0
|
293
|
+
for exp_prop in prop_active:
|
294
|
+
ss_hist = np.convolve(ss_hist, [1 - exp_prop, exp_prop])
|
295
|
+
|
296
|
+
self.null_distributions_["histweights_corr-none_method-approximate"] = ss_hist
|
297
|
+
|
298
|
+
|
299
|
+
class MKDAChi2(PairwiseCBMAEstimator):
|
300
|
+
r"""Multilevel kernel density analysis- Chi-square analysis.
|
301
|
+
|
302
|
+
The MKDA chi-square method was originally introduced in :footcite:t:`wager2007meta`.
|
303
|
+
|
304
|
+
.. versionchanged:: 0.2.1
|
305
|
+
|
306
|
+
- Make `prior` parameter default to None, which controls if posterior probabilities
|
307
|
+
pFgA, pAgF_prior and pFgA_prior are calculated. This is useful because probability
|
308
|
+
maps are difficult to interpret and for speeding up the algorithm.
|
309
|
+
- Rename ``consistency`` to ``uniformity`` and ``specificity`` to ``association`` to match
|
310
|
+
Neurosynth's terminology
|
311
|
+
- New parameters: ``memory`` and ``memory_level`` for memory caching.
|
312
|
+
|
313
|
+
.. versionchanged:: 0.0.12
|
314
|
+
|
315
|
+
- Use a 4D sparse array for modeled activation maps.
|
316
|
+
|
317
|
+
.. versionchanged:: 0.0.8
|
318
|
+
|
319
|
+
* [REF] Use saved MA maps, when available.
|
320
|
+
|
321
|
+
Parameters
|
322
|
+
----------
|
323
|
+
kernel_transformer : :obj:`~nimare.meta.kernel.KernelTransformer`, optional
|
324
|
+
Kernel with which to convolve coordinates from dataset. Default is
|
325
|
+
:class:`~nimare.meta.kernel.MKDAKernel`.
|
326
|
+
prior : float, default=0.5
|
327
|
+
Uniform prior probability of each feature being active in a map in
|
328
|
+
the absence of evidence from the map. Default: 0.5
|
329
|
+
memory : instance of :class:`joblib.Memory`, :obj:`str`, or :class:`pathlib.Path`
|
330
|
+
Used to cache the output of a function. By default, no caching is done.
|
331
|
+
If a :obj:`str` is given, it is the path to the caching directory.
|
332
|
+
memory_level : :obj:`int`, default=0
|
333
|
+
Rough estimator of the amount of memory used by caching.
|
334
|
+
Higher value means more memory for caching. Zero means no caching.
|
335
|
+
**kwargs
|
336
|
+
Keyword arguments. Arguments for the kernel_transformer can be assigned
|
337
|
+
here, with the prefix '\kernel__' in the variable name.
|
338
|
+
|
339
|
+
Attributes
|
340
|
+
----------
|
341
|
+
masker : :class:`~nilearn.input_data.NiftiMasker` or similar
|
342
|
+
Masker object.
|
343
|
+
inputs_ : :obj:`dict`
|
344
|
+
Inputs to the Estimator. For CBMA estimators, there is only one key: coordinates.
|
345
|
+
This is an edited version of the dataset's coordinates DataFrame.
|
346
|
+
null_distributions_ : :obj:`dict` of :class:`numpy.ndarray`
|
347
|
+
Null distributions for any multiple-comparisons correction methods.
|
348
|
+
|
349
|
+
.. important::
|
350
|
+
MKDAChi2 does not retain uncorrected summary-statistic-to-p null distributions,
|
351
|
+
since the summary statistic in this case is the chi-squared value, which has an
|
352
|
+
established null distribution.
|
353
|
+
|
354
|
+
Entries are added to this attribute if and when the corresponding method is applied.
|
355
|
+
|
356
|
+
If :meth:`correct_fwe_montecarlo` is applied:
|
357
|
+
|
358
|
+
- ``values_desc-pAgF_level-voxel_corr-fwe_method-montecarlo``: The maximum
|
359
|
+
chi-squared value from the p(A|F) one-way chi-squared test from each Monte Carlo
|
360
|
+
iteration. An array of shape (n_iters,).
|
361
|
+
- ``values_desc-pAgFsize_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
362
|
+
cluster size value from the p(A|F) one-way chi-squared test from each Monte Carlo
|
363
|
+
iteration. An array of shape (n_iters,).
|
364
|
+
- ``values_desc-pAgFmass_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
365
|
+
cluster mass value from the p(A|F) one-way chi-squared test from each Monte Carlo
|
366
|
+
iteration. An array of shape (n_iters,).
|
367
|
+
- ``values_desc-pFgA_level-voxel_corr-fwe_method-montecarlo``: The maximum
|
368
|
+
chi-squared value from the p(F|A) two-way chi-squared test from each Monte Carlo
|
369
|
+
iteration. An array of shape (n_iters,).
|
370
|
+
- ``values_desc-pFgAsize_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
371
|
+
cluster size value from the p(F|A) two-way chi-squared test from each Monte Carlo
|
372
|
+
iteration. An array of shape (n_iters,).
|
373
|
+
- ``values_desc-pFgAmass_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
374
|
+
cluster mass value from the p(F|A) two-way chi-squared test from each Monte Carlo
|
375
|
+
iteration. An array of shape (n_iters,).
|
376
|
+
|
377
|
+
Notes
|
378
|
+
-----
|
379
|
+
The MKDA Chi-square algorithm was originally implemented as part of the Neurosynth Python
|
380
|
+
library (https://github.com/neurosynth/neurosynth).
|
381
|
+
|
382
|
+
Available correction methods: :meth:`MKDAChi2.correct_fwe_montecarlo`,
|
383
|
+
:meth:`MKDAChi2.correct_fdr_indep`.
|
384
|
+
|
385
|
+
References
|
386
|
+
----------
|
387
|
+
.. footbibliography::
|
388
|
+
"""
|
389
|
+
|
390
|
+
def __init__(
|
391
|
+
self,
|
392
|
+
kernel_transformer=MKDAKernel,
|
393
|
+
prior=0.5,
|
394
|
+
memory=Memory(location=None, verbose=0),
|
395
|
+
memory_level=0,
|
396
|
+
**kwargs,
|
397
|
+
):
|
398
|
+
if not (isinstance(kernel_transformer, MKDAKernel) or kernel_transformer == MKDAKernel):
|
399
|
+
LGR.warning(
|
400
|
+
f"The KernelTransformer being used ({kernel_transformer}) is not optimized "
|
401
|
+
f"for the {type(self).__name__} algorithm. "
|
402
|
+
"Expect suboptimal performance and beware bugs."
|
403
|
+
)
|
404
|
+
|
405
|
+
# Add kernel transformer attribute and process keyword arguments
|
406
|
+
super().__init__(
|
407
|
+
kernel_transformer=kernel_transformer,
|
408
|
+
memory=memory,
|
409
|
+
memory_level=memory_level,
|
410
|
+
**kwargs,
|
411
|
+
)
|
412
|
+
|
413
|
+
self.prior = prior
|
414
|
+
|
415
|
+
def _generate_description(self):
|
416
|
+
description = (
|
417
|
+
"A multilevel kernel density chi-squared analysis \\citep{wager2007meta} was "
|
418
|
+
"performed according to the same procedure as implemented in Neurosynth with NiMARE "
|
419
|
+
f"{__version__} "
|
420
|
+
"(RRID:SCR_017398; \\citealt{Salo2023}), "
|
421
|
+
f"using a(n) {self.kernel_transformer.__class__.__name__.replace('Kernel', '')} "
|
422
|
+
"kernel. "
|
423
|
+
f"{self.kernel_transformer._generate_description()} "
|
424
|
+
"This analysis calculated several measures. "
|
425
|
+
"The first dataset was evaluated for uniformity of activation via a one-way "
|
426
|
+
"chi-square test. "
|
427
|
+
f"The first input dataset included {self.inputs_['coordinates1'].shape[0]} foci from "
|
428
|
+
f"{len(self.inputs_['id1'])} experiments. "
|
429
|
+
f"The second input dataset included {self.inputs_['coordinates2'].shape[0]} foci from "
|
430
|
+
f"{len(self.inputs_['id2'])} experiments."
|
431
|
+
)
|
432
|
+
|
433
|
+
return description
|
434
|
+
|
435
|
+
def _fit(self, dataset1, dataset2):
|
436
|
+
self.dataset1 = dataset1
|
437
|
+
self.dataset2 = dataset2
|
438
|
+
self.masker = self.masker or dataset1.masker
|
439
|
+
self.null_distributions_ = {}
|
440
|
+
|
441
|
+
# Generate MA maps and calculate count variables for first dataset
|
442
|
+
n_selected_active_voxels = self._collect_ma_maps(
|
443
|
+
maps_key="ma_maps1",
|
444
|
+
coords_key="coordinates1",
|
445
|
+
return_type="summary_array",
|
446
|
+
)
|
447
|
+
|
448
|
+
n_selected = self.dataset1.coordinates["id"].unique().shape[0]
|
449
|
+
|
450
|
+
# Generate MA maps and calculate count variables for second dataset
|
451
|
+
n_unselected_active_voxels = self._collect_ma_maps(
|
452
|
+
maps_key="ma_maps2",
|
453
|
+
coords_key="coordinates2",
|
454
|
+
return_type="summary_array",
|
455
|
+
)
|
456
|
+
n_unselected = self.dataset2.coordinates["id"].unique().shape[0]
|
457
|
+
|
458
|
+
n_mappables = n_selected + n_unselected
|
459
|
+
|
460
|
+
# Nomenclature for variables below: p = probability,
|
461
|
+
# F = feature present, g = given, U = unselected, A = activation.
|
462
|
+
# So, e.g., pAgF = p(A|F) = probability of activation
|
463
|
+
# in a voxel if we know that the feature is present in a study.
|
464
|
+
pF = n_selected / n_mappables
|
465
|
+
pA = np.array(
|
466
|
+
(n_selected_active_voxels + n_unselected_active_voxels) / n_mappables
|
467
|
+
).squeeze()
|
468
|
+
|
469
|
+
del n_mappables
|
470
|
+
|
471
|
+
pAgF = n_selected_active_voxels / n_selected
|
472
|
+
pAgU = n_unselected_active_voxels / n_unselected
|
473
|
+
pFgA = pAgF * pF / pA
|
474
|
+
|
475
|
+
del pF
|
476
|
+
|
477
|
+
if self.prior:
|
478
|
+
# Recompute conditionals with uniform prior
|
479
|
+
pAgF_prior = self.prior * pAgF + (1 - self.prior) * pAgU
|
480
|
+
pFgA_prior = pAgF * self.prior / pAgF_prior
|
481
|
+
|
482
|
+
# One-way chi-square test for uniformity of activation
|
483
|
+
pAgF_chi2_vals = one_way(np.squeeze(n_selected_active_voxels), n_selected)
|
484
|
+
pAgF_p_vals = chi2.sf(pAgF_chi2_vals, 1)
|
485
|
+
pAgF_sign = np.sign(n_selected_active_voxels - np.mean(n_selected_active_voxels))
|
486
|
+
pAgF_z = p_to_z(pAgF_p_vals, tail="two") * pAgF_sign
|
487
|
+
|
488
|
+
del pAgF_sign
|
489
|
+
|
490
|
+
# Two-way chi-square for association of activation
|
491
|
+
cells = np.squeeze(
|
492
|
+
np.array(
|
493
|
+
[
|
494
|
+
[n_selected_active_voxels, n_unselected_active_voxels],
|
495
|
+
[
|
496
|
+
n_selected - n_selected_active_voxels,
|
497
|
+
n_unselected - n_unselected_active_voxels,
|
498
|
+
],
|
499
|
+
]
|
500
|
+
).T
|
501
|
+
)
|
502
|
+
|
503
|
+
del n_selected, n_unselected
|
504
|
+
|
505
|
+
pFgA_chi2_vals = two_way(cells)
|
506
|
+
|
507
|
+
del n_selected_active_voxels, n_unselected_active_voxels
|
508
|
+
|
509
|
+
eps = np.spacing(1)
|
510
|
+
pFgA_p_vals = chi2.sf(pFgA_chi2_vals, 1)
|
511
|
+
pFgA_p_vals[pFgA_p_vals < eps] = eps
|
512
|
+
pFgA_sign = np.sign(pAgF - pAgU).ravel()
|
513
|
+
pFgA_z = p_to_z(pFgA_p_vals, tail="two") * pFgA_sign
|
514
|
+
|
515
|
+
del pFgA_sign, pAgU
|
516
|
+
|
517
|
+
maps = {
|
518
|
+
"z_desc-uniformity": pAgF_z,
|
519
|
+
"z_desc-association": pFgA_z,
|
520
|
+
"chi2_desc-uniformity": pAgF_chi2_vals,
|
521
|
+
"chi2_desc-association": pFgA_chi2_vals,
|
522
|
+
"p_desc-uniformity": pAgF_p_vals,
|
523
|
+
"p_desc-association": pFgA_p_vals,
|
524
|
+
"prob_desc-A": pA,
|
525
|
+
"prob_desc-AgF": pAgF,
|
526
|
+
"prob_desc-FgA": pFgA,
|
527
|
+
}
|
528
|
+
|
529
|
+
if self.prior:
|
530
|
+
maps["prob_desc-AgF_prior"] = pAgF_prior
|
531
|
+
maps["prob_desc-FgA_prior"] = pFgA_prior
|
532
|
+
|
533
|
+
description = self._generate_description()
|
534
|
+
return maps, {}, description
|
535
|
+
|
536
|
+
def _run_fwe_permutation(self, iter_xyz1, iter_xyz2, iter_df1, iter_df2, conn, voxel_thresh):
|
537
|
+
"""Run a single permutation of the Monte Carlo FWE correction procedure.
|
538
|
+
|
539
|
+
Parameters
|
540
|
+
----------
|
541
|
+
iter_xyz1, iter_xyz2 : :obj:`numpy.ndarray`
|
542
|
+
Random coordinates for the permutation.
|
543
|
+
iter_df1, iter_df2 : :obj:`pandas.DataFrame`
|
544
|
+
DataFrames with as many rows as there are coordinates in each of the two datasets,
|
545
|
+
to be filled in with random coordinates for the permutation.
|
546
|
+
conn : :obj:`numpy.ndarray` of shape (3, 3, 3)
|
547
|
+
Connectivity matrix for defining clusters.
|
548
|
+
voxel_thresh : :obj:`float`
|
549
|
+
Uncorrected summary-statistic thresholded for defining clusters.
|
550
|
+
|
551
|
+
Returns
|
552
|
+
-------
|
553
|
+
pAgF_max_chi2_value : :obj:`float`
|
554
|
+
Forward inference maximum chi-squared value, for voxel-level FWE correction.
|
555
|
+
pAgF_max_size : :obj:`float`
|
556
|
+
Forward inference maximum cluster size, for cluster-level FWE correction.
|
557
|
+
pAgF_max_mass : :obj:`float`
|
558
|
+
Forward inference maximum cluster mass, for cluster-level FWE correction.
|
559
|
+
pFgA_max_chi2_value : :obj:`float`
|
560
|
+
Reverse inference maximum chi-squared value, for voxel-level FWE correction.
|
561
|
+
pFgA_max_size : :obj:`float`
|
562
|
+
Reverse inference maximum cluster size, for cluster-level FWE correction.
|
563
|
+
pFgA_max_mass : :obj:`float`
|
564
|
+
Reverse inference maximum cluster mass, for cluster-level FWE correction.
|
565
|
+
"""
|
566
|
+
# Not sure if joblib will automatically use a copy of the object, but I'll make a copy to
|
567
|
+
# be safe.
|
568
|
+
iter_df1 = iter_df1.copy()
|
569
|
+
iter_df2 = iter_df2.copy()
|
570
|
+
|
571
|
+
iter_xyz1 = np.squeeze(iter_xyz1)
|
572
|
+
iter_xyz2 = np.squeeze(iter_xyz2)
|
573
|
+
iter_df1[["x", "y", "z"]] = iter_xyz1
|
574
|
+
iter_df2[["x", "y", "z"]] = iter_xyz2
|
575
|
+
|
576
|
+
# Generate MA maps and calculate count variables for first dataset
|
577
|
+
n_selected_active_voxels = self.kernel_transformer.transform(
|
578
|
+
iter_df1, self.masker, return_type="summary_array"
|
579
|
+
)
|
580
|
+
|
581
|
+
n_selected = self.dataset1.coordinates["id"].unique().shape[0]
|
582
|
+
|
583
|
+
# Generate MA maps and calculate count variables for second dataset
|
584
|
+
n_unselected_active_voxels = self.kernel_transformer.transform(
|
585
|
+
iter_df2, self.masker, return_type="summary_array"
|
586
|
+
)
|
587
|
+
n_unselected = self.dataset2.coordinates["id"].unique().shape[0]
|
588
|
+
|
589
|
+
# Currently unused conditional probabilities
|
590
|
+
# pAgF = n_selected_active_voxels / n_selected
|
591
|
+
# pAgU = n_unselected_active_voxels / n_unselected
|
592
|
+
|
593
|
+
# One-way chi-square test for uniformity of activation
|
594
|
+
pAgF_chi2_vals = one_way(np.squeeze(n_selected_active_voxels), n_selected)
|
595
|
+
|
596
|
+
# Voxel-level inference
|
597
|
+
pAgF_max_chi2_value = np.max(np.abs(pAgF_chi2_vals))
|
598
|
+
|
599
|
+
# Cluster-level inference
|
600
|
+
pAgF_chi2_map = self.masker.inverse_transform(pAgF_chi2_vals).get_fdata()
|
601
|
+
pAgF_max_size, pAgF_max_mass = _calculate_cluster_measures(
|
602
|
+
pAgF_chi2_map, voxel_thresh, conn, tail="two"
|
603
|
+
)
|
604
|
+
|
605
|
+
# Two-way chi-square for association of activation
|
606
|
+
cells = np.squeeze(
|
607
|
+
np.array(
|
608
|
+
[
|
609
|
+
[n_selected_active_voxels, n_unselected_active_voxels],
|
610
|
+
[
|
611
|
+
n_selected - n_selected_active_voxels,
|
612
|
+
n_unselected - n_unselected_active_voxels,
|
613
|
+
],
|
614
|
+
]
|
615
|
+
).T
|
616
|
+
)
|
617
|
+
pFgA_chi2_vals = two_way(cells)
|
618
|
+
|
619
|
+
# Voxel-level inference
|
620
|
+
pFgA_max_chi2_value = np.max(np.abs(pFgA_chi2_vals))
|
621
|
+
|
622
|
+
# Cluster-level inference
|
623
|
+
pFgA_chi2_map = self.masker.inverse_transform(pFgA_chi2_vals).get_fdata()
|
624
|
+
pFgA_max_size, pFgA_max_mass = _calculate_cluster_measures(
|
625
|
+
pFgA_chi2_map, voxel_thresh, conn, tail="two"
|
626
|
+
)
|
627
|
+
|
628
|
+
return (
|
629
|
+
pAgF_max_chi2_value,
|
630
|
+
pAgF_max_size,
|
631
|
+
pAgF_max_mass,
|
632
|
+
pFgA_max_chi2_value,
|
633
|
+
pFgA_max_size,
|
634
|
+
pFgA_max_mass,
|
635
|
+
)
|
636
|
+
|
637
|
+
def _apply_correction(self, stat_values, voxel_thresh, vfwe_null, csfwe_null, cmfwe_null):
|
638
|
+
"""Apply different kinds of FWE correction to statistical value matrix.
|
639
|
+
|
640
|
+
.. versionchanged:: 0.0.13
|
641
|
+
|
642
|
+
Change cluster neighborhood from faces+edges to faces, to match Nilearn.
|
643
|
+
|
644
|
+
Parameters
|
645
|
+
----------
|
646
|
+
stat_values : :obj:`numpy.ndarray`
|
647
|
+
1D array of summary-statistic values.
|
648
|
+
voxel_thresh : :obj:`float`
|
649
|
+
Summary statistic threshold for defining clusters.
|
650
|
+
vfwe_null, csfwe_null, cmfwe_null : :obj:`numpy.ndarray`
|
651
|
+
Null distributions for FWE correction.
|
652
|
+
|
653
|
+
Returns
|
654
|
+
-------
|
655
|
+
p_vfwe_values, p_csfwe_values, p_cmfwe_values : :obj:`numpy.ndarray`
|
656
|
+
1D arrays of FWE-corrected p-values.
|
657
|
+
"""
|
658
|
+
eps = np.spacing(1)
|
659
|
+
|
660
|
+
# Define connectivity matrix for cluster labeling
|
661
|
+
conn = ndimage.generate_binary_structure(rank=3, connectivity=1)
|
662
|
+
|
663
|
+
# Voxel-level FWE
|
664
|
+
p_vfwe_values = null_to_p(np.abs(stat_values), vfwe_null, tail="upper")
|
665
|
+
|
666
|
+
# Crop p-values of 0 or 1 to nearest values that won't evaluate to 0 or 1.
|
667
|
+
# Prevents inf z-values.
|
668
|
+
p_vfwe_values[p_vfwe_values < eps] = eps
|
669
|
+
p_vfwe_values[p_vfwe_values > (1.0 - eps)] = 1.0 - eps
|
670
|
+
|
671
|
+
# Cluster-level FWE
|
672
|
+
# Extract the summary statistics in voxel-wise (3D) form, threshold, and cluster-label
|
673
|
+
stat_map_thresh = self.masker.inverse_transform(stat_values).get_fdata()
|
674
|
+
|
675
|
+
stat_map_thresh[np.abs(stat_map_thresh) <= voxel_thresh] = 0
|
676
|
+
|
677
|
+
# Label positive and negative clusters separately
|
678
|
+
labeled_matrix = np.empty(stat_map_thresh.shape, int)
|
679
|
+
labeled_matrix, _ = ndimage.label(stat_map_thresh > 0, conn)
|
680
|
+
n_positive_clusters = np.max(labeled_matrix)
|
681
|
+
temp_labeled_matrix, _ = ndimage.label(stat_map_thresh < 0, conn)
|
682
|
+
temp_labeled_matrix[temp_labeled_matrix > 0] += n_positive_clusters
|
683
|
+
labeled_matrix = labeled_matrix + temp_labeled_matrix
|
684
|
+
del temp_labeled_matrix
|
685
|
+
|
686
|
+
cluster_labels, idx, cluster_sizes = np.unique(
|
687
|
+
labeled_matrix,
|
688
|
+
return_inverse=True,
|
689
|
+
return_counts=True,
|
690
|
+
)
|
691
|
+
assert cluster_labels[0] == 0
|
692
|
+
|
693
|
+
# Cluster mass-based inference
|
694
|
+
cluster_masses = np.zeros(cluster_labels.shape)
|
695
|
+
for i_val in cluster_labels:
|
696
|
+
if i_val == 0:
|
697
|
+
cluster_masses[i_val] = 0
|
698
|
+
|
699
|
+
cluster_mass = np.sum(np.abs(stat_map_thresh[labeled_matrix == i_val]) - voxel_thresh)
|
700
|
+
cluster_masses[i_val] = cluster_mass
|
701
|
+
|
702
|
+
p_cmfwe_vals = null_to_p(cluster_masses, cmfwe_null, tail="upper")
|
703
|
+
p_cmfwe_map = p_cmfwe_vals[np.reshape(idx, labeled_matrix.shape)]
|
704
|
+
|
705
|
+
p_cmfwe_values = np.squeeze(
|
706
|
+
self.masker.transform(nib.Nifti1Image(p_cmfwe_map, self.masker.mask_img.affine))
|
707
|
+
)
|
708
|
+
|
709
|
+
# Cluster size-based inference
|
710
|
+
cluster_sizes[0] = 0 # replace background's "cluster size" with zeros
|
711
|
+
p_csfwe_vals = null_to_p(cluster_sizes, csfwe_null, tail="upper")
|
712
|
+
p_csfwe_map = p_csfwe_vals[np.reshape(idx, labeled_matrix.shape)]
|
713
|
+
p_csfwe_values = np.squeeze(
|
714
|
+
self.masker.transform(nib.Nifti1Image(p_csfwe_map, self.masker.mask_img.affine))
|
715
|
+
)
|
716
|
+
|
717
|
+
return p_vfwe_values, p_csfwe_values, p_cmfwe_values
|
718
|
+
|
719
|
+
def correct_fwe_montecarlo(self, result, voxel_thresh=0.001, n_iters=1000, n_cores=1):
|
720
|
+
"""Perform FWE correction using the max-value permutation method.
|
721
|
+
|
722
|
+
Only call this method from within a Corrector.
|
723
|
+
|
724
|
+
.. versionchanged:: 0.0.13
|
725
|
+
|
726
|
+
Change cluster neighborhood from faces+edges to faces, to match Nilearn.
|
727
|
+
|
728
|
+
.. versionchanged:: 0.0.12
|
729
|
+
|
730
|
+
Include cluster level-corrected results in Monte Carlo null method.
|
731
|
+
|
732
|
+
Parameters
|
733
|
+
----------
|
734
|
+
result : :obj:`~nimare.results.MetaResult`
|
735
|
+
Result object from a KDA meta-analysis.
|
736
|
+
voxel_thresh : :obj:`float`, default=0.001
|
737
|
+
Voxel-level threshold. Default is 0.001.
|
738
|
+
n_iters : :obj:`int`, default=1000
|
739
|
+
Number of iterations to build the vFWE null distribution.
|
740
|
+
Default is 1000.
|
741
|
+
n_cores : :obj:`int`, default=1
|
742
|
+
Number of cores to use for parallelization.
|
743
|
+
If <=0, defaults to using all available cores. Default is 1.
|
744
|
+
|
745
|
+
Returns
|
746
|
+
-------
|
747
|
+
maps : :obj:`dict`
|
748
|
+
Dictionary of 1D arrays corresponding to masked maps generated by
|
749
|
+
the correction procedure. The following arrays are generated by
|
750
|
+
this method:
|
751
|
+
|
752
|
+
- ``p_desc-uniformity_level-voxel``: Voxel-level FWE-corrected p-values from the
|
753
|
+
uniformity/forward inference analysis.
|
754
|
+
- ``z_desc-uniformity_level-voxel``: Voxel-level FWE-corrected z-values from the
|
755
|
+
uniformity/forward inference analysis.
|
756
|
+
- ``logp_desc-uniformity_level-voxel``: Voxel-level FWE-corrected -log10 p-values
|
757
|
+
from the uniformity/forward inference analysis.
|
758
|
+
- ``p_desc-uniformityMass_level-cluster``: Cluster-level FWE-corrected p-values
|
759
|
+
from the uniformity/forward inference analysis, using cluster mass.
|
760
|
+
- ``z_desc-uniformityMass_level-cluster``: Cluster-level FWE-corrected z-values
|
761
|
+
from the uniformity/forward inference analysis, using cluster mass.
|
762
|
+
- ``logp_desc-uniformityMass_level-cluster``: Cluster-level FWE-corrected -log10
|
763
|
+
p-values from the uniformity/forward inference analysis, using cluster mass.
|
764
|
+
- ``p_desc-uniformitySize_level-cluster``: Cluster-level FWE-corrected p-values
|
765
|
+
from the uniformity/forward inference analysis, using cluster size.
|
766
|
+
- ``z_desc-uniformitySize_level-cluster``: Cluster-level FWE-corrected z-values
|
767
|
+
from the uniformity/forward inference analysis, using cluster size.
|
768
|
+
- ``logp_desc-uniformitySize_level-cluster``: Cluster-level FWE-corrected -log10
|
769
|
+
p-values from the uniformity/forward inference analysis, using cluster size.
|
770
|
+
- ``p_desc-association_level-voxel``: Voxel-level FWE-corrected p-values from the
|
771
|
+
association/reverse inference analysis.
|
772
|
+
- ``z_desc-association_level-voxel``: Voxel-level FWE-corrected z-values from the
|
773
|
+
association/reverse inference analysis.
|
774
|
+
- ``logp_desc-association_level-voxel``: Voxel-level FWE-corrected -log10 p-values
|
775
|
+
from the association/reverse inference analysis.
|
776
|
+
- ``p_desc-associationMass_level-cluster``: Cluster-level FWE-corrected p-values
|
777
|
+
from the association/reverse inference analysis, using cluster mass.
|
778
|
+
- ``z_desc-associationMass_level-cluster``: Cluster-level FWE-corrected z-values
|
779
|
+
from the association/reverse inference analysis, using cluster mass.
|
780
|
+
- ``logp_desc-associationMass_level-cluster``: Cluster-level FWE-corrected -log10
|
781
|
+
p-values from the association/reverse inference analysis, using cluster mass.
|
782
|
+
- ``p_desc-associationSize_level-cluster``: Cluster-level FWE-corrected p-values
|
783
|
+
from the association/reverse inference analysis, using cluster size.
|
784
|
+
- ``z_desc-associationSize_level-cluster``: Cluster-level FWE-corrected z-values
|
785
|
+
from the association/reverse inference analysis, using cluster size.
|
786
|
+
- ``logp_desc-associationSize_level-cluster``: Cluster-level FWE-corrected -log10
|
787
|
+
p-values from the association/reverse inference analysis, using cluster size.
|
788
|
+
|
789
|
+
Notes
|
790
|
+
-----
|
791
|
+
This method adds six new keys to the ``null_distributions_`` attribute:
|
792
|
+
|
793
|
+
- ``values_desc-pAgF_level-voxel_corr-fwe_method-montecarlo``: The maximum
|
794
|
+
chi-squared value from the p(A|F) one-way chi-squared test from each Monte Carlo
|
795
|
+
iteration. An array of shape (n_iters,).
|
796
|
+
- ``values_desc-pAgFsize_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
797
|
+
cluster size value from the p(A|F) one-way chi-squared test from each Monte Carlo
|
798
|
+
iteration. An array of shape (n_iters,).
|
799
|
+
- ``values_desc-pAgFmass_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
800
|
+
cluster mass value from the p(A|F) one-way chi-squared test from each Monte Carlo
|
801
|
+
iteration. An array of shape (n_iters,).
|
802
|
+
- ``values_desc-pFgA_level-voxel_corr-fwe_method-montecarlo``: The maximum
|
803
|
+
chi-squared value from the p(F|A) two-way chi-squared test from each Monte Carlo
|
804
|
+
iteration. An array of shape (n_iters,).
|
805
|
+
- ``values_desc-pFgAsize_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
806
|
+
cluster size value from the p(F|A) two-way chi-squared test from each Monte Carlo
|
807
|
+
iteration. An array of shape (n_iters,).
|
808
|
+
- ``values_desc-pFgAmass_level-cluster_corr-fwe_method-montecarlo``: The maximum
|
809
|
+
cluster mass value from the p(F|A) two-way chi-squared test from each Monte Carlo
|
810
|
+
iteration. An array of shape (n_iters,).
|
811
|
+
|
812
|
+
See Also
|
813
|
+
--------
|
814
|
+
nimare.correct.FWECorrector : The Corrector from which to call this method.
|
815
|
+
|
816
|
+
Examples
|
817
|
+
--------
|
818
|
+
>>> meta = MKDAChi2()
|
819
|
+
>>> result = meta.fit(dset)
|
820
|
+
>>> corrector = FWECorrector(method='montecarlo', n_iters=5, n_cores=1)
|
821
|
+
>>> cresult = corrector.transform(result)
|
822
|
+
"""
|
823
|
+
null_xyz = vox2mm(
|
824
|
+
np.vstack(np.where(self.masker.mask_img.get_fdata())).T,
|
825
|
+
self.masker.mask_img.affine,
|
826
|
+
)
|
827
|
+
pAgF_chi2_vals = result.get_map("chi2_desc-uniformity", return_type="array")
|
828
|
+
pFgA_chi2_vals = result.get_map("chi2_desc-association", return_type="array")
|
829
|
+
pAgF_z_vals = result.get_map("z_desc-uniformity", return_type="array")
|
830
|
+
pFgA_z_vals = result.get_map("z_desc-association", return_type="array")
|
831
|
+
pAgF_sign = np.sign(pAgF_z_vals)
|
832
|
+
pFgA_sign = np.sign(pFgA_z_vals)
|
833
|
+
|
834
|
+
n_cores = _check_ncores(n_cores)
|
835
|
+
|
836
|
+
iter_df1 = self.inputs_["coordinates1"]
|
837
|
+
iter_df2 = self.inputs_["coordinates2"]
|
838
|
+
rand_idx1 = np.random.choice(null_xyz.shape[0], size=(iter_df1.shape[0], n_iters))
|
839
|
+
rand_xyz1 = null_xyz[rand_idx1, :]
|
840
|
+
iter_xyzs1 = np.split(rand_xyz1, rand_xyz1.shape[1], axis=1)
|
841
|
+
rand_idx2 = np.random.choice(null_xyz.shape[0], size=(iter_df2.shape[0], n_iters))
|
842
|
+
rand_xyz2 = null_xyz[rand_idx2, :]
|
843
|
+
iter_xyzs2 = np.split(rand_xyz2, rand_xyz2.shape[1], axis=1)
|
844
|
+
eps = np.spacing(1)
|
845
|
+
|
846
|
+
# Identify summary statistic corresponding to intensity threshold
|
847
|
+
ss_thresh = chi2.isf(voxel_thresh, 1)
|
848
|
+
|
849
|
+
# Define connectivity matrix for cluster labeling
|
850
|
+
conn = ndimage.generate_binary_structure(rank=3, connectivity=1)
|
851
|
+
|
852
|
+
perm_results = [
|
853
|
+
r
|
854
|
+
for r in tqdm(
|
855
|
+
Parallel(return_as="generator", n_jobs=n_cores)(
|
856
|
+
delayed(self._run_fwe_permutation)(
|
857
|
+
iter_xyz1=iter_xyzs1[i_iter],
|
858
|
+
iter_xyz2=iter_xyzs2[i_iter],
|
859
|
+
iter_df1=iter_df1,
|
860
|
+
iter_df2=iter_df2,
|
861
|
+
conn=conn,
|
862
|
+
voxel_thresh=ss_thresh,
|
863
|
+
)
|
864
|
+
for i_iter in range(n_iters)
|
865
|
+
),
|
866
|
+
total=n_iters,
|
867
|
+
)
|
868
|
+
]
|
869
|
+
|
870
|
+
del rand_idx1, rand_xyz1, iter_xyzs1
|
871
|
+
del rand_idx2, rand_xyz2, iter_xyzs2
|
872
|
+
|
873
|
+
(
|
874
|
+
pAgF_vfwe_null,
|
875
|
+
pAgF_csfwe_null,
|
876
|
+
pAgF_cmfwe_null,
|
877
|
+
pFgA_vfwe_null,
|
878
|
+
pFgA_csfwe_null,
|
879
|
+
pFgA_cmfwe_null,
|
880
|
+
) = zip(*perm_results)
|
881
|
+
|
882
|
+
del perm_results
|
883
|
+
|
884
|
+
# pAgF_FWE
|
885
|
+
pAgF_p_vfwe_vals, pAgF_p_csfwe_vals, pAgF_p_cmfwe_vals = self._apply_correction(
|
886
|
+
pAgF_chi2_vals,
|
887
|
+
ss_thresh,
|
888
|
+
vfwe_null=pAgF_vfwe_null,
|
889
|
+
csfwe_null=pAgF_csfwe_null,
|
890
|
+
cmfwe_null=pAgF_cmfwe_null,
|
891
|
+
)
|
892
|
+
|
893
|
+
self.null_distributions_["values_desc-pAgF_level-voxel_corr-fwe_method-montecarlo"] = (
|
894
|
+
pAgF_vfwe_null
|
895
|
+
)
|
896
|
+
self.null_distributions_[
|
897
|
+
"values_desc-pAgFsize_level-cluster_corr-fwe_method-montecarlo"
|
898
|
+
] = pAgF_csfwe_null
|
899
|
+
self.null_distributions_[
|
900
|
+
"values_desc-pAgFmass_level-cluster_corr-fwe_method-montecarlo"
|
901
|
+
] = pAgF_cmfwe_null
|
902
|
+
|
903
|
+
del pAgF_vfwe_null, pAgF_csfwe_null, pAgF_cmfwe_null
|
904
|
+
|
905
|
+
# pFgA_FWE
|
906
|
+
pFgA_p_vfwe_vals, pFgA_p_csfwe_vals, pFgA_p_cmfwe_vals = self._apply_correction(
|
907
|
+
pFgA_chi2_vals,
|
908
|
+
ss_thresh,
|
909
|
+
vfwe_null=pFgA_vfwe_null,
|
910
|
+
csfwe_null=pFgA_csfwe_null,
|
911
|
+
cmfwe_null=pFgA_cmfwe_null,
|
912
|
+
)
|
913
|
+
|
914
|
+
self.null_distributions_["values_desc-pFgA_level-voxel_corr-fwe_method-montecarlo"] = (
|
915
|
+
pFgA_vfwe_null
|
916
|
+
)
|
917
|
+
self.null_distributions_[
|
918
|
+
"values_desc-pFgAsize_level-cluster_corr-fwe_method-montecarlo"
|
919
|
+
] = pFgA_csfwe_null
|
920
|
+
self.null_distributions_[
|
921
|
+
"values_desc-pFgAmass_level-cluster_corr-fwe_method-montecarlo"
|
922
|
+
] = pFgA_cmfwe_null
|
923
|
+
|
924
|
+
del pFgA_vfwe_null, pFgA_csfwe_null, pFgA_cmfwe_null
|
925
|
+
|
926
|
+
# Convert p-values
|
927
|
+
# pAgF
|
928
|
+
pAgF_z_vfwe_vals = p_to_z(pAgF_p_vfwe_vals, tail="two") * pAgF_sign
|
929
|
+
pAgF_logp_vfwe_vals = -np.log10(pAgF_p_vfwe_vals)
|
930
|
+
pAgF_logp_vfwe_vals[np.isinf(pAgF_logp_vfwe_vals)] = -np.log10(eps)
|
931
|
+
pAgF_z_cmfwe_vals = p_to_z(pAgF_p_cmfwe_vals, tail="two") * pAgF_sign
|
932
|
+
pAgF_logp_cmfwe_vals = -np.log10(pAgF_p_cmfwe_vals)
|
933
|
+
pAgF_logp_cmfwe_vals[np.isinf(pAgF_logp_cmfwe_vals)] = -np.log10(eps)
|
934
|
+
pAgF_z_csfwe_vals = p_to_z(pAgF_p_csfwe_vals, tail="two") * pAgF_sign
|
935
|
+
pAgF_logp_csfwe_vals = -np.log10(pAgF_p_csfwe_vals)
|
936
|
+
pAgF_logp_csfwe_vals[np.isinf(pAgF_logp_csfwe_vals)] = -np.log10(eps)
|
937
|
+
|
938
|
+
# pFgA
|
939
|
+
pFgA_z_vfwe_vals = p_to_z(pFgA_p_vfwe_vals, tail="two") * pFgA_sign
|
940
|
+
pFgA_logp_vfwe_vals = -np.log10(pFgA_p_vfwe_vals)
|
941
|
+
pFgA_logp_vfwe_vals[np.isinf(pFgA_logp_vfwe_vals)] = -np.log10(eps)
|
942
|
+
pFgA_z_cmfwe_vals = p_to_z(pFgA_p_cmfwe_vals, tail="two") * pFgA_sign
|
943
|
+
pFgA_logp_cmfwe_vals = -np.log10(pFgA_p_cmfwe_vals)
|
944
|
+
pFgA_logp_cmfwe_vals[np.isinf(pFgA_logp_cmfwe_vals)] = -np.log10(eps)
|
945
|
+
pFgA_z_csfwe_vals = p_to_z(pFgA_p_csfwe_vals, tail="two") * pFgA_sign
|
946
|
+
pFgA_logp_csfwe_vals = -np.log10(pFgA_p_csfwe_vals)
|
947
|
+
pFgA_logp_csfwe_vals[np.isinf(pFgA_logp_csfwe_vals)] = -np.log10(eps)
|
948
|
+
|
949
|
+
maps = {
|
950
|
+
# uniformity analysis
|
951
|
+
"p_desc-uniformity_level-voxel": pAgF_p_vfwe_vals,
|
952
|
+
"z_desc-uniformity_level-voxel": pAgF_z_vfwe_vals,
|
953
|
+
"logp_desc-uniformity_level-voxel": pAgF_logp_vfwe_vals,
|
954
|
+
"p_desc-uniformityMass_level-cluster": pAgF_p_cmfwe_vals,
|
955
|
+
"z_desc-uniformityMass_level-cluster": pAgF_z_cmfwe_vals,
|
956
|
+
"logp_desc-uniformityMass_level-cluster": pAgF_logp_cmfwe_vals,
|
957
|
+
"p_desc-uniformitySize_level-cluster": pAgF_p_csfwe_vals,
|
958
|
+
"z_desc-uniformitySize_level-cluster": pAgF_z_csfwe_vals,
|
959
|
+
"logp_desc-uniformitySize_level-cluster": pAgF_logp_csfwe_vals,
|
960
|
+
# association analysis
|
961
|
+
"p_desc-association_level-voxel": pFgA_p_vfwe_vals,
|
962
|
+
"z_desc-association_level-voxel": pFgA_z_vfwe_vals,
|
963
|
+
"logp_desc-association_level-voxel": pFgA_logp_vfwe_vals,
|
964
|
+
"p_desc-associationMass_level-cluster": pFgA_p_cmfwe_vals,
|
965
|
+
"z_desc-associationMass_level-cluster": pFgA_z_cmfwe_vals,
|
966
|
+
"logp_desc-associationMass_level-cluster": pFgA_logp_cmfwe_vals,
|
967
|
+
"p_desc-associationSize_level-cluster": pFgA_p_csfwe_vals,
|
968
|
+
"z_desc-associationSize_level-cluster": pFgA_z_csfwe_vals,
|
969
|
+
"logp_desc-associationSize_level-cluster": pFgA_logp_csfwe_vals,
|
970
|
+
}
|
971
|
+
|
972
|
+
description = ""
|
973
|
+
|
974
|
+
return maps, {}, description
|
975
|
+
|
976
|
+
def correct_fdr_indep(self, result, alpha=0.05):
|
977
|
+
"""Perform FDR correction using the Benjamini-Hochberg method.
|
978
|
+
|
979
|
+
Only call this method from within a Corrector.
|
980
|
+
|
981
|
+
.. versionchanged:: 0.0.12
|
982
|
+
|
983
|
+
Renamed from ``correct_fdr_bh`` to ``correct_fdr_indep``.
|
984
|
+
|
985
|
+
Parameters
|
986
|
+
----------
|
987
|
+
result : :obj:`~nimare.results.MetaResult`
|
988
|
+
Result object from a KDA meta-analysis.
|
989
|
+
alpha : :obj:`float`, optional
|
990
|
+
Alpha. Default is 0.05.
|
991
|
+
|
992
|
+
Returns
|
993
|
+
-------
|
994
|
+
maps : :obj:`dict`
|
995
|
+
Dictionary of 1D arrays corresponding to masked maps generated by
|
996
|
+
the correction procedure. The following arrays are generated by
|
997
|
+
this method: 'z_desc-uniformity_level-voxel' and 'z_desc-association_level-voxel'.
|
998
|
+
|
999
|
+
See Also
|
1000
|
+
--------
|
1001
|
+
nimare.correct.FDRCorrector : The Corrector from which to call this method.
|
1002
|
+
|
1003
|
+
Examples
|
1004
|
+
--------
|
1005
|
+
>>> meta = MKDAChi2()
|
1006
|
+
>>> result = meta.fit(dset)
|
1007
|
+
>>> corrector = FDRCorrector(method='indep', alpha=0.05)
|
1008
|
+
>>> cresult = corrector.transform(result)
|
1009
|
+
"""
|
1010
|
+
pAgF_p_vals = result.get_map("p_desc-uniformity", return_type="array")
|
1011
|
+
pFgA_p_vals = result.get_map("p_desc-association", return_type="array")
|
1012
|
+
pAgF_z_vals = result.get_map("z_desc-uniformity", return_type="array")
|
1013
|
+
pFgA_z_vals = result.get_map("z_desc-association", return_type="array")
|
1014
|
+
pAgF_sign = np.sign(pAgF_z_vals)
|
1015
|
+
pFgA_sign = np.sign(pFgA_z_vals)
|
1016
|
+
pAgF_p_FDR = fdr(pAgF_p_vals, q=alpha, method="bh")
|
1017
|
+
pAgF_z_FDR = p_to_z(pAgF_p_FDR, tail="two") * pAgF_sign
|
1018
|
+
|
1019
|
+
pFgA_p_FDR = fdr(pFgA_p_vals, q=alpha, method="bh")
|
1020
|
+
pFgA_z_FDR = p_to_z(pFgA_p_FDR, tail="two") * pFgA_sign
|
1021
|
+
|
1022
|
+
maps = {
|
1023
|
+
"z_desc-uniformity_level-voxel": pAgF_z_FDR,
|
1024
|
+
"z_desc-association_level-voxel": pFgA_z_FDR,
|
1025
|
+
}
|
1026
|
+
|
1027
|
+
description = ""
|
1028
|
+
|
1029
|
+
return maps, {}, description
|
1030
|
+
|
1031
|
+
|
1032
|
+
class KDA(CBMAEstimator):
|
1033
|
+
r"""Kernel density analysis.
|
1034
|
+
|
1035
|
+
.. versionchanged:: 0.2.1
|
1036
|
+
|
1037
|
+
- New parameters: ``memory`` and ``memory_level`` for memory caching.
|
1038
|
+
|
1039
|
+
.. versionchanged:: 0.0.12
|
1040
|
+
|
1041
|
+
- Use a 4D sparse array for modeled activation maps.
|
1042
|
+
|
1043
|
+
Parameters
|
1044
|
+
----------
|
1045
|
+
kernel_transformer : :obj:`~nimare.meta.kernel.KernelTransformer`, optional
|
1046
|
+
Kernel with which to convolve coordinates from dataset. Default is
|
1047
|
+
:class:`~nimare.meta.kernel.KDAKernel`.
|
1048
|
+
null_method : {"approximate", "montecarlo"}, optional
|
1049
|
+
Method by which to determine uncorrected p-values. The available options are
|
1050
|
+
|
1051
|
+
======================= =================================================================
|
1052
|
+
"approximate" (default) Build a histogram of summary-statistic values and their
|
1053
|
+
expected frequencies under the assumption of random spatial
|
1054
|
+
associated between studies, via a weighted convolution.
|
1055
|
+
|
1056
|
+
This method is much faster, but slightly less accurate.
|
1057
|
+
"montecarlo" Perform a large number of permutations, in which the coordinates
|
1058
|
+
in the studies are randomly drawn from the Estimator's brain mask
|
1059
|
+
and the full set of resulting summary-statistic values are
|
1060
|
+
incorporated into a null distribution (stored as a histogram for
|
1061
|
+
memory reasons).
|
1062
|
+
|
1063
|
+
This method is must slower, and is only slightly more accurate.
|
1064
|
+
======================= =================================================================
|
1065
|
+
|
1066
|
+
n_iters : int, default=5000
|
1067
|
+
Number of iterations to use to define the null distribution.
|
1068
|
+
This is only used if ``null_method=="montecarlo"``.
|
1069
|
+
Default is 5000.
|
1070
|
+
memory : instance of :class:`joblib.Memory`, :obj:`str`, or :class:`pathlib.Path`
|
1071
|
+
Used to cache the output of a function. By default, no caching is done.
|
1072
|
+
If a :obj:`str` is given, it is the path to the caching directory.
|
1073
|
+
memory_level : :obj:`int`, default=0
|
1074
|
+
Rough estimator of the amount of memory used by caching.
|
1075
|
+
Higher value means more memory for caching. Zero means no caching.
|
1076
|
+
n_cores : :obj:`int`, default=1
|
1077
|
+
Number of cores to use for parallelization.
|
1078
|
+
This is only used if ``null_method=="montecarlo"``.
|
1079
|
+
If <=0, defaults to using all available cores.
|
1080
|
+
Default is 1.
|
1081
|
+
**kwargs
|
1082
|
+
Keyword arguments. Arguments for the kernel_transformer can be assigned
|
1083
|
+
here, with the prefix '\kernel__' in the variable name.
|
1084
|
+
|
1085
|
+
Attributes
|
1086
|
+
----------
|
1087
|
+
masker : :class:`~nilearn.input_data.NiftiMasker` or similar
|
1088
|
+
Masker object.
|
1089
|
+
inputs_ : :obj:`dict`
|
1090
|
+
Inputs to the Estimator. For CBMA estimators, there is only one key: coordinates.
|
1091
|
+
This is an edited version of the dataset's coordinates DataFrame.
|
1092
|
+
null_distributions_ : :obj:`dict` of :class:`numpy.ndarray`
|
1093
|
+
Null distributions for the uncorrected summary-statistic-to-p-value conversion and any
|
1094
|
+
multiple-comparisons correction methods.
|
1095
|
+
Entries are added to this attribute if and when the corresponding method is applied.
|
1096
|
+
|
1097
|
+
If ``null_method == "approximate"``:
|
1098
|
+
|
1099
|
+
- ``histogram_bins``: Array of bin centers for the null distribution histogram,
|
1100
|
+
ranging from zero to the maximum possible summary statistic value for the Dataset.
|
1101
|
+
- ``histweights_corr-none_method-approximate``: Array of weights for the null
|
1102
|
+
distribution histogram, with one value for each bin in ``histogram_bins``.
|
1103
|
+
|
1104
|
+
If ``null_method == "montecarlo"``:
|
1105
|
+
|
1106
|
+
- ``histogram_bins``: Array of bin centers for the null distribution histogram,
|
1107
|
+
ranging from zero to the maximum possible summary statistic value for the Dataset.
|
1108
|
+
- ``histweights_corr-none_method-montecarlo``: Array of weights for the null
|
1109
|
+
distribution histogram, with one value for each bin in ``histogram_bins``.
|
1110
|
+
These values are derived from the full set of summary statistics from each
|
1111
|
+
iteration of the Monte Carlo procedure.
|
1112
|
+
- ``histweights_level-voxel_corr-fwe_method-montecarlo``: Array of weights for the
|
1113
|
+
voxel-level FWE-correction null distribution, with one value for each bin in
|
1114
|
+
``histogram_bins``. These values are derived from the maximum summary statistic
|
1115
|
+
from each iteration of the Monte Carlo procedure.
|
1116
|
+
|
1117
|
+
If :meth:`correct_fwe_montecarlo` is applied:
|
1118
|
+
|
1119
|
+
- ``values_level-voxel_corr-fwe_method-montecarlo``: The maximum summary statistic
|
1120
|
+
value from each Monte Carlo iteration. An array of shape (n_iters,).
|
1121
|
+
- ``values_desc-size_level-cluster_corr-fwe_method-montecarlo``: The maximum cluster
|
1122
|
+
size from each Monte Carlo iteration. An array of shape (n_iters,).
|
1123
|
+
- ``values_desc-mass_level-cluster_corr-fwe_method-montecarlo``: The maximum cluster
|
1124
|
+
mass from each Monte Carlo iteration. An array of shape (n_iters,).
|
1125
|
+
|
1126
|
+
Notes
|
1127
|
+
-----
|
1128
|
+
Kernel density analysis was first introduced in :footcite:t:`wager2003valence` and
|
1129
|
+
:footcite:t:`wager2004neuroimaging`.
|
1130
|
+
|
1131
|
+
Available correction methods: :func:`KDA.correct_fwe_montecarlo`
|
1132
|
+
|
1133
|
+
Warnings
|
1134
|
+
--------
|
1135
|
+
The KDA algorithm has been replaced in the literature with the MKDA algorithm.
|
1136
|
+
As such, this estimator should almost never be used, outside of systematic
|
1137
|
+
comparisons between algorithms.
|
1138
|
+
|
1139
|
+
References
|
1140
|
+
----------
|
1141
|
+
.. footbibliography::
|
1142
|
+
"""
|
1143
|
+
|
1144
|
+
def __init__(
|
1145
|
+
self,
|
1146
|
+
kernel_transformer=KDAKernel,
|
1147
|
+
null_method="approximate",
|
1148
|
+
n_iters=5000,
|
1149
|
+
memory=Memory(location=None, verbose=0),
|
1150
|
+
memory_level=0,
|
1151
|
+
n_cores=1,
|
1152
|
+
**kwargs,
|
1153
|
+
):
|
1154
|
+
LGR.warning(
|
1155
|
+
"The KDA algorithm has been replaced in the literature with the MKDA algorithm. "
|
1156
|
+
"As such, this estimator should almost never be used, outside of systematic "
|
1157
|
+
"comparisons between algorithms."
|
1158
|
+
)
|
1159
|
+
|
1160
|
+
if not (isinstance(kernel_transformer, KDAKernel) or kernel_transformer == KDAKernel):
|
1161
|
+
LGR.warning(
|
1162
|
+
f"The KernelTransformer being used ({kernel_transformer}) is not optimized "
|
1163
|
+
f"for the {type(self).__name__} algorithm. "
|
1164
|
+
"Expect suboptimal performance and beware bugs."
|
1165
|
+
)
|
1166
|
+
|
1167
|
+
# Add kernel transformer attribute and process keyword arguments
|
1168
|
+
super().__init__(
|
1169
|
+
kernel_transformer=kernel_transformer,
|
1170
|
+
memory=memory,
|
1171
|
+
memory_level=memory_level,
|
1172
|
+
**kwargs,
|
1173
|
+
)
|
1174
|
+
self.null_method = null_method
|
1175
|
+
self.n_iters = None if null_method == "approximate" else n_iters or 5000
|
1176
|
+
self.n_cores = _check_ncores(n_cores)
|
1177
|
+
self.dataset = None
|
1178
|
+
|
1179
|
+
def _generate_description(self):
|
1180
|
+
"""Generate a description of the fitted Estimator.
|
1181
|
+
|
1182
|
+
Returns
|
1183
|
+
-------
|
1184
|
+
str
|
1185
|
+
Description of the Estimator.
|
1186
|
+
"""
|
1187
|
+
if self.null_method == "montecarlo":
|
1188
|
+
null_method_str = (
|
1189
|
+
"a Monte Carlo-based null distribution, in which dataset coordinates were "
|
1190
|
+
"randomly drawn from the analysis mask and the full set of ALE values were "
|
1191
|
+
f"retained, using {self.n_iters} iterations"
|
1192
|
+
)
|
1193
|
+
else:
|
1194
|
+
null_method_str = "an approximate null distribution"
|
1195
|
+
|
1196
|
+
description = (
|
1197
|
+
"A kernel density (KDA) meta-analysis \\citep{wager2007meta} was "
|
1198
|
+
"performed was performed with NiMARE "
|
1199
|
+
f"{__version__} "
|
1200
|
+
"(RRID:SCR_017398; \\citealt{Salo2023}), "
|
1201
|
+
f"using a(n) {self.kernel_transformer.__class__.__name__.replace('Kernel', '')} "
|
1202
|
+
"kernel. "
|
1203
|
+
f"{self.kernel_transformer._generate_description()} "
|
1204
|
+
f"Summary statistics (OF values) were converted to p-values using {null_method_str}. "
|
1205
|
+
f"The input dataset included {self.inputs_['coordinates'].shape[0]} foci from "
|
1206
|
+
f"{len(self.inputs_['id'])} experiments."
|
1207
|
+
)
|
1208
|
+
return description
|
1209
|
+
|
1210
|
+
def _compute_summarystat_est(self, ma_values):
|
1211
|
+
"""Compute OF scores from data.
|
1212
|
+
|
1213
|
+
Parameters
|
1214
|
+
----------
|
1215
|
+
ma_maps : :obj:`numpy.ndarray` or :obj:`sparse._coo.core.COO`
|
1216
|
+
MA maps.
|
1217
|
+
The ma_maps can be:
|
1218
|
+
(1) a 1d contrast-len or 2d contrast-by-voxel array of MA values,
|
1219
|
+
or (2) a 4d sparse array of MA maps,
|
1220
|
+
|
1221
|
+
Returns
|
1222
|
+
-------
|
1223
|
+
stat_values : 1d array
|
1224
|
+
OF values. One value per voxel.
|
1225
|
+
"""
|
1226
|
+
# OF is just a sum of MA values.
|
1227
|
+
if isinstance(ma_values, sparse._coo.core.COO):
|
1228
|
+
# NOTE: This may not work correctly with a non-NiftiMasker.
|
1229
|
+
mask_data = self.masker.mask_img.get_fdata().astype(bool)
|
1230
|
+
|
1231
|
+
stat_values = ma_values.sum(axis=0)
|
1232
|
+
|
1233
|
+
stat_values = stat_values.todense().reshape(-1)
|
1234
|
+
stat_values = stat_values[mask_data.reshape(-1)]
|
1235
|
+
|
1236
|
+
# This is used by _compute_null_approximate
|
1237
|
+
self.__n_mask_voxels = stat_values.shape[0]
|
1238
|
+
else:
|
1239
|
+
# np.array type is used by _determine_histogram_bins to calculate max_poss_value
|
1240
|
+
stat_values = np.sum(ma_values, axis=0)
|
1241
|
+
|
1242
|
+
return stat_values
|
1243
|
+
|
1244
|
+
def _determine_histogram_bins(self, ma_maps):
|
1245
|
+
"""Determine histogram bins for null distribution methods.
|
1246
|
+
|
1247
|
+
Parameters
|
1248
|
+
----------
|
1249
|
+
ma_maps : :obj:`sparse._coo.core.COO`
|
1250
|
+
MA maps.
|
1251
|
+
|
1252
|
+
Notes
|
1253
|
+
-----
|
1254
|
+
This method adds one entry to the null_distributions_ dict attribute: "histogram_bins".
|
1255
|
+
"""
|
1256
|
+
if not isinstance(ma_maps, sparse._coo.core.COO):
|
1257
|
+
raise ValueError(f"Unsupported data type '{type(ma_maps)}'")
|
1258
|
+
|
1259
|
+
# assumes that groupby results in same order as MA maps
|
1260
|
+
n_foci_per_study = self.inputs_["coordinates"].groupby("id").size().values
|
1261
|
+
|
1262
|
+
# Determine bins for null distribution histogram
|
1263
|
+
if hasattr(self.kernel_transformer, "value"):
|
1264
|
+
# Binary-sphere kernels (KDA & MKDA)
|
1265
|
+
# The maximum possible MA value for each study is the weighting factor (generally 1)
|
1266
|
+
# times the number of foci in the study.
|
1267
|
+
# We grab the weighting factor from the kernel transformer.
|
1268
|
+
step_size = self.kernel_transformer.value # typically 1
|
1269
|
+
max_ma_values = step_size * n_foci_per_study
|
1270
|
+
max_poss_value = self._compute_summarystat_est(max_ma_values)
|
1271
|
+
else:
|
1272
|
+
# Continuous-sphere kernels (ALE)
|
1273
|
+
LGR.info(
|
1274
|
+
"A non-binary kernel has been detected. Parameters for the null distribution "
|
1275
|
+
"will be guesstimated."
|
1276
|
+
)
|
1277
|
+
|
1278
|
+
N_BINS = 100000
|
1279
|
+
# The maximum possible MA value is the max value from each MA map,
|
1280
|
+
# unlike the case with a summation-based kernel.
|
1281
|
+
# Need to convert to dense because np.ceil is too slow with sparse
|
1282
|
+
max_ma_values = ma_maps.max(axis=[1, 2, 3]).todense()
|
1283
|
+
|
1284
|
+
# round up based on resolution
|
1285
|
+
# hardcoding 1000 here because figuring out what to round to was difficult.
|
1286
|
+
max_ma_values = np.ceil(max_ma_values * 1000) / 1000
|
1287
|
+
max_poss_value = self._compute_summarystat(max_ma_values)
|
1288
|
+
|
1289
|
+
# create bin centers
|
1290
|
+
hist_bins = np.linspace(0, max_poss_value, N_BINS - 1)
|
1291
|
+
step_size = hist_bins[1] - hist_bins[0]
|
1292
|
+
|
1293
|
+
# Weighting is not supported yet, so I'm going to build my bins around the min MA value.
|
1294
|
+
# The histogram bins are bin *centers*, not edges.
|
1295
|
+
hist_bins = np.arange(0, max_poss_value + (step_size * 1.5), step_size)
|
1296
|
+
self.null_distributions_["histogram_bins"] = hist_bins
|
1297
|
+
|
1298
|
+
def _compute_null_approximate(self, ma_maps):
|
1299
|
+
"""Compute uncorrected null distribution using approximate solution.
|
1300
|
+
|
1301
|
+
Parameters
|
1302
|
+
----------
|
1303
|
+
ma_maps : :obj:`sparse._coo.core.COO`
|
1304
|
+
MA maps.
|
1305
|
+
|
1306
|
+
Notes
|
1307
|
+
-----
|
1308
|
+
This method adds two entries to the null_distributions_ dict attribute:
|
1309
|
+
"histogram_bins" and "histogram_weights".
|
1310
|
+
"""
|
1311
|
+
if not isinstance(ma_maps, sparse._coo.core.COO):
|
1312
|
+
raise ValueError(f"Unsupported data type '{type(ma_maps)}'")
|
1313
|
+
|
1314
|
+
# Derive bin edges from histogram bin centers for numpy histogram function
|
1315
|
+
bin_centers = self.null_distributions_["histogram_bins"]
|
1316
|
+
step_size = bin_centers[1] - bin_centers[0]
|
1317
|
+
inv_step_size = 1 / step_size
|
1318
|
+
bin_edges = bin_centers - (step_size / 2)
|
1319
|
+
bin_edges = np.append(bin_centers, bin_centers[-1] + step_size)
|
1320
|
+
|
1321
|
+
n_exp = ma_maps.shape[0]
|
1322
|
+
n_bins = bin_centers.shape[0]
|
1323
|
+
ma_hists = np.zeros((n_exp, n_bins))
|
1324
|
+
data = ma_maps.data
|
1325
|
+
coords = ma_maps.coords
|
1326
|
+
for exp_idx in range(n_exp):
|
1327
|
+
# The first column of coords is the fourth dimension of the dense array
|
1328
|
+
study_ma_values = data[coords[0, :] == exp_idx]
|
1329
|
+
|
1330
|
+
n_nonzero_voxels = study_ma_values.shape[0]
|
1331
|
+
n_zero_voxels = self.__n_mask_voxels - n_nonzero_voxels
|
1332
|
+
|
1333
|
+
ma_hists[exp_idx, :] = np.histogram(study_ma_values, bins=bin_edges, density=False)[
|
1334
|
+
0
|
1335
|
+
].astype(float)
|
1336
|
+
ma_hists[exp_idx, 0] += n_zero_voxels
|
1337
|
+
|
1338
|
+
# Normalize MA histograms to get probabilities
|
1339
|
+
ma_hists /= ma_hists.sum(1)[:, None]
|
1340
|
+
|
1341
|
+
# Null distribution to convert summary statistics to p-values.
|
1342
|
+
stat_hist = ma_hists[0, :].copy()
|
1343
|
+
|
1344
|
+
for i_exp in range(1, ma_hists.shape[0]):
|
1345
|
+
exp_hist = ma_hists[i_exp, :]
|
1346
|
+
|
1347
|
+
# Find histogram bins with nonzero values for each histogram.
|
1348
|
+
stat_idx = np.where(stat_hist > 0)[0]
|
1349
|
+
exp_idx = np.where(exp_hist > 0)[0]
|
1350
|
+
|
1351
|
+
# Compute output MA values, stat_hist indices, and probabilities
|
1352
|
+
stat_scores = np.add.outer(bin_centers[exp_idx], bin_centers[stat_idx]).ravel()
|
1353
|
+
score_idx = np.floor(stat_scores * inv_step_size).astype(int)
|
1354
|
+
probabilities = np.outer(exp_hist[exp_idx], stat_hist[stat_idx]).ravel()
|
1355
|
+
|
1356
|
+
# Reset histogram and set probabilities. Use at() because there can
|
1357
|
+
# be redundant values in score_idx.
|
1358
|
+
stat_hist = np.zeros(stat_hist.shape)
|
1359
|
+
np.add.at(stat_hist, score_idx, probabilities)
|
1360
|
+
|
1361
|
+
self.null_distributions_["histweights_corr-none_method-approximate"] = stat_hist
|