nimare 0.4.2rc4__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.
Files changed (119) hide show
  1. benchmarks/__init__.py +0 -0
  2. benchmarks/bench_cbma.py +57 -0
  3. nimare/__init__.py +45 -0
  4. nimare/_version.py +21 -0
  5. nimare/annotate/__init__.py +21 -0
  6. nimare/annotate/cogat.py +213 -0
  7. nimare/annotate/gclda.py +924 -0
  8. nimare/annotate/lda.py +147 -0
  9. nimare/annotate/text.py +75 -0
  10. nimare/annotate/utils.py +87 -0
  11. nimare/base.py +217 -0
  12. nimare/cli.py +124 -0
  13. nimare/correct.py +462 -0
  14. nimare/dataset.py +685 -0
  15. nimare/decode/__init__.py +33 -0
  16. nimare/decode/base.py +115 -0
  17. nimare/decode/continuous.py +462 -0
  18. nimare/decode/discrete.py +753 -0
  19. nimare/decode/encode.py +110 -0
  20. nimare/decode/utils.py +44 -0
  21. nimare/diagnostics.py +510 -0
  22. nimare/estimator.py +139 -0
  23. nimare/extract/__init__.py +19 -0
  24. nimare/extract/extract.py +466 -0
  25. nimare/extract/utils.py +295 -0
  26. nimare/generate.py +331 -0
  27. nimare/io.py +635 -0
  28. nimare/meta/__init__.py +39 -0
  29. nimare/meta/cbma/__init__.py +6 -0
  30. nimare/meta/cbma/ale.py +951 -0
  31. nimare/meta/cbma/base.py +947 -0
  32. nimare/meta/cbma/mkda.py +1361 -0
  33. nimare/meta/cbmr.py +970 -0
  34. nimare/meta/ibma.py +1683 -0
  35. nimare/meta/kernel.py +501 -0
  36. nimare/meta/models.py +1199 -0
  37. nimare/meta/utils.py +494 -0
  38. nimare/nimads.py +492 -0
  39. nimare/reports/__init__.py +24 -0
  40. nimare/reports/base.py +664 -0
  41. nimare/reports/default.yml +123 -0
  42. nimare/reports/figures.py +651 -0
  43. nimare/reports/report.tpl +160 -0
  44. nimare/resources/__init__.py +1 -0
  45. nimare/resources/atlases/Harvard-Oxford-LICENSE +93 -0
  46. nimare/resources/atlases/HarvardOxford-cort-maxprob-thr25-2mm.nii.gz +0 -0
  47. nimare/resources/database_file_manifest.json +142 -0
  48. nimare/resources/english_spellings.csv +1738 -0
  49. nimare/resources/filenames.json +32 -0
  50. nimare/resources/neurosynth_laird_studies.json +58773 -0
  51. nimare/resources/neurosynth_stoplist.txt +396 -0
  52. nimare/resources/nidm_pain_dset.json +1349 -0
  53. nimare/resources/references.bib +541 -0
  54. nimare/resources/semantic_knowledge_children.txt +325 -0
  55. nimare/resources/semantic_relatedness_children.txt +249 -0
  56. nimare/resources/templates/MNI152_2x2x2_brainmask.nii.gz +0 -0
  57. nimare/resources/templates/tpl-MNI152NLin6Asym_res-01_T1w.nii.gz +0 -0
  58. nimare/resources/templates/tpl-MNI152NLin6Asym_res-01_desc-brain_mask.nii.gz +0 -0
  59. nimare/resources/templates/tpl-MNI152NLin6Asym_res-02_T1w.nii.gz +0 -0
  60. nimare/resources/templates/tpl-MNI152NLin6Asym_res-02_desc-brain_mask.nii.gz +0 -0
  61. nimare/results.py +225 -0
  62. nimare/stats.py +276 -0
  63. nimare/tests/__init__.py +1 -0
  64. nimare/tests/conftest.py +229 -0
  65. nimare/tests/data/amygdala_roi.nii.gz +0 -0
  66. nimare/tests/data/data-neurosynth_version-7_coordinates.tsv.gz +0 -0
  67. nimare/tests/data/data-neurosynth_version-7_metadata.tsv.gz +0 -0
  68. nimare/tests/data/data-neurosynth_version-7_vocab-terms_source-abstract_type-tfidf_features.npz +0 -0
  69. nimare/tests/data/data-neurosynth_version-7_vocab-terms_vocabulary.txt +100 -0
  70. nimare/tests/data/neurosynth_dset.json +2868 -0
  71. nimare/tests/data/neurosynth_laird_studies.json +58773 -0
  72. nimare/tests/data/nidm_pain_dset.json +1349 -0
  73. nimare/tests/data/nimads_annotation.json +1 -0
  74. nimare/tests/data/nimads_studyset.json +1 -0
  75. nimare/tests/data/test_baseline.txt +2 -0
  76. nimare/tests/data/test_pain_dataset.json +1278 -0
  77. nimare/tests/data/test_pain_dataset_multiple_contrasts.json +1242 -0
  78. nimare/tests/data/test_sleuth_file.txt +18 -0
  79. nimare/tests/data/test_sleuth_file2.txt +10 -0
  80. nimare/tests/data/test_sleuth_file3.txt +5 -0
  81. nimare/tests/data/test_sleuth_file4.txt +5 -0
  82. nimare/tests/data/test_sleuth_file5.txt +5 -0
  83. nimare/tests/test_annotate_cogat.py +32 -0
  84. nimare/tests/test_annotate_gclda.py +86 -0
  85. nimare/tests/test_annotate_lda.py +27 -0
  86. nimare/tests/test_dataset.py +99 -0
  87. nimare/tests/test_decode_continuous.py +132 -0
  88. nimare/tests/test_decode_discrete.py +92 -0
  89. nimare/tests/test_diagnostics.py +168 -0
  90. nimare/tests/test_estimator_performance.py +385 -0
  91. nimare/tests/test_extract.py +46 -0
  92. nimare/tests/test_generate.py +247 -0
  93. nimare/tests/test_io.py +240 -0
  94. nimare/tests/test_meta_ale.py +298 -0
  95. nimare/tests/test_meta_cbmr.py +295 -0
  96. nimare/tests/test_meta_ibma.py +240 -0
  97. nimare/tests/test_meta_kernel.py +209 -0
  98. nimare/tests/test_meta_mkda.py +234 -0
  99. nimare/tests/test_nimads.py +21 -0
  100. nimare/tests/test_reports.py +110 -0
  101. nimare/tests/test_stats.py +101 -0
  102. nimare/tests/test_transforms.py +272 -0
  103. nimare/tests/test_utils.py +200 -0
  104. nimare/tests/test_workflows.py +221 -0
  105. nimare/tests/utils.py +126 -0
  106. nimare/transforms.py +907 -0
  107. nimare/utils.py +1367 -0
  108. nimare/workflows/__init__.py +14 -0
  109. nimare/workflows/base.py +189 -0
  110. nimare/workflows/cbma.py +165 -0
  111. nimare/workflows/ibma.py +108 -0
  112. nimare/workflows/macm.py +77 -0
  113. nimare/workflows/misc.py +65 -0
  114. nimare-0.4.2rc4.dist-info/LICENSE +21 -0
  115. nimare-0.4.2rc4.dist-info/METADATA +124 -0
  116. nimare-0.4.2rc4.dist-info/RECORD +119 -0
  117. nimare-0.4.2rc4.dist-info/WHEEL +5 -0
  118. nimare-0.4.2rc4.dist-info/entry_points.txt +2 -0
  119. nimare-0.4.2rc4.dist-info/top_level.txt +2 -0
nimare/transforms.py ADDED
@@ -0,0 +1,907 @@
1
+ """Miscellaneous spatial and statistical transforms."""
2
+
3
+ import copy
4
+ import logging
5
+ import os.path as op
6
+ import warnings
7
+
8
+ import nibabel as nib
9
+ import numpy as np
10
+ import pandas as pd
11
+ from nilearn.reporting import get_clusters_table
12
+ from scipy import stats
13
+
14
+ from nimare.base import NiMAREBase
15
+ from nimare.utils import _dict_to_coordinates, _dict_to_df, _listify, get_masker
16
+
17
+ LGR = logging.getLogger(__name__)
18
+
19
+
20
+ class ImageTransformer(NiMAREBase):
21
+ """A class to create new images from existing ones within a Dataset.
22
+
23
+ This class is a light wrapper around :func:`~nimare.transforms.transform_images`.
24
+
25
+ .. versionadded:: 0.0.9
26
+
27
+ Parameters
28
+ ----------
29
+ target : {'z', 'p', 'beta', 'varcope'} or list
30
+ Target image type. Multiple target types may be specified as a list.
31
+ overwrite : :obj:`bool`, optional
32
+ Whether to overwrite existing files or not. Default is False.
33
+
34
+ See Also
35
+ --------
36
+ nimare.transforms.transform_images : The function called by this class.
37
+ """
38
+
39
+ def __init__(self, target, overwrite=False):
40
+ self.target = _listify(target)
41
+ self.overwrite = overwrite
42
+
43
+ def transform(self, dataset):
44
+ """Generate images of the target type from other image types in a Dataset.
45
+
46
+ Parameters
47
+ ----------
48
+ dataset : :obj:`~nimare.dataset.Dataset`
49
+ A Dataset containing images and relevant metadata.
50
+
51
+ Returns
52
+ -------
53
+ new_dataset : :obj:`~nimare.dataset.Dataset`
54
+ A copy of the input Dataset, with new images added to its images attribute.
55
+ """
56
+ # Using attribute check instead of type check to allow fake Datasets for testing.
57
+ if not hasattr(dataset, "slice"):
58
+ raise ValueError(
59
+ f"Argument 'dataset' must be a valid Dataset object, not a {type(dataset)}."
60
+ )
61
+
62
+ new_dataset = dataset.copy()
63
+ temp_images = dataset.images
64
+
65
+ for target_type in self.target:
66
+ temp_images = transform_images(
67
+ temp_images,
68
+ target=target_type,
69
+ masker=dataset.masker,
70
+ metadata_df=dataset.metadata,
71
+ out_dir=dataset.basepath,
72
+ overwrite=self.overwrite,
73
+ )
74
+ new_dataset.images = temp_images
75
+ return new_dataset
76
+
77
+
78
+ def transform_images(images_df, target, masker, metadata_df=None, out_dir=None, overwrite=False):
79
+ """Generate images of a given type from other image types and write out to files.
80
+
81
+ .. versionchanged:: 0.0.9
82
+
83
+ * [ENH] Add overwrite option to transform_images
84
+
85
+ .. versionadded:: 0.0.4
86
+
87
+ Parameters
88
+ ----------
89
+ images_df : :class:`pandas.DataFrame`
90
+ DataFrame with paths to images for studies in Dataset.
91
+ target : {'z', 'p', 'beta', 'varcope'}
92
+ Target data type.
93
+ masker : :class:`~nilearn.input_data.NiftiMasker` or similar
94
+ Masker used to define orientation and resolution of images.
95
+ Specific voxels defined in mask will not be used, and a new masker
96
+ with _all_ voxels in acquisition matrix selected will be created.
97
+ metadata_df : :class:`pandas.DataFrame` or :obj:`None`, optional
98
+ DataFrame with metadata. Rows in this DataFrame must match those in
99
+ ``images_df``, including the ``'id'`` column.
100
+ out_dir : :obj:`str` or :obj:`None`, optional
101
+ Path to output directory. If None, use folder containing first image
102
+ for each study in ``images_df``.
103
+ overwrite : :obj:`bool`, optional
104
+ Whether to overwrite existing files or not. Default is False.
105
+
106
+ Returns
107
+ -------
108
+ images_df : :class:`pandas.DataFrame`
109
+ DataFrame with paths to new images added.
110
+ """
111
+ new_images_df = images_df.copy() # Work on a copy of the images_df
112
+
113
+ valid_targets = {"t", "z", "p", "beta", "varcope"}
114
+ if target not in valid_targets:
115
+ raise ValueError(
116
+ f"Target type {target} not supported. Must be one of: {', '.join(valid_targets)}"
117
+ )
118
+
119
+ mask_img = masker.mask_img
120
+ new_mask = np.ones(mask_img.shape, int)
121
+ new_mask = nib.Nifti1Image(new_mask, mask_img.affine, header=mask_img.header)
122
+ new_masker = get_masker(new_mask)
123
+ res = masker.mask_img.header.get_zooms()
124
+ res = "x".join([str(r) for r in res])
125
+ if target not in images_df.columns:
126
+ target_ids = images_df["id"].values
127
+ else:
128
+ target_ids = images_df.loc[images_df[target].isnull(), "id"]
129
+
130
+ for id_ in target_ids:
131
+ row = images_df.loc[images_df["id"] == id_].iloc[0]
132
+
133
+ # Determine output filename, if file can be generated
134
+ if out_dir is None:
135
+ options = [r for r in row.values if isinstance(r, str) and op.isfile(r)]
136
+ id_out_dir = op.dirname(options[0])
137
+ else:
138
+ id_out_dir = out_dir
139
+ new_file = op.join(id_out_dir, f"{id_}_{res}_{target}.nii.gz")
140
+
141
+ # Grab columns with actual values
142
+ available_data = row[~row.isnull()].to_dict()
143
+ if metadata_df is not None:
144
+ metadata_row = metadata_df.loc[metadata_df["id"] == id_].iloc[0]
145
+ metadata = metadata_row[~metadata_row.isnull()].to_dict()
146
+ for k, v in metadata.items():
147
+ if k not in available_data.keys():
148
+ available_data[k] = v
149
+
150
+ # Get converted data
151
+ img = resolve_transforms(target, available_data, new_masker)
152
+ if img is not None:
153
+ if overwrite or not op.isfile(new_file):
154
+ img.to_filename(new_file)
155
+ else:
156
+ LGR.debug("Image already exists. Not overwriting.")
157
+
158
+ new_images_df.loc[new_images_df["id"] == id_, target] = new_file
159
+ else:
160
+ new_images_df.loc[new_images_df["id"] == id_, target] = None
161
+ return new_images_df
162
+
163
+
164
+ def resolve_transforms(target, available_data, masker):
165
+ """Determine and apply the appropriate transforms to a target image type from available data.
166
+
167
+ .. versionchanged:: 0.0.8
168
+
169
+ * [FIX] Remove unnecessary dimensions from output image object *img_like*. \
170
+ Now, the image object only has 3 dimensions.
171
+
172
+ .. versionadded:: 0.0.4
173
+
174
+ Parameters
175
+ ----------
176
+ target : {'z', 'p', 't', 'beta', 'varcope'}
177
+ Target image type.
178
+ available_data : dict
179
+ Dictionary mapping data types to their values. Images in the dictionary
180
+ are paths to files.
181
+ masker : nilearn Masker
182
+ Masker used to convert images to arrays and back. Preferably, this mask
183
+ should cover the full acquisition matrix (rather than an ROI), given
184
+ that the calculated images will be saved and used for the full Dataset.
185
+
186
+ Returns
187
+ -------
188
+ img_like or None
189
+ Image object with the desired data type, if it can be generated.
190
+ Otherwise, None.
191
+ """
192
+ if target in available_data.keys():
193
+ LGR.warning(f"Target '{target}' already available.")
194
+ return available_data[target]
195
+
196
+ if target == "z":
197
+ if ("t" in available_data.keys()) and ("sample_sizes" in available_data.keys()):
198
+ dof = sample_sizes_to_dof(available_data["sample_sizes"])
199
+ t = masker.transform(available_data["t"])
200
+ z = t_to_z(t, dof)
201
+ elif "p" in available_data.keys():
202
+ p = masker.transform(available_data["p"])
203
+ z = p_to_z(p)
204
+ else:
205
+ return None
206
+ z = masker.inverse_transform(z.squeeze())
207
+ return z
208
+ elif target == "t":
209
+ # will return none given no transform/target exists
210
+ temp = resolve_transforms("z", available_data, masker)
211
+ if temp is not None:
212
+ available_data["z"] = temp
213
+
214
+ if ("z" in available_data.keys()) and ("sample_sizes" in available_data.keys()):
215
+ dof = sample_sizes_to_dof(available_data["sample_sizes"])
216
+ z = masker.transform(available_data["z"])
217
+ t = z_to_t(z, dof)
218
+ t = masker.inverse_transform(t.squeeze())
219
+ return t
220
+ else:
221
+ return None
222
+ elif target == "beta":
223
+ if "t" not in available_data.keys():
224
+ # will return none given no transform/target exists
225
+ temp = resolve_transforms("t", available_data, masker)
226
+ if temp is not None:
227
+ available_data["t"] = temp
228
+
229
+ if "varcope" not in available_data.keys():
230
+ temp = resolve_transforms("varcope", available_data, masker)
231
+ if temp is not None:
232
+ available_data["varcope"] = temp
233
+
234
+ if ("t" in available_data.keys()) and ("varcope" in available_data.keys()):
235
+ t = masker.transform(available_data["t"])
236
+ varcope = masker.transform(available_data["varcope"])
237
+ beta = t_and_varcope_to_beta(t, varcope)
238
+ beta = masker.inverse_transform(beta.squeeze())
239
+ return beta
240
+ else:
241
+ return None
242
+ elif target == "varcope":
243
+ if "se" in available_data.keys():
244
+ se = masker.transform(available_data["se"])
245
+ varcope = se_to_varcope(se)
246
+ elif ("samplevar_dataset" in available_data.keys()) and (
247
+ "sample_sizes" in available_data.keys()
248
+ ):
249
+ sample_size = sample_sizes_to_sample_size(available_data["sample_sizes"])
250
+ samplevar_dataset = masker.transform(available_data["samplevar_dataset"])
251
+ varcope = samplevar_dataset_to_varcope(samplevar_dataset, sample_size)
252
+ elif ("sd" in available_data.keys()) and ("sample_sizes" in available_data.keys()):
253
+ sample_size = sample_sizes_to_sample_size(available_data["sample_sizes"])
254
+ sd = masker.transform(available_data["sd"])
255
+ varcope = sd_to_varcope(sd, sample_size)
256
+ varcope = masker.inverse_transform(varcope)
257
+ elif ("t" in available_data.keys()) and ("beta" in available_data.keys()):
258
+ t = masker.transform(available_data["t"])
259
+ beta = masker.transform(available_data["beta"])
260
+ varcope = t_and_beta_to_varcope(t, beta)
261
+ else:
262
+ return None
263
+ varcope = masker.inverse_transform(varcope.squeeze())
264
+ return varcope
265
+ elif target == "p":
266
+ if ("t" in available_data.keys()) and ("sample_sizes" in available_data.keys()):
267
+ dof = sample_sizes_to_dof(available_data["sample_sizes"])
268
+ t = masker.transform(available_data["t"])
269
+ z = t_to_z(t, dof)
270
+ p = z_to_p(z)
271
+ elif "z" in available_data.keys():
272
+ z = masker.transform(available_data["z"])
273
+ p = z_to_p(z)
274
+ else:
275
+ return None
276
+ p = masker.inverse_transform(p.squeeze())
277
+ return p
278
+ else:
279
+ return None
280
+
281
+
282
+ class ImagesToCoordinates(NiMAREBase):
283
+ """Transformer from images to coordinates.
284
+
285
+ .. versionadded:: 0.0.8
286
+
287
+ Parameters
288
+ ----------
289
+ merge_strategy : {"fill", "replace", "demolish"}, optional
290
+ Strategy for how to incorporate the generated coordinates with possible pre-existing
291
+ coordinates. The available options are
292
+
293
+ ================ =========================================================================
294
+ "fill" (default) Only add coordinates to study contrasts that do not have coordinates.
295
+ If a study contrast has both image and coordinate data, the original
296
+ coordinate data will be kept.
297
+ "replace" Replace existing coordinates with coordinates generated by this function.
298
+ If a study contrast only has coordinate data and no images or if the
299
+ statistical threshold is too high for nimare to detect any peaks the
300
+ original coordinates will be kept.
301
+ "demolish" Only keep generated coordinates and discard any study contrasts with
302
+ coordinate data, but no images.
303
+ ================ =========================================================================
304
+
305
+ cluster_threshold : :obj:`int` or `None`, optional
306
+ Cluster size threshold, in voxels. Default=None.
307
+ remove_subpeaks : :obj:`bool`, optional
308
+ If True, removes subpeaks from the cluster results. Default=False.
309
+ two_sided : :obj:`bool`, optional
310
+ Whether to employ two-sided thresholding or to evaluate positive values only.
311
+ Default=False.
312
+ min_distance : :obj:`float`, optional
313
+ Minimum distance between subpeaks in mm. Default=8mm.
314
+ z_threshold : :obj:`float`
315
+ Cluster forming z-scale threshold. Default=3.1.
316
+
317
+ Notes
318
+ -----
319
+ The raw Z and/or P maps are not corrected for multiple comparisons. Uncorrected z-values and/or
320
+ p-values are used for thresholding.
321
+ """
322
+
323
+ def __init__(
324
+ self,
325
+ merge_strategy="fill",
326
+ cluster_threshold=None,
327
+ remove_subpeaks=False,
328
+ two_sided=False,
329
+ min_distance=8.0,
330
+ z_threshold=3.1,
331
+ ):
332
+ self.merge_strategy = merge_strategy
333
+ self.cluster_threshold = cluster_threshold
334
+ self.remove_subpeaks = remove_subpeaks
335
+ self.min_distance = min_distance
336
+ self.two_sided = two_sided
337
+ self.z_threshold = z_threshold
338
+
339
+ def transform(self, dataset):
340
+ """Create coordinate peaks from statistical images.
341
+
342
+ Parameters
343
+ ----------
344
+ dataset : :obj:`~nimare.dataset.Dataset`
345
+ Dataset with z maps and/or p maps
346
+ that can be converted to coordinates.
347
+
348
+ Returns
349
+ -------
350
+ dataset : :obj:`~nimare.dataset.Dataset`
351
+ Dataset with coordinates generated from
352
+ images and metadata indicating origin
353
+ of coordinates ('original' or 'nimare').
354
+ """
355
+ # relevant variables from dataset
356
+ space = dataset.space
357
+ masker = dataset.masker
358
+ images_df = dataset.images
359
+ metadata = dataset.metadata.copy()
360
+
361
+ # conform space specification
362
+ if "mni" in space.lower() or "ale" in space.lower():
363
+ coordinate_space = "MNI"
364
+ elif "tal" in space.lower():
365
+ coordinate_space = "TAL"
366
+ else:
367
+ coordinate_space = None
368
+
369
+ coordinates_dict = {}
370
+ for _, row in images_df.iterrows():
371
+ if row["id"] in list(dataset.coordinates["id"]) and self.merge_strategy == "fill":
372
+ continue
373
+
374
+ if row.get("z"):
375
+ clusters = get_clusters_table(
376
+ nib.funcs.squeeze_image(nib.load(row.get("z"))),
377
+ self.z_threshold,
378
+ self.cluster_threshold,
379
+ self.two_sided,
380
+ self.min_distance,
381
+ )
382
+ elif row.get("p"):
383
+ LGR.info(
384
+ f"No Z map for {row['id']}, using p map "
385
+ "(p-values will be treated as positive z-values)"
386
+ )
387
+ if self.two_sided:
388
+ LGR.warning(f"Cannot use two_sided threshold using a p map for {row['id']}")
389
+
390
+ p_threshold = 1 - z_to_p(self.z_threshold)
391
+ nimg = nib.funcs.squeeze_image(nib.load(row.get("p")))
392
+ inv_nimg = nib.Nifti1Image(1 - nimg.get_fdata(), nimg.affine, nimg.header)
393
+ clusters = get_clusters_table(
394
+ inv_nimg,
395
+ p_threshold,
396
+ self.cluster_threshold,
397
+ self.min_distance,
398
+ )
399
+ # Peak stat p-values are reported as 1 - p in get_clusters_table
400
+ clusters["Peak Stat"] = p_to_z(1 - clusters["Peak Stat"])
401
+ else:
402
+ LGR.warning(f"No Z or p map for {row['id']}, skipping...")
403
+ continue
404
+
405
+ # skip entry if no clusters are found
406
+ if clusters.empty:
407
+ LGR.warning(
408
+ f"No clusters were found for {row['id']} at a threshold of {self.z_threshold}"
409
+ )
410
+ continue
411
+
412
+ if self.remove_subpeaks:
413
+ # subpeaks are identified as 1a, 1b, etc
414
+ # while peaks are kept as 1, 2, 3, etc,
415
+ # so removing all non-int rows will
416
+ # keep main peaks while removing subpeaks
417
+ clusters = clusters[clusters["Cluster ID"].apply(lambda x: isinstance(x, int))]
418
+
419
+ coordinates_dict[row["study_id"]] = {
420
+ "contrasts": {
421
+ row["contrast_id"]: {
422
+ "coords": {
423
+ "space": coordinate_space,
424
+ "x": list(clusters["X"]),
425
+ "y": list(clusters["Y"]),
426
+ "z": list(clusters["Z"]),
427
+ "z_stat": list(clusters["Peak Stat"]),
428
+ },
429
+ "metadata": {"coordinate_source": "nimare"},
430
+ }
431
+ }
432
+ }
433
+
434
+ # only the generated coordinates ('demolish')
435
+ coordinates_df = _dict_to_coordinates(coordinates_dict, masker, space)
436
+ meta_df = _dict_to_df(
437
+ pd.DataFrame(dataset._ids),
438
+ coordinates_dict,
439
+ "metadata",
440
+ )
441
+
442
+ if "coordinate_source" in meta_df.columns:
443
+ metadata["coordinate_source"] = meta_df["coordinate_source"]
444
+ else:
445
+ # nimare did not overwrite any coordinates
446
+ metadata["coordinate_source"] = ["original"] * metadata.shape[0]
447
+
448
+ if self.merge_strategy != "demolish":
449
+ original_idxs = ~dataset.coordinates["id"].isin(coordinates_df["id"])
450
+ old_coordinates_df = dataset.coordinates[original_idxs]
451
+ coordinates_df = pd.concat([coordinates_df, old_coordinates_df], ignore_index=True)
452
+
453
+ # specify original coordinates
454
+ original_ids = set(old_coordinates_df["id"])
455
+ metadata.loc[metadata["id"].isin(original_ids), "coordinate_source"] = "original"
456
+
457
+ if "z_stat" in coordinates_df.columns:
458
+ # ensure z_stat is treated as float
459
+ coordinates_df["z_stat"] = coordinates_df["z_stat"].astype(float)
460
+
461
+ # Raise warning if coordinates dataset contains both positive and negative z_stats
462
+ if ((coordinates_df["z_stat"].values >= 0).any()) and (
463
+ (coordinates_df["z_stat"].values < 0).any()
464
+ ):
465
+ warnings.warn(
466
+ "Coordinates dataset contains both positive and negative z_stats. "
467
+ "The algorithms currently implemented in NiMARE are designed for "
468
+ "one-sided tests. This might lead to unexpected results."
469
+ )
470
+
471
+ new_dataset = copy.deepcopy(dataset)
472
+ new_dataset.coordinates = coordinates_df
473
+ new_dataset.metadata = metadata
474
+
475
+ return new_dataset
476
+
477
+
478
+ class StandardizeField(NiMAREBase):
479
+ """Standardize metadata fields."""
480
+
481
+ def __init__(self, fields):
482
+ self.fields = fields # the fields to be standardized
483
+
484
+ def transform(self, dataset):
485
+ """Standardize metadata fields."""
486
+ # update a copy of the dataset
487
+ dataset = dataset.copy()
488
+
489
+ categorical_metadata, numerical_metadata = [], []
490
+ for metadata_name in self.fields:
491
+ if np.array_equal(
492
+ dataset.annotations[metadata_name], dataset.annotations[metadata_name].astype(str)
493
+ ):
494
+ categorical_metadata.append(metadata_name)
495
+ elif np.array_equal(
496
+ dataset.annotations[metadata_name],
497
+ dataset.annotations[metadata_name].astype(float),
498
+ ):
499
+ numerical_metadata.append(metadata_name)
500
+ if len(categorical_metadata) > 0:
501
+ LGR.warning(f"Categorical metadata {categorical_metadata} can't be standardized.")
502
+ if len(numerical_metadata) == 0:
503
+ raise ValueError("No numerical metadata found.")
504
+
505
+ moderators = dataset.annotations[numerical_metadata]
506
+ standardize_moderators = moderators - np.mean(moderators, axis=0)
507
+ standardize_moderators /= np.std(standardize_moderators, axis=0)
508
+ if isinstance(self.fields, str):
509
+ column_name = "standardized_" + self.fields
510
+ elif isinstance(self.fields, list):
511
+ column_name = ["standardized_" + moderator for moderator in numerical_metadata]
512
+ dataset.annotations[column_name] = standardize_moderators
513
+
514
+ return dataset
515
+
516
+
517
+ def sample_sizes_to_dof(sample_sizes):
518
+ """Calculate degrees of freedom from a list of sample sizes using a simple heuristic.
519
+
520
+ .. versionadded:: 0.0.4
521
+
522
+ Parameters
523
+ ----------
524
+ sample_sizes : array_like
525
+ A list of sample sizes for different groups in the study.
526
+
527
+ Returns
528
+ -------
529
+ dof : int
530
+ An estimate of degrees of freedom. Number of participants minus number
531
+ of groups.
532
+ """
533
+ dof = np.sum(sample_sizes) - len(sample_sizes)
534
+ return dof
535
+
536
+
537
+ def sample_sizes_to_sample_size(sample_sizes):
538
+ """Calculate appropriate sample size from a list of sample sizes using a simple heuristic.
539
+
540
+ .. versionadded:: 0.0.4
541
+
542
+ Parameters
543
+ ----------
544
+ sample_sizes : array_like
545
+ A list of sample sizes for different groups in the study.
546
+
547
+ Returns
548
+ -------
549
+ sample_size : int
550
+ Total (sum) sample size.
551
+ """
552
+ sample_size = np.sum(sample_sizes)
553
+ return sample_size
554
+
555
+
556
+ def sd_to_varcope(sd, sample_size):
557
+ """Convert standard deviation to sampling variance.
558
+
559
+ .. versionadded:: 0.0.3
560
+
561
+ Parameters
562
+ ----------
563
+ sd : array_like
564
+ Standard deviation of the sample
565
+ sample_size : int
566
+ Sample size
567
+
568
+ Returns
569
+ -------
570
+ varcope : array_like
571
+ Sampling variance of the parameter
572
+ """
573
+ se = sd / np.sqrt(sample_size)
574
+ varcope = se_to_varcope(se)
575
+ return varcope
576
+
577
+
578
+ def se_to_varcope(se):
579
+ """Convert standard error values to sampling variance.
580
+
581
+ .. versionadded:: 0.0.3
582
+
583
+ Parameters
584
+ ----------
585
+ se : array_like
586
+ Standard error of the sample parameter
587
+
588
+ Returns
589
+ -------
590
+ varcope : array_like
591
+ Sampling variance of the parameter
592
+
593
+ Notes
594
+ -----
595
+ Sampling variance is standard error squared.
596
+ """
597
+ varcope = se**2
598
+ return varcope
599
+
600
+
601
+ def samplevar_dataset_to_varcope(samplevar_dataset, sample_size):
602
+ """Convert "sample variance of the dataset" to "sampling variance".
603
+
604
+ .. versionadded:: 0.0.3
605
+
606
+ Parameters
607
+ ----------
608
+ samplevar_dataset : array_like
609
+ Sample variance of the dataset (i.e., variance of the individual observations in a single
610
+ sample). Can be calculated with ``np.var``.
611
+ sample_size : int
612
+ Sample size
613
+
614
+ Returns
615
+ -------
616
+ varcope : array_like
617
+ Sampling variance of the parameter (i.e., variance of sampling distribution for the
618
+ parameter).
619
+
620
+ Notes
621
+ -----
622
+ Sampling variance is sample variance divided by sample size.
623
+ """
624
+ varcope = samplevar_dataset / sample_size
625
+ return varcope
626
+
627
+
628
+ def t_and_varcope_to_beta(t, varcope):
629
+ """Convert t-statistic to parameter estimate using sampling variance.
630
+
631
+ .. versionadded:: 0.0.3
632
+
633
+ Parameters
634
+ ----------
635
+ t : array_like
636
+ T-statistics of the parameter
637
+ varcope : array_like
638
+ Sampling variance of the parameter
639
+
640
+ Returns
641
+ -------
642
+ beta : array_like
643
+ Parameter estimates
644
+ """
645
+ beta = t * np.sqrt(varcope)
646
+ return beta
647
+
648
+
649
+ def t_and_beta_to_varcope(t, beta):
650
+ """Convert t-statistic to sampling variance using parameter estimate.
651
+
652
+ .. versionadded:: 0.0.4
653
+
654
+ Parameters
655
+ ----------
656
+ t : array_like
657
+ T-statistics of the parameter
658
+ beta : array_like
659
+ Parameter estimates
660
+
661
+ Returns
662
+ -------
663
+ varcope : array_like
664
+ Sampling variance of the parameter
665
+ """
666
+ varcope = (beta / t) ** 2
667
+ return varcope
668
+
669
+
670
+ def z_to_p(z, tail="two"):
671
+ """Convert z-values to p-values.
672
+
673
+ .. versionadded:: 0.0.8
674
+
675
+ Parameters
676
+ ----------
677
+ z : array_like
678
+ Z-statistics
679
+ tail : {'one', 'two'}, optional
680
+ Whether p-values come from one-tailed or two-tailed test. Default is
681
+ 'two'.
682
+
683
+ Returns
684
+ -------
685
+ p : array_like
686
+ P-values
687
+ """
688
+ z = np.array(z)
689
+ if tail == "two":
690
+ p = stats.norm.sf(abs(z)) * 2
691
+ elif tail == "one":
692
+ p = stats.norm.sf(z)
693
+ else:
694
+ raise ValueError('Argument "tail" must be one of ["one", "two"]')
695
+
696
+ if p.shape == ():
697
+ p = p[()]
698
+ return p
699
+
700
+
701
+ def p_to_z(p, tail="two"):
702
+ """Convert p-values to (unsigned) z-values.
703
+
704
+ .. versionadded:: 0.0.3
705
+
706
+ Parameters
707
+ ----------
708
+ p : array_like
709
+ P-values
710
+ tail : {'one', 'two'}, optional
711
+ Whether p-values come from one-tailed or two-tailed test. Default is
712
+ 'two'.
713
+
714
+ Returns
715
+ -------
716
+ z : array_like
717
+ Z-statistics (unsigned)
718
+ """
719
+ p = np.array(p)
720
+ if tail == "two":
721
+ z = stats.norm.isf(p / 2)
722
+ elif tail == "one":
723
+ z = stats.norm.isf(p)
724
+ z = np.array(z)
725
+ z[z < 0] = 0
726
+ else:
727
+ raise ValueError('Argument "tail" must be one of ["one", "two"]')
728
+
729
+ if z.shape == ():
730
+ z = z[()]
731
+ return z
732
+
733
+
734
+ def t_to_z(t_values, dof):
735
+ """Convert t-statistics to z-statistics.
736
+
737
+ .. versionadded:: 0.0.3
738
+
739
+ An implementation of :footcite:t:`hughett2008accurate` from Vanessa Sochat's TtoZ package
740
+ :footcite:p:`sochat2015ttoz`.
741
+
742
+ Parameters
743
+ ----------
744
+ t_values : array_like
745
+ T-statistics
746
+ dof : int
747
+ Degrees of freedom
748
+
749
+ Returns
750
+ -------
751
+ z_values : array_like
752
+ Z-statistics
753
+
754
+ License
755
+ -------
756
+ The MIT License (MIT)
757
+ Copyright (c) 2015 Vanessa Sochat
758
+
759
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
760
+ and associated documentation files (the "Software"), to deal in the Software without
761
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
762
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
763
+ Software is furnished to do so, subject to the following conditions:
764
+
765
+ The above copyright notice and this permission notice shall be included in all copies or
766
+ substantial portions of the Software.
767
+
768
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
769
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
770
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
771
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
772
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
773
+ SOFTWARE.
774
+
775
+ References
776
+ ----------
777
+ .. footbibliography::
778
+ """
779
+ # Select just the nonzero voxels
780
+ nonzero = t_values[t_values != 0]
781
+
782
+ # We will store our results here
783
+ z_values_nonzero = np.zeros(len(nonzero))
784
+
785
+ # Select values less than or == 0, and greater than zero
786
+ c = np.zeros(len(nonzero))
787
+ k1 = nonzero <= c
788
+ k2 = nonzero > c
789
+
790
+ # Subset the data into two sets
791
+ t1 = nonzero[k1]
792
+ t2 = nonzero[k2]
793
+
794
+ # Calculate p values for <=0
795
+ p_values_t1 = stats.t.cdf(t1, df=dof)
796
+ p_values_t1[p_values_t1 < np.finfo(p_values_t1.dtype).eps] = np.finfo(p_values_t1.dtype).eps
797
+ z_values_t1 = stats.norm.ppf(p_values_t1)
798
+
799
+ # Calculate p values for > 0
800
+ p_values_t2 = stats.t.cdf(-t2, df=dof)
801
+ p_values_t2[p_values_t2 < np.finfo(p_values_t2.dtype).eps] = np.finfo(p_values_t2.dtype).eps
802
+ z_values_t2 = -stats.norm.ppf(p_values_t2)
803
+ z_values_nonzero[k1] = z_values_t1
804
+ z_values_nonzero[k2] = z_values_t2
805
+
806
+ z_values = np.zeros(t_values.shape)
807
+ z_values[t_values != 0] = z_values_nonzero
808
+ return z_values
809
+
810
+
811
+ def z_to_t(z_values, dof):
812
+ """Convert z-statistics to t-statistics.
813
+
814
+ .. versionadded:: 0.0.3
815
+
816
+ An inversion of the t_to_z implementation of :footcite:t:`hughett2008accurate` from
817
+ Vanessa Sochat's TtoZ package :footcite:p:`sochat2015ttoz`.
818
+
819
+ Parameters
820
+ ----------
821
+ z_values : array_like
822
+ Z-statistics
823
+ dof : int
824
+ Degrees of freedom
825
+
826
+ Returns
827
+ -------
828
+ t_values : array_like
829
+ T-statistics
830
+
831
+ References
832
+ ----------
833
+ .. footbibliography::
834
+ """
835
+ # Select just the nonzero voxels
836
+ nonzero = z_values[z_values != 0]
837
+
838
+ # We will store our results here
839
+ t_values_nonzero = np.zeros(len(nonzero))
840
+
841
+ # Select values less than or == 0, and greater than zero
842
+ c = np.zeros(len(nonzero))
843
+ k1 = nonzero <= c
844
+ k2 = nonzero > c
845
+
846
+ # Subset the data into two sets
847
+ z1 = nonzero[k1]
848
+ z2 = nonzero[k2]
849
+
850
+ # Calculate p values for <=0
851
+ p_values_z1 = stats.norm.cdf(z1)
852
+ t_values_z1 = stats.t.ppf(p_values_z1, df=dof)
853
+
854
+ # Calculate p values for > 0
855
+ p_values_z2 = stats.norm.cdf(-z2)
856
+ t_values_z2 = -stats.t.ppf(p_values_z2, df=dof)
857
+ t_values_nonzero[k1] = t_values_z1
858
+ t_values_nonzero[k2] = t_values_z2
859
+
860
+ t_values = np.zeros(z_values.shape)
861
+ t_values[z_values != 0] = t_values_nonzero
862
+ return t_values
863
+
864
+
865
+ def t_to_d(t_values, sample_sizes):
866
+ """Convert t-statistics to Cohen's d.
867
+
868
+ Parameters
869
+ ----------
870
+ t_values : array_like
871
+ T-statistics
872
+ sample_sizes : array_like
873
+ Sample sizes
874
+
875
+ Returns
876
+ -------
877
+ d_values : array_like
878
+ Cohen's d
879
+ """
880
+ d_values = t_values / np.sqrt(sample_sizes)
881
+ return d_values
882
+
883
+
884
+ def d_to_g(d, N, return_variance=False):
885
+ """Convert Cohen's d to Hedges' g.
886
+
887
+ Parameters
888
+ ----------
889
+ d : array_like
890
+ Cohen's d
891
+ N : array_like
892
+ Sample sizes
893
+ return_variance : bool, optional
894
+ Whether to return the variance of Hedges' g. Default is False.
895
+
896
+ Returns
897
+ -------
898
+ g_values : array_like
899
+ Hedges' g
900
+ """
901
+ # Calculate bias correction h(N)
902
+ h = 1 - (3 / (4 * (N - 1) - 1))
903
+
904
+ if return_variance:
905
+ return d * h, ((N - 1) * (1 + N * d**2) * (h**2) / (N * (N - 3))) - d**2
906
+
907
+ return d * h