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.
- nltools/__init__.py +55 -0
- nltools/algorithms/__init__.py +90 -0
- nltools/algorithms/alignment/__init__.py +21 -0
- nltools/algorithms/alignment/procrustes.py +565 -0
- nltools/algorithms/alignment/srm.py +758 -0
- nltools/algorithms/backends.py +1059 -0
- nltools/algorithms/corrections.py +177 -0
- nltools/algorithms/decoding.py +327 -0
- nltools/algorithms/inference/__init__.py +50 -0
- nltools/algorithms/inference/bootstrap.py +1386 -0
- nltools/algorithms/inference/correlation.py +373 -0
- nltools/algorithms/inference/intersubject.py +422 -0
- nltools/algorithms/inference/isc.py +1554 -0
- nltools/algorithms/inference/matrix.py +602 -0
- nltools/algorithms/inference/one_sample.py +288 -0
- nltools/algorithms/inference/random.py +122 -0
- nltools/algorithms/inference/timeseries.py +347 -0
- nltools/algorithms/inference/two_sample.py +212 -0
- nltools/algorithms/inference/utils.py +58 -0
- nltools/algorithms/inference/validation.py +282 -0
- nltools/algorithms/neighborhoods.py +207 -0
- nltools/algorithms/outliers.py +308 -0
- nltools/algorithms/regression.py +83 -0
- nltools/algorithms/signal.py +303 -0
- nltools/algorithms/similarity.py +234 -0
- nltools/algorithms/validation.py +151 -0
- nltools/cross_validation.py +72 -0
- nltools/data/__init__.py +30 -0
- nltools/data/adjacency/__init__.py +875 -0
- nltools/data/adjacency/io.py +111 -0
- nltools/data/adjacency/modeling.py +569 -0
- nltools/data/adjacency/plotting.py +174 -0
- nltools/data/adjacency/state.py +349 -0
- nltools/data/adjacency/stats.py +596 -0
- nltools/data/adjacency/utils.py +79 -0
- nltools/data/atlases/__init__.py +23 -0
- nltools/data/atlases/labeling.py +158 -0
- nltools/data/atlases/loading.py +76 -0
- nltools/data/atlases/registry.py +96 -0
- nltools/data/atlases/reporting.py +456 -0
- nltools/data/braindata/__init__.py +2170 -0
- nltools/data/braindata/analysis.py +1381 -0
- nltools/data/braindata/bootstrap.py +398 -0
- nltools/data/braindata/io.py +896 -0
- nltools/data/braindata/modeling.py +594 -0
- nltools/data/braindata/plotting.py +501 -0
- nltools/data/braindata/prediction.py +1250 -0
- nltools/data/braindata/utils.py +348 -0
- nltools/data/braindata/validation.py +197 -0
- nltools/data/braindata/viewer.js +266 -0
- nltools/data/braindata/viewer.py +770 -0
- nltools/data/combine.py +27 -0
- nltools/data/designmatrix/__init__.py +1032 -0
- nltools/data/designmatrix/append.py +518 -0
- nltools/data/designmatrix/diagnostics.py +248 -0
- nltools/data/designmatrix/io.py +356 -0
- nltools/data/designmatrix/plotting.py +291 -0
- nltools/data/designmatrix/regressors.py +463 -0
- nltools/data/designmatrix/transforms.py +200 -0
- nltools/data/designmatrix/utils.py +350 -0
- nltools/data/ownership.py +129 -0
- nltools/data/results.py +291 -0
- nltools/data/roc/__init__.py +398 -0
- nltools/data/simulator/__init__.py +927 -0
- nltools/data/simulator/haxby.py +124 -0
- nltools/data/validation.py +83 -0
- nltools/datasets.py +218 -0
- nltools/io/__init__.py +10 -0
- nltools/io/events.py +67 -0
- nltools/io/h5.py +246 -0
- nltools/mask.py +403 -0
- nltools/models/__init__.py +11 -0
- nltools/models/glm.py +543 -0
- nltools/models/results.py +49 -0
- nltools/models/ridge.py +1303 -0
- nltools/models/validation.py +26 -0
- nltools/plotting/__init__.py +32 -0
- nltools/plotting/adjacency.py +421 -0
- nltools/plotting/brain.py +669 -0
- nltools/plotting/decomposition.py +111 -0
- nltools/plotting/prediction.py +110 -0
- nltools/resources/covariates_example.csv +161 -0
- nltools/resources/onsets_example.csv +40 -0
- nltools/templates/__init__.py +51 -0
- nltools/templates/config.py +144 -0
- nltools/templates/fetch.py +260 -0
- nltools/templates/matching.py +183 -0
- nltools/templates/paths.py +106 -0
- nltools/templates/registry.py +25 -0
- nltools/utils.py +230 -0
- nltools/version.py +13 -0
- nltools-0.6.0.dev0.dist-info/METADATA +95 -0
- nltools-0.6.0.dev0.dist-info/RECORD +95 -0
- nltools-0.6.0.dev0.dist-info/WHEEL +4 -0
- 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
|