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,234 @@
1
+ """Similarity metrics and correlation."""
2
+
3
+ import itertools
4
+
5
+ import numpy as np
6
+ from scipy.spatial.distance import cdist
7
+ from scipy.stats import rankdata
8
+
9
+ from .regression import regress
10
+
11
+
12
+ def fisher_r_to_z(r):
13
+ """Convert correlation coefficients to Fisher z values.
14
+
15
+ Args:
16
+ r (float | np.ndarray): Correlation coefficient(s).
17
+
18
+ Returns:
19
+ np.ndarray: Fisher z-transformed correlation(s).
20
+ """
21
+ with np.errstate(invalid="ignore"):
22
+ return np.arctanh(r)
23
+
24
+
25
+ def fisher_z_to_r(z):
26
+ """Convert Fisher z back to a correlation coefficient.
27
+
28
+ Args:
29
+ z (float | np.ndarray): Fisher z value(s).
30
+
31
+ Returns:
32
+ np.ndarray: Correlation coefficient(s).
33
+ """
34
+ return np.tanh(z)
35
+
36
+
37
+ # Adapted from the scikit-learn RankSVM example by Fabian Pedregosa and
38
+ # Alexandre Gramfort (BSD licensed).
39
+ def transform_pairwise(X, y):
40
+ """Transform data into pairwise differences with balanced labels for ranking.
41
+
42
+ Turns an n-class ranking problem into a two-class classification problem:
43
+ every pair of samples with different target values becomes one difference
44
+ row, and signs are flipped so that the -1 and +1 classes are balanced.
45
+
46
+ Reference: Herbrich, R., Graepel, T., & Obermayer, K. "Large Margin Rank
47
+ Boundaries for Ordinal Regression".
48
+
49
+ Args:
50
+ X (np.ndarray): Data, shape (n_samples, n_features).
51
+ y (np.ndarray): Target labels, shape (n_samples,) or (n_samples, 2). A
52
+ second column groups the samples; pairs from different groups are skipped.
53
+
54
+ Returns:
55
+ tuple[np.ndarray, np.ndarray]: `(X_trans, y_trans)`. `X_trans` has shape
56
+ (k, n_features) with one row per retained pair (k is at most
57
+ n_samples * (n_samples - 1) / 2; pairs are formed within groups when
58
+ given). `y_trans` holds the labels in {-1, +1}, shape (k,), or (k, 2)
59
+ with the group in the second column when `y` had two columns.
60
+ """
61
+ X = np.asarray(X)
62
+ y = np.asarray(y)
63
+ y_ndim = y.ndim
64
+ if y_ndim == 1:
65
+ y = np.c_[y, np.ones(y.shape[0])]
66
+
67
+ # Pre-allocate lists (more efficient than repeated appends)
68
+ X_new = []
69
+ y_new = []
70
+ y_group = []
71
+
72
+ # Use itertools.combinations (necessary for pairwise combinations)
73
+ # Optimize by using pre-allocated lists and vectorized sign operation
74
+ comb = itertools.combinations(range(X.shape[0]), 2)
75
+ for k, (i, j) in enumerate(comb):
76
+ if y[i, 0] == y[j, 0] or y[i, 1] != y[j, 1]:
77
+ # skip if same target or different group
78
+ continue
79
+ X_new.append(X[i] - X[j])
80
+ sign_val = np.sign(y[i, 0] - y[j, 0])
81
+ # output balanced classes
82
+ if sign_val != (-1) ** k:
83
+ sign_val = -sign_val
84
+ X_new[-1] = -X_new[-1]
85
+ y_new.append(sign_val)
86
+ y_group.append(y[i, 1])
87
+
88
+ # Convert to arrays efficiently
89
+ if len(X_new) == 0:
90
+ if y_ndim == 1:
91
+ return np.array([]).reshape(0, X.shape[1]), np.array([])
92
+ return np.array([]).reshape(0, X.shape[1]), np.array([]).reshape(0, 2)
93
+
94
+ X_trans = np.array(X_new)
95
+ if y_ndim == 1:
96
+ return X_trans, np.array(y_new)
97
+ return X_trans, np.column_stack([np.array(y_new), np.array(y_group)])
98
+
99
+
100
+ def compute_similarity(data1, data2, metric="correlation"):
101
+ """Compute row-wise similarity between two data arrays.
102
+
103
+ The array engine behind `BrainData.similarity`.
104
+
105
+ Args:
106
+ data1 (np.ndarray): First data array, shape (n_samples1, n_features).
107
+ data2 (np.ndarray): Second data array, shape (n_samples2, n_features).
108
+ metric (str): 'correlation' (or 'pearson'), 'spearman' (or
109
+ 'rank_correlation'), 'dot_product', or 'cosine'. Defaults to 'correlation'.
110
+
111
+ Returns:
112
+ np.ndarray: Similarities of shape (n_samples1, n_samples2), squeezed: a 1D
113
+ array when either input has a single row, a scalar when both do.
114
+
115
+ Examples:
116
+ ```python
117
+ data1 = np.random.randn(10, 100)
118
+ data2 = np.random.randn(5, 100)
119
+ sim = compute_similarity(data1, data2, metric="correlation")
120
+ sim.shape # → (10, 5)
121
+ ```
122
+ """
123
+ # Ensure 2D arrays
124
+ data1 = np.atleast_2d(data1)
125
+ data2 = np.atleast_2d(data2)
126
+
127
+ if metric == "dot_product":
128
+ # Vectorized dot product
129
+ if data2.shape[0] == 1:
130
+ out = np.dot(data1, data2.T).squeeze()
131
+ else:
132
+ out = np.dot(data1, data2.T)
133
+ elif metric in ["pearson", "correlation"]:
134
+ # Use np.corrcoef (BLAS-optimized) for Pearson correlation
135
+ stacked = np.vstack([data1, data2])
136
+ corr_matrix = np.corrcoef(stacked)
137
+ n_data1 = data1.shape[0]
138
+ n_data2 = data2.shape[0]
139
+ # Extract correlations between data1 rows and data2 rows
140
+ out = corr_matrix[:n_data1, n_data1 : n_data1 + n_data2]
141
+ out = out.squeeze()
142
+ elif metric in ["spearman", "rank_correlation"]:
143
+ # Spearman correlation: rank-transform then use np.corrcoef
144
+ data1_ranked = np.apply_along_axis(rankdata, axis=1, arr=data1)
145
+ data2_ranked = np.apply_along_axis(rankdata, axis=1, arr=data2)
146
+ stacked = np.vstack([data1_ranked, data2_ranked])
147
+ corr_matrix = np.corrcoef(stacked)
148
+ n_data1 = data1.shape[0]
149
+ n_data2 = data2.shape[0]
150
+ out = corr_matrix[:n_data1, n_data1 : n_data1 + n_data2]
151
+ out = out.squeeze()
152
+ elif metric == "cosine":
153
+ # Use cdist with cosine metric, then convert distance to similarity
154
+ out = cdist(data1, data2, metric="cosine").squeeze()
155
+ out = 1 - out # Convert distance to similarity
156
+ else:
157
+ raise ValueError(
158
+ f"metric must be one of ['correlation', 'pearson', 'spearman', "
159
+ f"'rank_correlation', 'dot_product', 'cosine'], got '{metric}'"
160
+ )
161
+
162
+ return out
163
+
164
+
165
+ def _compute_multivariate_similarity(y, X, tail=2):
166
+ """Compute multivariate similarity by regressing one pattern on several.
167
+
168
+ The array engine behind `BrainData.multivariate_similarity`: predicts the
169
+ spatial pattern `y` from a linear combination of the columns of `X` and
170
+ returns the OLS coefficients, t-statistics, p-values, and residuals.
171
+
172
+ Args:
173
+ y (np.ndarray): Target pattern, shape (n_features,).
174
+ X (np.ndarray): Predictor patterns, shape (n_features, n_predictors) (the
175
+ transpose is accepted). An intercept column is always prepended, so
176
+ do not include one.
177
+ tail (int): 2 for two-sided p-values, 1 for an upper-tail test. Defaults to 2.
178
+
179
+ Returns:
180
+ dict: Keys 'beta' (coefficients, intercept first, shape (n_predictors + 1,)),
181
+ 't' (t-statistics, same shape), 'p' (p-values, same shape), 'df'
182
+ (residual degrees of freedom), 'sigma' (residual standard deviation),
183
+ and 'residual' (residuals, shape (n_features,)).
184
+
185
+ Examples:
186
+ ```python
187
+ y = np.random.randn(100)
188
+ X = np.random.randn(100, 5)
189
+ result = _compute_multivariate_similarity(y, X)
190
+ result["beta"].shape # → (6,) 5 predictors + intercept
191
+ ```
192
+ """
193
+ # Ensure y is 1D
194
+ y = np.atleast_1d(y)
195
+ if y.ndim > 1:
196
+ raise ValueError(f"y must be 1D, got shape {y.shape}")
197
+
198
+ # Ensure X is 2D: (n_features, n_predictors)
199
+ X = np.atleast_2d(X)
200
+ if X.ndim != 2:
201
+ raise ValueError(f"X must be 2D, got shape {X.shape}")
202
+
203
+ # Check if X needs to be transposed (handle both orientations)
204
+ if X.shape[0] == y.shape[0]:
205
+ # X is (n_features, n_predictors) - correct orientation
206
+ pass
207
+ elif X.shape[1] == y.shape[0]:
208
+ # X is (n_predictors, n_features) - transpose needed
209
+ X = X.T
210
+ else:
211
+ raise ValueError(
212
+ f"X must have shape (n_features, n_predictors) or (n_predictors, n_features), "
213
+ f"where n_features={y.shape[0]}, got shape {X.shape}"
214
+ )
215
+
216
+ # Add intercept (first column)
217
+ X_with_intercept = np.hstack([np.ones((X.shape[0], 1)), X])
218
+
219
+ b, _, t_out, p, _, res = regress(X_with_intercept, y, tail=tail)
220
+
221
+ n, p_cols = X_with_intercept.shape
222
+ df = n - p_cols
223
+ # Unbiased estimator of residual standard error: sqrt(RSS / df); correct for
224
+ # both intercept and intercept-free models. See GH #287.
225
+ sigma = float(np.sqrt(np.dot(res, res) / df))
226
+
227
+ return {
228
+ "beta": b,
229
+ "t": t_out,
230
+ "p": p,
231
+ "df": df,
232
+ "sigma": sigma,
233
+ "residual": res,
234
+ }
@@ -0,0 +1,151 @@
1
+ """The argument vocabulary every algorithms family shares.
2
+
3
+ `tail` is spoken by inference, alignment and regression alike, and by the two
4
+ facade methods that expose a permutation test (`Roc.summary`,
5
+ `Adjacency.ttest`), so its validator and the p-value computation built on it
6
+ live here rather than inside any one family.
7
+
8
+ Examples:
9
+ ```python
10
+ from nltools.algorithms.validation import _validate_tail_parameter
11
+
12
+ _validate_tail_parameter(2) # → 'two'
13
+ _validate_tail_parameter("invalid") # raises ValueError
14
+ ```
15
+ """
16
+
17
+ import numpy as np
18
+
19
+
20
+ def _validate_tail_parameter(tail: int | str) -> str:
21
+ """Validate the public tail vocabulary and normalize to the internal form.
22
+
23
+ The public vocabulary is deliberately two-valued: the *direction* of a
24
+ one-tailed test is fixed by the test's convention, never chosen from the
25
+ data (a data-driven direction would silently halve every p-value). A fixed
26
+ direction across all tests is what keeps multiple-comparison correction
27
+ (FDR, Bonferroni) valid (GH #315).
28
+
29
+ Args:
30
+ tail (int | str): `2` or `'two'` (the default everywhere) for a
31
+ two-tailed test (`|obs|` vs `|null|`); `1` or `'one'` for a
32
+ one-tailed test in the test's canonical positive direction
33
+ (correlation/ISC/similarity > 0, mean > popmean, group1 > group2).
34
+ To test the negative direction, negate your data, swap the groups,
35
+ or flip the contrast.
36
+
37
+ Returns:
38
+ str: Normalized internal tail, `'two'` or `'upper'`.
39
+
40
+ Raises:
41
+ ValueError: If `tail` is not a valid option (including the removed
42
+ `'upper'`/`'lower'`/`-1` forms).
43
+ """
44
+ # One mapping table lives in `_normalize_tail_internal`; the public layer
45
+ # only rejects the internal-only directional forms it must not accept.
46
+ if tail not in (-1, "upper", "lower"):
47
+ try:
48
+ return _normalize_tail_internal(tail)
49
+ except ValueError:
50
+ pass
51
+ raise ValueError(
52
+ f"tail must be 2|'two' (two-tailed) or 1|'one' (one-tailed, the test's "
53
+ f"positive direction), got {tail!r}. The 'upper'/'lower'/-1 forms were "
54
+ "removed in v0.6.0: to test the negative direction, negate your data, "
55
+ "swap the groups, or flip the contrast."
56
+ )
57
+
58
+
59
+ def _normalize_tail_internal(tail: int | str) -> str:
60
+ """Normalize a tail value to the internal 'two'/'upper'/'lower' form.
61
+
62
+ Accepts BOTH the public v0.6.0 vocabulary (2 or 'two', 1 or 'one') and the
63
+ internal directional forms ('upper', 'lower', -1) that forced-tail call
64
+ sites use directly. Public entry points must validate with the strict
65
+ `_validate_tail_parameter` first — this permissive form exists only so
66
+ `_compute_pvalue` can serve both layers.
67
+ """
68
+ if tail == 2 or tail == "two":
69
+ return "two"
70
+ if tail == 1 or tail == "one" or tail == "upper":
71
+ return "upper"
72
+ if tail == -1 or tail == "lower":
73
+ return "lower"
74
+ raise ValueError(
75
+ f"tail must be 2|'two', 1|'one' (or internal 'upper'/'lower'/-1), got {tail!r}"
76
+ )
77
+
78
+
79
+ def _compute_pvalue(
80
+ obs_stat: np.ndarray,
81
+ null_dist: np.ndarray,
82
+ tail: int | str = 2,
83
+ ) -> np.ndarray:
84
+ """Calculate p-values from observed statistic and null distribution.
85
+
86
+ Computes the proportion of null-distribution values at least as extreme as
87
+ the observed statistic, with the `(count + 1) / (n_permute + 1)` correction:
88
+ the observed value counts as one draw, so p is never 0 and its minimum is
89
+ `1 / (n_permute + 1)`. Two-tailed tests compare absolute values. The fixed
90
+ per-tail direction keeps multiple-comparison correction (FDR, Bonferroni)
91
+ valid across tests (GH #315).
92
+
93
+ Args:
94
+ obs_stat (np.ndarray): Observed statistic(s), shape `()` for a scalar or
95
+ `(n_features,)` for multi-feature.
96
+ null_dist (np.ndarray): Null distribution from permutations, shape
97
+ `(n_permute,)` for a single feature or `(n_permute, n_features)`
98
+ for multi-feature.
99
+ tail (int | str): `2` or `'two'` for a two-tailed test (`|obs|` vs
100
+ `|null|`); `1`, `'one'`, or the internal `'upper'` for a one-tailed
101
+ upper test (`null >= obs`, positive effects); the internal `'lower'`
102
+ or `-1` for a one-tailed lower test (`null <= obs`), reserved for
103
+ forced-tail call sites and not part of the public vocabulary.
104
+
105
+ Returns:
106
+ np.ndarray: P-value(s) with the same shape as `obs_stat`.
107
+
108
+ Examples:
109
+ ```python
110
+ obs_stat = np.array([2.5])
111
+ null_dist = np.random.randn(1000, 1)
112
+ p = _compute_pvalue(obs_stat, null_dist, tail="two")
113
+ 0 < p <= 1 # → True
114
+
115
+ # Explicit directions for forced-tail call sites
116
+ p_upper = _compute_pvalue(obs_stat, null_dist, tail="upper")
117
+ p_lower = _compute_pvalue(obs_stat, null_dist, tail="lower")
118
+ ```
119
+ """
120
+ tail_normalized = _normalize_tail_internal(tail)
121
+
122
+ # Ensure inputs are numpy arrays (handles Python float/int scalars)
123
+ obs_stat = np.asarray(obs_stat)
124
+ null_dist = np.asarray(null_dist)
125
+
126
+ # Handle shape differences
127
+ if null_dist.ndim == 1:
128
+ null_dist = null_dist[:, np.newaxis]
129
+ if obs_stat.ndim == 0:
130
+ obs_stat = obs_stat.reshape(1)
131
+ elif obs_stat.ndim == 1:
132
+ obs_stat = obs_stat.reshape(1, -1)
133
+
134
+ n_permute = null_dist.shape[0]
135
+ denom = float(n_permute) + 1.0
136
+
137
+ if tail_normalized == "upper":
138
+ # One-tailed upper: count how many null >= observed
139
+ # Tests for positive effects (H1: statistic > 0)
140
+ numer = np.sum(null_dist >= obs_stat, axis=0) + 1.0
141
+ elif tail_normalized == "lower":
142
+ # One-tailed lower: count how many null <= observed
143
+ # Tests for negative effects (H1: statistic < 0)
144
+ numer = np.sum(null_dist <= obs_stat, axis=0) + 1.0
145
+ else: # tail_normalized == "two"
146
+ # Two-tailed: count how many |null| >= |observed|
147
+ numer = np.sum(np.abs(null_dist) >= np.abs(obs_stat), axis=0) + 1.0
148
+
149
+ p_values = numer / denom
150
+
151
+ return p_values
@@ -0,0 +1,72 @@
1
+ """Scikit-learn-compatible cross-validation data classes."""
2
+
3
+ __all__ = ["KFoldStratified"]
4
+
5
+ from sklearn.model_selection._split import _BaseKFold
6
+ from sklearn.utils import check_random_state
7
+ from sklearn.utils.validation import check_array
8
+ import numpy as np
9
+
10
+
11
+ class KFoldStratified(_BaseKFold):
12
+ """Stratify continuous targets across K-fold cross-validation.
13
+
14
+ Unlike the scikit-learn equivalent, this iterator stratifies continuous data.
15
+
16
+ Provides train/test indices to split data in train test sets. Samples are
17
+ ordered by their continuous target `y` and dealt round-robin into k folds
18
+ so each fold spans the full range of `y`. Each fold is then used as a
19
+ validation set once while the k - 1 remaining folds form the training set.
20
+
21
+ Args:
22
+ n_splits (int): Number of folds. Must be at least 2. Defaults to 3.
23
+ shuffle (bool): Whether to break ties in `y` randomly before dealing samples
24
+ into folds. Default False.
25
+ random_state (int | np.random.RandomState, optional): Seed or RandomState for
26
+ the tie-break shuffle. If None, use the default numpy RNG.
27
+ """
28
+
29
+ def __init__(self, n_splits=3, *, shuffle=False, random_state=None):
30
+ super().__init__(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
31
+
32
+ def _make_test_folds(self, X, y=None, groups=None):
33
+ y_arr = np.asarray(y).ravel()
34
+ n = len(y_arr)
35
+ if self.shuffle:
36
+ # Sort by y (stratification) but break ties randomly so that
37
+ # shuffle/random_state actually vary the fold assignment. lexsort
38
+ # uses the last key as the primary sort, so y_arr stays primary and
39
+ # the random tiebreak only reorders samples that share a y value.
40
+ rng = check_random_state(self.random_state)
41
+ tiebreak = rng.permutation(n)
42
+ order = np.lexsort((tiebreak, y_arr))
43
+ else:
44
+ order = np.argsort(y_arr, kind="stable")
45
+ test_folds = np.full(n, np.nan)
46
+ for k in range(self.n_splits):
47
+ test_folds[order[np.arange(k, n, self.n_splits)]] = k
48
+ return test_folds
49
+
50
+ def _iter_test_masks(self, X=None, y=None, groups=None):
51
+ test_folds = self._make_test_folds(X, y)
52
+ for i in range(self.n_splits):
53
+ yield test_folds == i
54
+
55
+ def split(self, X, y=None, groups=None):
56
+ """Generate indices to split data into training and test set.
57
+
58
+ Args:
59
+ X (array-like): Training data of shape `(n_samples, n_features)`. Only
60
+ `y` is needed to generate the splits, so `np.zeros(n_samples)` works
61
+ as a placeholder.
62
+ y (array-like): Continuous target of shape `(n_samples,)`; stratification
63
+ is based on its ordering.
64
+ groups (array-like, optional): Always ignored; exists for sklearn
65
+ compatibility.
66
+
67
+ Yields:
68
+ tuple[np.ndarray, np.ndarray]: `(train, test)` — the training set indices
69
+ and the testing set indices for that split.
70
+ """
71
+ y = check_array(y, ensure_2d=False, dtype=None)
72
+ return super().split(X, y, groups)
@@ -0,0 +1,30 @@
1
+ """Data classes for neuroimaging analysis.
2
+
3
+ `BrainData` (masked voxel data), `Adjacency` (similarity/distance matrices),
4
+ `DesignMatrix` (regressors), the `Roc` analysis class, the
5
+ `Simulator`/`SimulateGrid` data simulators, and the frozen result records those
6
+ classes return (`Predict`, `BootstrapResult`, `ContrastResult`) plus the
7
+ brain-space configuration record (`BrainSpaceConfig`).
8
+ """
9
+
10
+ from .braindata import BrainData
11
+ from .adjacency import Adjacency
12
+ from .designmatrix import DesignMatrix
13
+ from .results import BootstrapResult, Predict
14
+ from .simulator import Simulator, SimulateGrid
15
+ from .roc import Roc
16
+ from nltools.models.results import ContrastResult
17
+ from nltools.templates.config import BrainSpaceConfig
18
+
19
+ __all__ = [
20
+ "Adjacency",
21
+ "BootstrapResult",
22
+ "BrainData",
23
+ "BrainSpaceConfig",
24
+ "ContrastResult",
25
+ "DesignMatrix",
26
+ "Predict",
27
+ "Roc",
28
+ "SimulateGrid",
29
+ "Simulator",
30
+ ]