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,288 @@
1
+ """One-sample permutation test (sign flipping).
2
+
3
+ Tests whether a mean differs from zero by randomly flipping the sign of each
4
+ observation — the permutation analogue of a one-sample t-test. Permutations run
5
+ on joblib workers; `n_jobs` sets how many, and a given `random_state` gives the
6
+ same result at any worker count.
7
+ """
8
+
9
+ import numpy as np
10
+ from scipy.stats import ttest_1samp
11
+
12
+ from .utils import (
13
+ _generate_sign_flips,
14
+ _signed_z_from_p,
15
+ _maybe_tqdm,
16
+ )
17
+ from .validation import _validate_array_shape_range
18
+ from ..validation import _compute_pvalue, _validate_tail_parameter
19
+
20
+
21
+ def _one_sample_permutation_cpu_parallel(
22
+ data: np.ndarray,
23
+ *,
24
+ n_permute: int,
25
+ tail: int,
26
+ return_null: bool,
27
+ n_jobs: int,
28
+ random_state: int | None,
29
+ single_feature: bool = False,
30
+ progress_bar: bool = False,
31
+ ) -> dict:
32
+ """One-sample permutation test parallelized across CPU cores with joblib.
33
+
34
+ Pre-generates every sign flip deterministically (`n_permute × n_samples`
35
+ bytes — negligible) and parallelizes only the computation, so p-values are
36
+ reproducible regardless of worker count. Typical speedup is 4-8× on an
37
+ 8-core machine.
38
+
39
+ Args:
40
+ data (np.ndarray): Data to test, shape `(n_samples, n_features)`.
41
+ n_permute (int): Number of permutations.
42
+ tail (int | str): `2` or `'two'` for two-tailed; `1` or `'one'` for
43
+ one-tailed.
44
+ return_null (bool): Whether to return the null distribution.
45
+ n_jobs (int): Number of parallel jobs (-1 = all cores).
46
+ random_state (int | None): Random seed for reproducibility.
47
+ single_feature (bool): Whether the caller passed 1D data (results are
48
+ returned as scalars).
49
+ progress_bar (bool): Whether to display a tqdm progress bar.
50
+
51
+ Returns:
52
+ dict: Same format as `one_sample_permutation_test`.
53
+ """
54
+ from joblib import Parallel, delayed
55
+
56
+ # Get dimensions (data is already reshaped by caller)
57
+ n_samples, n_features = data.shape
58
+
59
+ # Compute observed statistic
60
+ obs_stat = np.nanmean(data, axis=0)
61
+
62
+ # Pre-generate ALL sign-flips (matches stats.py pattern exactly)
63
+ sign_flips = _generate_sign_flips(n_permute, n_samples, random_state=random_state)
64
+
65
+ # Define worker function (each processes ONE permutation with pre-computed signs)
66
+ def _compute_one_perm(signs):
67
+ """Compute statistic for one sign-flip permutation (signs pre-computed)."""
68
+ perm_data = data * signs[:, np.newaxis]
69
+ return np.nanmean(perm_data, axis=0)
70
+
71
+ # Execute in parallel with progress bar
72
+ null_dist = Parallel(n_jobs=n_jobs)(
73
+ delayed(_compute_one_perm)(sign_flips[i])
74
+ for i in _maybe_tqdm(
75
+ range(n_permute),
76
+ progress_bar=progress_bar,
77
+ desc="CPU parallel perms",
78
+ unit="perm",
79
+ )
80
+ )
81
+ null_dist = np.array(null_dist) # Shape: (n_permute, n_features)
82
+
83
+ # Compute p-values
84
+ p_values = _compute_pvalue(obs_stat, null_dist, tail=tail)
85
+
86
+ # Return to original shape
87
+ if single_feature:
88
+ obs_stat = float(obs_stat[0])
89
+ p_values = float(p_values[0])
90
+
91
+ # Build result
92
+ result = {
93
+ "mean": obs_stat,
94
+ "p": p_values,
95
+ }
96
+
97
+ if return_null:
98
+ if single_feature:
99
+ null_dist = null_dist.squeeze()
100
+ result["null_dist"] = null_dist
101
+
102
+ return result
103
+
104
+
105
+ def one_sample_permutation_test(
106
+ data: np.ndarray,
107
+ *,
108
+ n_permute: int = 5000,
109
+ tail: int | str = 2,
110
+ return_null: bool = False,
111
+ n_jobs: int = -1,
112
+ random_state: int | None = None,
113
+ progress_bar: bool = False,
114
+ ) -> dict:
115
+ """One-sample permutation test using sign flipping.
116
+
117
+ Tests whether the mean of `data` differs from zero by randomly flipping the
118
+ sign of each observation — the permutation analogue of a one-sample t-test.
119
+ Multi-feature (voxel-wise) data tests each column independently against
120
+ the same permutations.
121
+
122
+ Assumes errors are distributed symmetrically around zero. For strongly
123
+ skewed data, prefer bootstrap resampling. NaN observations are dropped
124
+ from the observed and every permuted mean (`np.nanmean`), feature by
125
+ feature.
126
+
127
+ Args:
128
+ data (np.ndarray): Data to test, shape `(n_samples,)` for a single
129
+ feature or `(n_samples, n_features)` for voxel-wise data. May
130
+ contain NaN observations.
131
+ n_permute (int): Number of permutations. Defaults to 5000.
132
+ tail (int | str): `2` or `'two'` (default) for a two-tailed test
133
+ (mean != 0); `1` or `'one'` for a one-tailed test of mean > 0
134
+ (negate the data for the other direction — the fixed direction
135
+ keeps multiple-comparison correction valid).
136
+ return_null (bool): If True, include the full null distribution in the
137
+ result. Defaults to False.
138
+ n_jobs (int): Number of joblib workers. Defaults to -1 (all cores).
139
+ Results are identical at every worker count.
140
+ random_state (int | None): Random seed for reproducibility.
141
+ progress_bar (bool): Whether to display a progress bar. Defaults to False.
142
+
143
+ Returns:
144
+ dict: Keys `'mean'` (float or np.ndarray, observed mean(s)), `'p'`
145
+ (float or np.ndarray, p-value(s)), and — when `return_null=True` —
146
+ `'null_dist'` (np.ndarray, shape `(n_permute,)` or
147
+ `(n_permute, n_features)`).
148
+
149
+ Examples:
150
+ ```python
151
+ # Single feature
152
+ data = np.random.randn(30)
153
+ result = one_sample_permutation_test(data, n_permute=5000)
154
+ result["p"] # → 0.23
155
+
156
+ # Voxel-wise test
157
+ data = np.random.randn(30, 10000) # 30 subjects, 10K voxels
158
+ result = one_sample_permutation_test(data, n_permute=5000)
159
+ result["mean"].shape # → (10000,)
160
+ result["p"].shape # → (10000,)
161
+ ```
162
+ """
163
+ # Input validation
164
+ data = np.asarray(data, dtype=np.float64)
165
+ _validate_array_shape_range(data, 1, 2, name="data")
166
+ _validate_tail_parameter(tail)
167
+
168
+ # Handle shape
169
+ single_feature = data.ndim == 1
170
+ if single_feature:
171
+ data = data[:, np.newaxis] # (n_samples, 1)
172
+
173
+ return _one_sample_permutation_cpu_parallel(
174
+ data,
175
+ n_permute=n_permute,
176
+ tail=tail,
177
+ return_null=return_null,
178
+ n_jobs=n_jobs,
179
+ random_state=random_state,
180
+ single_feature=single_feature,
181
+ progress_bar=progress_bar,
182
+ )
183
+
184
+
185
+ def _one_sample_statistics(
186
+ data: np.ndarray,
187
+ *,
188
+ popmean: float = 0.0,
189
+ permutation: bool = False,
190
+ n_permute: int = 5000,
191
+ tail: int | str = 2,
192
+ return_null: bool = False,
193
+ n_jobs: int = -1,
194
+ random_state: int | None = None,
195
+ progress_bar: bool = False,
196
+ ) -> dict:
197
+ """Compute the shared one-sample t-test statistics for a 2-D feature matrix.
198
+
199
+ The single implementation behind `BrainData.ttest` and `Adjacency.ttest`
200
+ (see `development/specs/ttest.md`). It works on plain arrays and
201
+ returns plain arrays; each facade wraps them in its own result type.
202
+
203
+ `t` is always the observed SciPy statistic against `popmean`, on both the
204
+ parametric and the permutation path. Only `p` changes. The parametric path
205
+ uses SciPy's p-value for the requested tail; the permutation path uses the
206
+ empirical sign-flip p-value from `data - popmean`, computed once for the
207
+ whole matrix by `one_sample_permutation_test`. The permutation null
208
+ therefore holds centered *means*, not t-statistics.
209
+
210
+ Args:
211
+ data (np.ndarray): Observations to test, shape `(n_obs, n_features)`.
212
+ popmean (float): Population mean to test against. Defaults to 0.0.
213
+ permutation (bool): If True, take p from a sign-flip permutation test
214
+ instead of the parametric test. Defaults to False.
215
+ n_permute (int): Number of permutations, used only when
216
+ `permutation=True`. Defaults to 5000.
217
+ tail (int | str): `2` or `'two'` (default) for a two-tailed test;
218
+ `1` or `'one'` for a one-tailed test of mean > `popmean`.
219
+ return_null (bool): If True, also return the permutation null. Has no
220
+ effect on the parametric path, which computes no null. Defaults to
221
+ False.
222
+ n_jobs (int): CPU cores for the permutation engine. Defaults to -1.
223
+ random_state (int | None): Random seed for reproducibility.
224
+ progress_bar (bool): Whether to display a progress bar. Defaults to False.
225
+
226
+ Returns:
227
+ dict: `'mean'` (sample mean minus `popmean`), `'t'`, `'z'`, and `'p'`,
228
+ each an `(n_features,)` array. With `permutation=True` and
229
+ `return_null=True` the dict also holds `'null_dist'`, an
230
+ `(n_permute, n_features)` array of centered means whose feature
231
+ axis is never squeezed. No returned array aliases `data` or any
232
+ other returned array.
233
+
234
+ Raises:
235
+ ValueError: If `data` is not 2-D or holds fewer than two observations.
236
+ """
237
+ values = np.asarray(data, dtype=np.float64)
238
+ if values.ndim != 2:
239
+ raise ValueError(
240
+ f"data must be 2-D with shape (n_obs, n_features); got {values.shape}."
241
+ )
242
+ if values.shape[0] < 2:
243
+ raise ValueError(
244
+ "A one-sample t-test requires at least two observations to estimate "
245
+ "the variance."
246
+ )
247
+
248
+ tail_internal = _validate_tail_parameter(tail)
249
+ # 'one' is the test's positive direction: mean > popmean.
250
+ alternative = "two-sided" if tail_internal == "two" else "greater"
251
+ t_values, p_parametric = ttest_1samp(
252
+ values, popmean, axis=0, alternative=alternative
253
+ )
254
+ t_values = np.asarray(t_values, dtype=np.float64)
255
+ mean_values = values.mean(axis=0) - popmean
256
+ null_dist = None
257
+
258
+ if permutation:
259
+ # Sign flipping tests symmetry around zero, so the engine must see the
260
+ # popmean-referenced data — flipping raw values would silently test
261
+ # mean != 0 instead of mean != popmean.
262
+ engine = one_sample_permutation_test(
263
+ values - popmean,
264
+ n_permute=n_permute,
265
+ tail=tail,
266
+ return_null=return_null,
267
+ n_jobs=n_jobs,
268
+ random_state=random_state,
269
+ progress_bar=progress_bar,
270
+ )
271
+ p_values = np.asarray(engine["p"], dtype=np.float64)
272
+ # The engine's mean of the centered data IS mean(data) - popmean; keep
273
+ # it so the reported effect and p come from the same numbers.
274
+ mean_values = np.asarray(engine["mean"], dtype=np.float64)
275
+ if return_null:
276
+ null_dist = np.asarray(engine["null_dist"], dtype=np.float64)
277
+ else:
278
+ p_values = np.asarray(p_parametric, dtype=np.float64)
279
+
280
+ results = {
281
+ "mean": mean_values,
282
+ "t": t_values,
283
+ "z": _signed_z_from_p(t_values, p_values, tail_internal),
284
+ "p": p_values,
285
+ }
286
+ if null_dist is not None:
287
+ results["null_dist"] = null_dist
288
+ return results
@@ -0,0 +1,122 @@
1
+ """Random-state utilities for deterministic parallel resampling.
2
+
3
+ The bootstrap and permutation tests pre-generate one seed per iteration from a
4
+ single `random_state`, then give each parallel worker its own `RandomState`
5
+ built from its seed. Results are therefore identical for any `n_jobs` and
6
+ across CPU and GPU execution.
7
+
8
+ Examples:
9
+ ```python
10
+ from nltools.algorithms.inference.random import _generate_seeds
11
+
12
+ seeds = _generate_seeds(100, random_state=42)
13
+ # Hand one seed to each parallel worker for deterministic results
14
+ ```
15
+ """
16
+
17
+ import numpy as np
18
+ from sklearn.utils import check_random_state
19
+
20
+
21
+ def _generate_seeds(n_permute: int, random_state: int | None = None) -> np.ndarray:
22
+ """Generate one random seed per permutation or bootstrap iteration.
23
+
24
+ Args:
25
+ n_permute (int): Number of iterations to seed.
26
+ random_state (int | None): Seed for the seed generator. Defaults to None.
27
+
28
+ Returns:
29
+ np.ndarray: Integer seeds, shape (n_permute,).
30
+
31
+ Examples:
32
+ ```python
33
+ seeds = _generate_seeds(100, random_state=42)
34
+ seeds.shape # (100,)
35
+ isinstance(seeds[0], (int, np.integer)) # True
36
+ ```
37
+ """
38
+ rng = check_random_state(random_state)
39
+ MAX_INT = 2**31 - 1
40
+ seeds = rng.randint(MAX_INT, size=n_permute)
41
+ return seeds
42
+
43
+
44
+ def _generate_sign_flips(
45
+ n_permute: int,
46
+ n_samples: int,
47
+ random_state: int | None = None,
48
+ ) -> np.ndarray:
49
+ """Generate the random sign-flip matrix for one-sample permutation tests.
50
+
51
+ Each row is one permutation: every sample is multiplied by +1 or -1 to build
52
+ the null distribution. Each permutation draws from an independent
53
+ `RandomState` seeded by `_generate_seeds`, so the matrix is reproducible for
54
+ any degree of parallelism.
55
+
56
+ Args:
57
+ n_permute (int): Number of permutations.
58
+ n_samples (int): Number of samples in the dataset.
59
+ random_state (int | None): Seed for reproducibility. Defaults to None.
60
+
61
+ Returns:
62
+ np.ndarray: Matrix of +1 and -1 values, shape (n_permute, n_samples). A
63
+ NumPy array; callers move it to the GPU when needed.
64
+
65
+ Examples:
66
+ ```python
67
+ sign_flips = _generate_sign_flips(n_permute=100, n_samples=30, random_state=42)
68
+ sign_flips.shape # → (100, 30)
69
+ np.all(np.isin(sign_flips, [-1, 1])) # → True
70
+ ```
71
+ """
72
+ seeds = _generate_seeds(n_permute, random_state=random_state)
73
+
74
+ # Generate sign-flips using independent RNG per permutation
75
+ # This matches stats._permute_sign behavior exactly
76
+ sign_flips = np.array(
77
+ [
78
+ np.random.RandomState(seeds[i]).choice([1, -1], n_samples)
79
+ for i in range(n_permute)
80
+ ]
81
+ )
82
+
83
+ return sign_flips
84
+
85
+
86
+ def _generate_bootstrap_indices(
87
+ n_samples: int,
88
+ n_bootstrap: int,
89
+ random_state: int | None = None,
90
+ ) -> np.ndarray:
91
+ """Generate bootstrap resampling indices deterministically.
92
+
93
+ Each bootstrap draw uses an independent `RandomState` seeded by
94
+ `_generate_seeds`, the same scheme as the permutation tests.
95
+
96
+ Args:
97
+ n_samples (int): Number of samples in the original dataset.
98
+ n_bootstrap (int): Number of bootstrap iterations.
99
+ random_state (int | None): Seed for reproducibility. Defaults to None.
100
+
101
+ Returns:
102
+ np.ndarray: Indices sampled with replacement from `[0, n_samples)`,
103
+ shape (n_bootstrap, n_samples); repeats within a row are expected.
104
+
105
+ Examples:
106
+ ```python
107
+ indices = _generate_bootstrap_indices(100, 1000, random_state=42)
108
+ indices.shape # → (1000, 100)
109
+ indices[0] # → array([23, 45, 23, 67, ...]) one bootstrap sample
110
+ ```
111
+ """
112
+ seeds = _generate_seeds(n_bootstrap, random_state=random_state)
113
+
114
+ # Each bootstrap gets independent RandomState
115
+ indices = np.array(
116
+ [
117
+ np.random.RandomState(seeds[i]).choice(n_samples, n_samples, replace=True)
118
+ for i in range(n_bootstrap)
119
+ ]
120
+ )
121
+
122
+ return indices