nltools 0.6.0.dev0__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 (95) hide show
  1. nltools/__init__.py +55 -0
  2. nltools/algorithms/__init__.py +90 -0
  3. nltools/algorithms/alignment/__init__.py +21 -0
  4. nltools/algorithms/alignment/procrustes.py +565 -0
  5. nltools/algorithms/alignment/srm.py +758 -0
  6. nltools/algorithms/backends.py +1059 -0
  7. nltools/algorithms/corrections.py +177 -0
  8. nltools/algorithms/decoding.py +327 -0
  9. nltools/algorithms/inference/__init__.py +50 -0
  10. nltools/algorithms/inference/bootstrap.py +1386 -0
  11. nltools/algorithms/inference/correlation.py +373 -0
  12. nltools/algorithms/inference/intersubject.py +422 -0
  13. nltools/algorithms/inference/isc.py +1554 -0
  14. nltools/algorithms/inference/matrix.py +602 -0
  15. nltools/algorithms/inference/one_sample.py +288 -0
  16. nltools/algorithms/inference/random.py +122 -0
  17. nltools/algorithms/inference/timeseries.py +347 -0
  18. nltools/algorithms/inference/two_sample.py +212 -0
  19. nltools/algorithms/inference/utils.py +58 -0
  20. nltools/algorithms/inference/validation.py +282 -0
  21. nltools/algorithms/neighborhoods.py +207 -0
  22. nltools/algorithms/outliers.py +308 -0
  23. nltools/algorithms/regression.py +83 -0
  24. nltools/algorithms/signal.py +303 -0
  25. nltools/algorithms/similarity.py +234 -0
  26. nltools/algorithms/validation.py +151 -0
  27. nltools/cross_validation.py +72 -0
  28. nltools/data/__init__.py +30 -0
  29. nltools/data/adjacency/__init__.py +875 -0
  30. nltools/data/adjacency/io.py +111 -0
  31. nltools/data/adjacency/modeling.py +569 -0
  32. nltools/data/adjacency/plotting.py +174 -0
  33. nltools/data/adjacency/state.py +349 -0
  34. nltools/data/adjacency/stats.py +596 -0
  35. nltools/data/adjacency/utils.py +79 -0
  36. nltools/data/atlases/__init__.py +23 -0
  37. nltools/data/atlases/labeling.py +158 -0
  38. nltools/data/atlases/loading.py +76 -0
  39. nltools/data/atlases/registry.py +96 -0
  40. nltools/data/atlases/reporting.py +456 -0
  41. nltools/data/braindata/__init__.py +2170 -0
  42. nltools/data/braindata/analysis.py +1381 -0
  43. nltools/data/braindata/bootstrap.py +398 -0
  44. nltools/data/braindata/io.py +896 -0
  45. nltools/data/braindata/modeling.py +594 -0
  46. nltools/data/braindata/plotting.py +501 -0
  47. nltools/data/braindata/prediction.py +1250 -0
  48. nltools/data/braindata/utils.py +348 -0
  49. nltools/data/braindata/validation.py +197 -0
  50. nltools/data/braindata/viewer.js +266 -0
  51. nltools/data/braindata/viewer.py +770 -0
  52. nltools/data/combine.py +27 -0
  53. nltools/data/designmatrix/__init__.py +1032 -0
  54. nltools/data/designmatrix/append.py +518 -0
  55. nltools/data/designmatrix/diagnostics.py +248 -0
  56. nltools/data/designmatrix/io.py +356 -0
  57. nltools/data/designmatrix/plotting.py +291 -0
  58. nltools/data/designmatrix/regressors.py +463 -0
  59. nltools/data/designmatrix/transforms.py +200 -0
  60. nltools/data/designmatrix/utils.py +350 -0
  61. nltools/data/ownership.py +129 -0
  62. nltools/data/results.py +291 -0
  63. nltools/data/roc/__init__.py +398 -0
  64. nltools/data/simulator/__init__.py +927 -0
  65. nltools/data/simulator/haxby.py +124 -0
  66. nltools/data/validation.py +83 -0
  67. nltools/datasets.py +218 -0
  68. nltools/io/__init__.py +10 -0
  69. nltools/io/events.py +67 -0
  70. nltools/io/h5.py +246 -0
  71. nltools/mask.py +403 -0
  72. nltools/models/__init__.py +11 -0
  73. nltools/models/glm.py +543 -0
  74. nltools/models/results.py +49 -0
  75. nltools/models/ridge.py +1303 -0
  76. nltools/models/validation.py +26 -0
  77. nltools/plotting/__init__.py +32 -0
  78. nltools/plotting/adjacency.py +421 -0
  79. nltools/plotting/brain.py +669 -0
  80. nltools/plotting/decomposition.py +111 -0
  81. nltools/plotting/prediction.py +110 -0
  82. nltools/resources/covariates_example.csv +161 -0
  83. nltools/resources/onsets_example.csv +40 -0
  84. nltools/templates/__init__.py +51 -0
  85. nltools/templates/config.py +144 -0
  86. nltools/templates/fetch.py +260 -0
  87. nltools/templates/matching.py +183 -0
  88. nltools/templates/paths.py +106 -0
  89. nltools/templates/registry.py +25 -0
  90. nltools/utils.py +230 -0
  91. nltools/version.py +13 -0
  92. nltools-0.6.0.dev0.dist-info/METADATA +95 -0
  93. nltools-0.6.0.dev0.dist-info/RECORD +95 -0
  94. nltools-0.6.0.dev0.dist-info/WHEEL +4 -0
  95. nltools-0.6.0.dev0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,398 @@
1
+ """Bootstrap resampling for `BrainData` — aggregate statistics and Ridge model statistics.
2
+
3
+ `BrainData.bootstrap` delegates here.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ from .utils import _result_from_array
9
+
10
+ #: Statistics that reduce `bd.data` directly and need no fitted model.
11
+ SIMPLE_STATS = ("mean", "median", "std", "sum", "min", "max")
12
+
13
+ #: Statistics that resample a fitted `_Ridge` and therefore need explicit
14
+ #: training features. `'predict'` additionally needs evaluation features.
15
+ FITTED_STATS = ("weights", "predict")
16
+
17
+
18
+ def _bootstrap(
19
+ bd,
20
+ statistic,
21
+ *,
22
+ X=None,
23
+ X_test=None,
24
+ n_samples=5000,
25
+ confidence_level=0.95,
26
+ device="cpu",
27
+ memory_budget_gb=None,
28
+ return_samples=False,
29
+ n_jobs=-1,
30
+ random_state=None,
31
+ progress_bar=False,
32
+ ):
33
+ """Bootstrap a statistic and its uncertainty, on CPU workers or a GPU.
34
+
35
+ Resamples observations with replacement and aggregates the replicates as
36
+ they complete, into a running Welford variance plus just enough retained
37
+ order statistics per output element to reproduce the exact percentile
38
+ interval. What the run holds is that retained tail — about
39
+ `(1 - confidence_level)` of the replicates per element — plus one dispatch
40
+ window, rather than all `n_samples` maps. This is memory-efficient, not
41
+ constant-memory: the tail still grows with `n_samples`, and
42
+ `return_samples=True` keeps the whole distribution.
43
+
44
+ A Ridge bootstrap resamples the explicitly supplied training `X` together
45
+ with `bd.data`, using the same row indices for every feature space, and
46
+ refits with the fitted model's selected `alpha_` — and, for a banded model,
47
+ its `feature_space_weights_` — held fixed. It never reruns cross-validation
48
+ or the banded random search.
49
+
50
+ Args:
51
+ bd (BrainData): Data to resample.
52
+ statistic (str): Statistic to bootstrap. Basic aggregates: ``'mean'``,
53
+ ``'median'``, ``'std'``, ``'sum'``, ``'min'``, ``'max'``. Model
54
+ statistics (require a fitted `_Ridge`): ``'weights'`` or
55
+ ``'predict'``.
56
+ X (np.ndarray | Mapping[str, np.ndarray] | None): Training features in
57
+ their original row order, required by both Ridge statistics and
58
+ rejected by the basic ones. A matrix for ordinary Ridge; a mapping
59
+ with exactly the fitted feature-space names for banded Ridge.
60
+ X_test (np.ndarray | Mapping[str, np.ndarray] | None): Evaluation
61
+ features for ``statistic='predict'``, in the same structure as `X`.
62
+ It may have any row count.
63
+ n_samples (int): Number of bootstrap replicates, at least two. Default
64
+ ``5000``.
65
+ confidence_level (float): Confidence level of the reported interval,
66
+ strictly between zero and one. Default ``0.95``.
67
+ device (str): Compute device for Ridge refits: ``'cpu'`` (default) or
68
+ ``'gpu'`` (PyTorch on CUDA/MPS; raises if neither is available).
69
+ Basic statistics reject ``'gpu'``.
70
+ memory_budget_gb (float | None): Working-memory budget in GB. It
71
+ governs the output preflight and CPU-worker planning for every
72
+ statistic, and GPU batch sizing for the Ridge ones. ``None``
73
+ (default) measures the device.
74
+ return_samples (bool): Retain and return every replicate. Default
75
+ ``False``. It changes retention only, never interval semantics.
76
+ n_jobs (int): CPU worker ceiling. Default ``-1`` (all cores); the
77
+ planner may use fewer.
78
+ random_state (int | None): Random seed for reproducibility.
79
+ progress_bar (bool): Show a progress bar. Default ``False``.
80
+
81
+ Returns:
82
+ BootstrapResult: `estimate` (the statistic on the unresampled full
83
+ sample), `standard_error`, `ci_lower` and `ci_upper` as `BrainData`
84
+ maps of identical shape, plus `samples` as a NumPy array with the
85
+ bootstrap axis first when ``return_samples=True``.
86
+
87
+ Raises:
88
+ ValueError: If `statistic` is unknown, a basic statistic is given `X`,
89
+ `X_test`, or ``device='gpu'``, a Ridge statistic is missing `X` (or
90
+ `X_test` for ``'predict'``), the fitted model is not a `_Ridge`, `X`
91
+ does not match the fitted feature structure and observation count,
92
+ an argument is out of range, or the retained output cannot fit the
93
+ memory budget.
94
+
95
+ Examples:
96
+ ```python
97
+ boot = brain.bootstrap('mean', n_samples=1000)
98
+ boot.estimate.plot()
99
+
100
+ brain.fit(model='ridge', X=features, ridge_alpha=1.0)
101
+ boot = brain.bootstrap('weights', X=features, n_samples=1000)
102
+ ```
103
+
104
+ Note:
105
+ This is an IID row bootstrap: rows must be exchangeable for the
106
+ interval to mean anything. Fitting retains no hidden copy of the
107
+ training features, so omitting `X` raises even when the same features
108
+ were supplied to `fit`.
109
+ """
110
+ from nltools.algorithms.inference.bootstrap import (
111
+ _bootstrap_simple_cpu_parallel,
112
+ _bootstrap_ridge_weights_cpu_parallel,
113
+ _bootstrap_ridge_predict_cpu_parallel,
114
+ _bootstrap_ridge_weights_gpu_batched,
115
+ _bootstrap_ridge_predict_gpu_batched,
116
+ )
117
+
118
+ _validate_statistic(statistic)
119
+ _validate_device(device)
120
+
121
+ if statistic in SIMPLE_STATS:
122
+ _reject_model_arguments(statistic, X, X_test, device)
123
+ result = _bootstrap_simple_cpu_parallel(
124
+ bd.data,
125
+ method=statistic,
126
+ n_samples=n_samples,
127
+ confidence_level=confidence_level,
128
+ memory_budget_gb=memory_budget_gb,
129
+ return_samples=return_samples,
130
+ n_jobs=n_jobs,
131
+ random_state=random_state,
132
+ progress_bar=progress_bar,
133
+ )
134
+ return _as_bootstrap_result(bd, result)
135
+
136
+ model = _fitted_ridge(bd, statistic)
137
+ spaces = _training_feature_spaces(model, X, statistic, bd.shape[0])
138
+ if statistic == "weights":
139
+ if X_test is not None:
140
+ raise ValueError(
141
+ "bootstrap('weights') summarizes training coefficients and "
142
+ "takes no X_test; use statistic='predict' to evaluate on new rows."
143
+ )
144
+ elif X_test is None:
145
+ raise ValueError(
146
+ "X_test parameter required for bootstrap(statistic='predict'). "
147
+ "Provide test features: bootstrap('predict', X=..., X_test=...)"
148
+ )
149
+
150
+ backend = _resolve_device(device)
151
+ alpha = model.alpha_
152
+ feature_space_weights = model.feature_space_weights_
153
+ coefficients = np.asarray(model.coef_, dtype=np.float64)
154
+
155
+ shared = {
156
+ "n_samples": n_samples,
157
+ "confidence_level": confidence_level,
158
+ "feature_space_weights": feature_space_weights,
159
+ "return_samples": return_samples,
160
+ "random_state": random_state,
161
+ "progress_bar": progress_bar,
162
+ }
163
+
164
+ if statistic == "weights":
165
+ if backend is None:
166
+ result = _bootstrap_ridge_weights_cpu_parallel(
167
+ spaces,
168
+ bd.data,
169
+ alpha,
170
+ coefficients,
171
+ memory_budget_gb=memory_budget_gb,
172
+ n_jobs=n_jobs,
173
+ **shared,
174
+ )
175
+ else:
176
+ result = _bootstrap_ridge_weights_gpu_batched(
177
+ spaces,
178
+ bd.data,
179
+ alpha,
180
+ coefficients,
181
+ backend=backend,
182
+ memory_budget_gb=memory_budget_gb,
183
+ **shared,
184
+ )
185
+ return _as_bootstrap_result(bd, result)
186
+
187
+ # `_Ridge` owns the alignment of both feature arguments, through the one
188
+ # seam this facade uses; the engines concatenate in coefficient order.
189
+ test_spaces = model._aligned_feature_spaces(X_test)
190
+ predictions = _stacked(test_spaces) @ coefficients
191
+
192
+ if backend is None:
193
+ result = _bootstrap_ridge_predict_cpu_parallel(
194
+ spaces,
195
+ bd.data,
196
+ test_spaces,
197
+ alpha,
198
+ predictions,
199
+ memory_budget_gb=memory_budget_gb,
200
+ n_jobs=n_jobs,
201
+ **shared,
202
+ )
203
+ else:
204
+ result = _bootstrap_ridge_predict_gpu_batched(
205
+ spaces,
206
+ bd.data,
207
+ test_spaces,
208
+ alpha,
209
+ predictions,
210
+ backend=backend,
211
+ memory_budget_gb=memory_budget_gb,
212
+ **shared,
213
+ )
214
+
215
+ return _as_bootstrap_result(bd, result)
216
+
217
+
218
+ def _validate_statistic(statistic):
219
+ """Reject anything outside the closed set of eight supported statistics.
220
+
221
+ Args:
222
+ statistic (str): The requested statistic.
223
+
224
+ Raises:
225
+ ValueError: If `statistic` is not one of the eight names.
226
+ """
227
+ if statistic in SIMPLE_STATS or statistic in FITTED_STATS:
228
+ return
229
+ raise ValueError(
230
+ f"Unsupported statistic '{statistic}'. "
231
+ f"Supported basic statistics: {list(SIMPLE_STATS)}. "
232
+ f"Supported fitted model statistics: {list(FITTED_STATS)}. "
233
+ f"For fitted statistics, you must call .fit() first."
234
+ )
235
+
236
+
237
+ def _validate_device(device):
238
+ """Reject a device name before the statistic decides what to do with it.
239
+
240
+ The range checks on `n_samples`, `confidence_level` and `memory_budget_gb`
241
+ belong to the engine, which every path reaches.
242
+
243
+ Args:
244
+ device (str): Requested compute device.
245
+
246
+ Raises:
247
+ ValueError: If `device` is not ``'cpu'`` or ``'gpu'``.
248
+ """
249
+ if device not in ("cpu", "gpu"):
250
+ raise ValueError(f"device must be 'cpu' or 'gpu', got {device!r}")
251
+
252
+
253
+ def _reject_model_arguments(statistic, X, X_test, device):
254
+ """Reject the arguments a basic statistic has no use for.
255
+
256
+ Args:
257
+ statistic (str): The requested basic statistic, for the message.
258
+ X (Any): Training features, which must be absent.
259
+ X_test (Any): Evaluation features, which must be absent.
260
+ device (str): Requested compute device, which must be ``'cpu'``.
261
+
262
+ Raises:
263
+ ValueError: If features were supplied, or a GPU was requested.
264
+ """
265
+ if X is not None or X_test is not None:
266
+ raise ValueError(
267
+ f"bootstrap({statistic!r}) reduces the data itself and takes no "
268
+ f"features; X and X_test belong to statistic='weights' or 'predict'."
269
+ )
270
+ if device != "cpu":
271
+ raise ValueError(
272
+ f"bootstrap({statistic!r}) is a NumPy reduction over rows and runs "
273
+ f"on the CPU; device='gpu' applies only to the Ridge statistics."
274
+ )
275
+
276
+
277
+ def _stacked(spaces):
278
+ """Concatenate aligned feature spaces into one matrix in coefficient order.
279
+
280
+ Args:
281
+ spaces (Sequence[np.ndarray]): One matrix per fitted feature space.
282
+
283
+ Returns:
284
+ np.ndarray: A `(n_rows, n_features)` float64 matrix.
285
+ """
286
+ matrices = [np.asarray(space, dtype=np.float64) for space in spaces]
287
+ return matrices[0] if len(matrices) == 1 else np.concatenate(matrices, axis=1)
288
+
289
+
290
+ def _fitted_ridge(bd, statistic):
291
+ """Return the fitted `_Ridge` a model bootstrap needs, or raise.
292
+
293
+ Args:
294
+ bd (BrainData): The object being resampled.
295
+ statistic (str): The requested model statistic, for the error message.
296
+
297
+ Returns:
298
+ _Ridge: The fitted estimator.
299
+
300
+ Raises:
301
+ ValueError: If nothing is fitted, or the fit is not a `_Ridge`.
302
+ """
303
+ from nltools.models import _Ridge
304
+
305
+ model = getattr(bd, "model_", None)
306
+ if model is None or not getattr(model, "is_fitted_", False):
307
+ raise ValueError(
308
+ f"Must call .fit(model='ridge', X=features) before bootstrap('{statistic}')"
309
+ )
310
+ if not isinstance(model, _Ridge):
311
+ raise ValueError(
312
+ f"bootstrap('{statistic}') only supports a fitted Ridge, but this "
313
+ f"BrainData holds a fitted {type(model).__name__}."
314
+ )
315
+ return model
316
+
317
+
318
+ def _training_feature_spaces(model, X, statistic, n_obs):
319
+ """Align the explicit training `X` to the fitted feature-space order.
320
+
321
+ Args:
322
+ model (Ridge): The fitted estimator.
323
+ X (np.ndarray | Mapping | None): Caller-supplied training features.
324
+ statistic (str): The requested model statistic, for the error message.
325
+ n_obs (int): Observation count of the `BrainData` being resampled.
326
+
327
+ Returns:
328
+ list[np.ndarray]: One matrix per fitted feature space, in coefficient
329
+ order.
330
+
331
+ Raises:
332
+ ValueError: If `X` is missing, does not match the fitted feature
333
+ structure, or has a different number of rows than the response.
334
+ """
335
+ if X is None:
336
+ raise ValueError(
337
+ f"bootstrap('{statistic}') requires the training features as X=. "
338
+ f"Fitting keeps no copy of them, so pass the same features you "
339
+ f"passed to fit()."
340
+ )
341
+ spaces = model._aligned_feature_spaces(X)
342
+ rows = spaces[0].shape[0]
343
+ if rows != n_obs:
344
+ raise ValueError(
345
+ f"X has {rows} rows, but the fitted BrainData has {n_obs} "
346
+ f"observations; the training features must be in their original "
347
+ f"row order."
348
+ )
349
+ return spaces
350
+
351
+
352
+ def _resolve_device(device):
353
+ """Resolve the bootstrap `device` request to a GPU backend, or None for CPU.
354
+
355
+ Args:
356
+ device (str): ``'cpu'`` or ``'gpu'``, already checked by
357
+ `_validate_device`.
358
+
359
+ Returns:
360
+ Backend | None: A resolved GPU backend, or None to stay on the CPU.
361
+
362
+ Raises:
363
+ ValueError: If ``'gpu'`` was requested with no accelerator available.
364
+ """
365
+ from nltools.algorithms.backends import check_gpu_available, _resolve_backend
366
+
367
+ if device == "cpu":
368
+ return None
369
+ if not check_gpu_available()[0]:
370
+ raise ValueError(
371
+ "GPU requested via device='gpu' but no CUDA or MPS device is "
372
+ "available. Use device='cpu'."
373
+ )
374
+ return _resolve_backend("gpu")
375
+
376
+
377
+ def _as_bootstrap_result(bd, result):
378
+ """Wrap an engine's arrays as a `BootstrapResult` of `BrainData` maps.
379
+
380
+ Args:
381
+ bd (BrainData): Template whose mask and spatial state the outputs
382
+ inherit; row metadata and fitted state are cleared.
383
+ result (dict): Engine output with `'estimate'`, `'standard_error'`,
384
+ `'ci_lower'`, `'ci_upper'`, and optionally `'samples'`.
385
+
386
+ Returns:
387
+ BootstrapResult: The four summaries as `BrainData`, and the retained
388
+ replicates as a NumPy array when present.
389
+ """
390
+ from nltools.data.results import BootstrapResult
391
+
392
+ return BootstrapResult(
393
+ estimate=_result_from_array(bd, result["estimate"], rows="clear"),
394
+ standard_error=_result_from_array(bd, result["standard_error"], rows="clear"),
395
+ ci_lower=_result_from_array(bd, result["ci_lower"], rows="clear"),
396
+ ci_upper=_result_from_array(bd, result["ci_upper"], rows="clear"),
397
+ samples=result.get("samples"),
398
+ )