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,373 @@
1
+ """Permutation test for the correlation between two variables.
2
+
3
+ `correlation_permutation_test` asks whether the Pearson, Spearman, or Kendall
4
+ correlation between two arrays differs from zero, building the null distribution
5
+ by shuffling one array's observations. It assumes observations are independent;
6
+ for autocorrelated time series use `_timeseries_correlation_permutation_test`.
7
+ Multi-feature inputs (2D arrays) test each column pair independently.
8
+ Permutations run on joblib workers; `n_jobs` sets how many, and a given
9
+ `random_state` gives the same result at any worker count.
10
+ """
11
+
12
+ import numpy as np
13
+ from collections.abc import Callable
14
+ from scipy.stats import rankdata, kendalltau
15
+ from sklearn.utils import check_random_state
16
+
17
+ from .utils import EPSILON, _maybe_tqdm
18
+ from ..validation import _compute_pvalue, _validate_tail_parameter
19
+
20
+
21
+ def _pearson_correlation(x: np.ndarray, y: np.ndarray) -> np.ndarray | float:
22
+ """Compute Pearson correlation coefficient(s).
23
+
24
+ Args:
25
+ x (np.ndarray): Data array, shape (n_samples,) or (n_permute, n_samples).
26
+ y (np.ndarray): Data array, shape (n_samples,).
27
+
28
+ Returns:
29
+ float | np.ndarray: A scalar if `x` is 1D, else one correlation per row
30
+ of `x`, shape (n_permute,).
31
+
32
+ Note:
33
+ Centers the data before computing, and vectorizes across the rows of a
34
+ 2D `x`.
35
+ """
36
+ # Handle dimensions
37
+ if x.ndim == 1:
38
+ x = x[np.newaxis, :] # (1, n_samples)
39
+ squeeze_output = True
40
+ else:
41
+ squeeze_output = False
42
+
43
+ # Center data
44
+ x_centered = x - x.mean(axis=1, keepdims=True) # (n_permute, n_samples)
45
+ y_centered = y - y.mean() # (n_samples,)
46
+
47
+ # Compute correlation
48
+ numerator = (x_centered @ y_centered) / x.shape[1]
49
+ denominator = x_centered.std(axis=1, ddof=0) * y_centered.std(ddof=0)
50
+
51
+ # Handle division by zero (constant data) using EPSILON
52
+ correlations = numerator / (denominator + EPSILON)
53
+
54
+ if squeeze_output:
55
+ return float(correlations[0])
56
+ return correlations
57
+
58
+
59
+ def _spearman_correlation(x: np.ndarray, y: np.ndarray) -> np.ndarray | float:
60
+ """Compute Spearman rank correlation coefficient(s).
61
+
62
+ Spearman correlation is the Pearson correlation of the rank-transformed data.
63
+ It measures monotonic (not necessarily linear) relationships.
64
+
65
+ Args:
66
+ x (np.ndarray): Data array, shape (n_samples,) or (n_permute, n_samples).
67
+ y (np.ndarray): Data array, shape (n_samples,).
68
+
69
+ Returns:
70
+ float | np.ndarray: A scalar if `x` is 1D, else one correlation per row
71
+ of `x`, shape (n_permute,).
72
+
73
+ Note:
74
+ Ranks with `scipy.stats.rankdata` (ties get their average rank), then
75
+ applies the Pearson correlation to the ranks.
76
+ """
77
+ # Handle dimensions
78
+ if x.ndim == 1:
79
+ x = x[np.newaxis, :] # (1, n_samples)
80
+ squeeze_output = True
81
+ else:
82
+ squeeze_output = False
83
+
84
+ n_permute, n_samples = x.shape
85
+
86
+ # Rank-transform data (average method for tied ranks)
87
+ # For vectorized case, rank each permutation separately
88
+ x_ranked = np.empty_like(x)
89
+ for i in range(n_permute):
90
+ x_ranked[i] = rankdata(x[i], method="average")
91
+
92
+ y_ranked = rankdata(y, method="average")
93
+
94
+ # Apply Pearson correlation to ranks
95
+ # Center ranked data
96
+ x_centered = x_ranked - x_ranked.mean(axis=1, keepdims=True)
97
+ y_centered = y_ranked - y_ranked.mean()
98
+
99
+ # Compute correlation
100
+ numerator = (x_centered @ y_centered) / n_samples
101
+ denominator = x_centered.std(axis=1, ddof=0) * y_centered.std(ddof=0)
102
+
103
+ # Handle division by zero (constant data - all tied ranks) using EPSILON
104
+ correlations = numerator / (denominator + EPSILON)
105
+
106
+ if squeeze_output:
107
+ return float(correlations[0])
108
+ return correlations
109
+
110
+
111
+ def _kendall_correlation(x: np.ndarray, y: np.ndarray) -> np.ndarray | float:
112
+ """Compute Kendall rank correlation coefficient(s).
113
+
114
+ Kendall tau correlation measures ordinal association based on concordant
115
+ and discordant pairs. More robust than Spearman for small samples or
116
+ data with many tied ranks.
117
+
118
+ Args:
119
+ x (np.ndarray): Data array, shape (n_samples,) or (n_permute, n_samples).
120
+ y (np.ndarray): Data array, shape (n_samples,).
121
+
122
+ Returns:
123
+ float | np.ndarray: A scalar if `x` is 1D, else one correlation per row
124
+ of `x`, shape (n_permute,). A NaN tau (constant input) is returned
125
+ as 0.0.
126
+
127
+ Note:
128
+ Calls `scipy.stats.kendalltau` once per row; O(n²) per call, so slower
129
+ than Pearson or Spearman.
130
+ """
131
+ # Handle dimensions
132
+ if x.ndim == 1:
133
+ # Single correlation
134
+ tau, _ = kendalltau(x, y)
135
+ return float(tau) if not np.isnan(tau) else 0.0
136
+ # Vectorized: compute each permutation separately
137
+ n_permute = x.shape[0]
138
+ correlations = np.empty(n_permute)
139
+ for i in range(n_permute):
140
+ tau, _ = kendalltau(x[i], y)
141
+ correlations[i] = tau if not np.isnan(tau) else 0.0
142
+ return correlations
143
+
144
+
145
+ def _select_corr_func(
146
+ metric: str,
147
+ ) -> Callable[[np.ndarray, np.ndarray], np.ndarray | float]:
148
+ """Return the correlation function for a metric name."""
149
+ if metric == "pearson":
150
+ return _pearson_correlation
151
+ if metric == "spearman":
152
+ return _spearman_correlation
153
+ if metric == "kendall":
154
+ return _kendall_correlation
155
+ raise NotImplementedError(f"Metric '{metric}' not yet implemented")
156
+
157
+
158
+ def _correlation_permutation_cpu_parallel(
159
+ data1: np.ndarray,
160
+ data2: np.ndarray,
161
+ *,
162
+ n_permute: int,
163
+ metric: str,
164
+ tail: int,
165
+ return_null: bool,
166
+ n_jobs: int,
167
+ random_state: int | None,
168
+ single_feature: bool = False,
169
+ progress_bar: bool = False,
170
+ ) -> dict:
171
+ """Correlation permutation test parallelized across CPU workers with joblib.
172
+
173
+ Each worker handles one permutation: shuffle `data1`, correlate with `data2`.
174
+ Seeds are pre-generated from `random_state`, so results do not depend on
175
+ `n_jobs`.
176
+
177
+ Args:
178
+ data1 (np.ndarray): Data to permute, shape (n_samples, n_features).
179
+ data2 (np.ndarray): Data to correlate with, shape (n_samples, n_features).
180
+ n_permute (int): Number of permutations.
181
+ metric (str): Correlation metric, one of 'pearson', 'spearman', or 'kendall'.
182
+ tail (int | str): `2` or `'two'` for two-tailed; `1` or `'one'` for one-tailed.
183
+ return_null (bool): Whether to return the null distribution.
184
+ n_jobs (int): Number of parallel workers (-1 = all cores).
185
+ random_state (int | None): Random seed for reproducibility.
186
+ single_feature (bool): Whether the caller passed 1D inputs (results are
187
+ returned as scalars).
188
+ progress_bar (bool): Show a progress bar over permutations.
189
+
190
+ Returns:
191
+ dict: Same keys as `correlation_permutation_test`.
192
+ """
193
+ from joblib import Parallel, delayed
194
+
195
+ # Setup random state and generate seeds for workers
196
+ rng = check_random_state(random_state)
197
+ MAX_INT = 2**31 - 1
198
+ seeds = rng.randint(MAX_INT, size=n_permute)
199
+
200
+ # Get dimensions (data already reshaped by caller)
201
+ n_samples, n_features = data1.shape
202
+
203
+ # Select correlation function
204
+ corr_func = _select_corr_func(metric)
205
+
206
+ # Compute observed correlation
207
+ if n_features == 1:
208
+ obs_corr = corr_func(data1[:, 0], data2[:, 0])
209
+ obs_corr = np.array([obs_corr])
210
+ else:
211
+ obs_corr = np.array(
212
+ [corr_func(data1[:, i], data2[:, i]) for i in range(n_features)]
213
+ )
214
+
215
+ # Define worker function (each processes ONE permutation)
216
+ def _compute_one_perm(seed):
217
+ """Compute correlation for one permutation."""
218
+ perm_rng = np.random.RandomState(seed)
219
+ # Permute data1 indices
220
+ indices = perm_rng.permutation(n_samples)
221
+ perm_data1 = data1[indices]
222
+
223
+ # Compute correlation for each feature
224
+ if n_features == 1:
225
+ return corr_func(perm_data1[:, 0], data2[:, 0])
226
+ return np.array(
227
+ [corr_func(perm_data1[:, i], data2[:, i]) for i in range(n_features)]
228
+ )
229
+
230
+ # Execute in parallel with progress bar
231
+ null_dist = Parallel(n_jobs=n_jobs)(
232
+ delayed(_compute_one_perm)(seeds[i])
233
+ for i in _maybe_tqdm(
234
+ range(n_permute),
235
+ progress_bar=progress_bar,
236
+ desc="CPU parallel perms",
237
+ unit="perm",
238
+ )
239
+ )
240
+ null_dist = np.array(null_dist) # Shape: (n_permute, n_features)
241
+
242
+ # Compute p-values
243
+ p_values = _compute_pvalue(obs_corr, null_dist, tail=tail)
244
+
245
+ # Return to original shape
246
+ if single_feature:
247
+ obs_corr = obs_corr.item() if hasattr(obs_corr, "item") else float(obs_corr[0])
248
+ p_values = p_values.item() if hasattr(p_values, "item") else float(p_values[0])
249
+
250
+ # Build result
251
+ result = {
252
+ "correlation": obs_corr,
253
+ "p": p_values,
254
+ }
255
+
256
+ if return_null:
257
+ if single_feature:
258
+ null_dist = null_dist.squeeze()
259
+ result["null_dist"] = null_dist
260
+
261
+ return result
262
+
263
+
264
+ def correlation_permutation_test(
265
+ data1: np.ndarray,
266
+ data2: np.ndarray,
267
+ *,
268
+ n_permute: int = 5000,
269
+ metric: str = "pearson",
270
+ tail: int | str = 2,
271
+ return_null: bool = False,
272
+ n_jobs: int = -1,
273
+ random_state: int | None = None,
274
+ progress_bar: bool = False,
275
+ ) -> dict:
276
+ """Permutation test for whether the correlation between two arrays differs from zero.
277
+
278
+ Builds the null distribution by randomly permuting the observations of `data1`
279
+ and re-correlating with `data2`. Assumes observations are independent (i.i.d.);
280
+ for autocorrelated time series use `_timeseries_correlation_permutation_test`,
281
+ whose `'circle_shift'` and `'phase_randomize'` methods preserve temporal
282
+ structure. With 2D inputs each column of `data1` is tested against the
283
+ matching column of `data2`, independently.
284
+
285
+ Args:
286
+ data1 (np.ndarray): Data to permute, shape (n_samples,) for a single
287
+ feature or (n_samples, n_features) for several.
288
+ data2 (np.ndarray): Data to correlate with, same shape as `data1`.
289
+ n_permute (int): Number of permutations. Defaults to 5000.
290
+ metric (str): 'pearson' (linear), 'spearman' (rank-based, monotonic), or
291
+ 'kendall' (tau-b, ordinal association, tie-corrected). Defaults to
292
+ 'pearson'.
293
+ tail (int | str): `2` or `'two'` for a two-tailed test (r != 0); `1` or
294
+ `'one'` for a one-tailed test of r > 0 (negate one variable for the
295
+ other direction; the fixed direction keeps multiple-comparison
296
+ correction valid). Defaults to 2.
297
+ return_null (bool): Also return the full null distribution. Defaults to
298
+ False.
299
+ n_jobs (int): Number of joblib workers, -1 = all cores. Defaults to -1.
300
+ Results are identical at every worker count.
301
+ random_state (int | None): Random seed for reproducibility.
302
+ progress_bar (bool): Show a progress bar over permutations. Defaults to
303
+ False.
304
+
305
+ Returns:
306
+ dict: Keys 'correlation' (float, or np.ndarray of shape (n_features,) for
307
+ 2D inputs: the observed correlation), 'p' (float or np.ndarray, the
308
+ matching p-values), and 'null_dist' (np.ndarray of shape
309
+ (n_permute,) or (n_permute, n_features)) when `return_null=True`.
310
+
311
+ Examples:
312
+ ```python
313
+ import numpy as np
314
+ from nltools.algorithms import correlation_permutation_test
315
+
316
+ # Single feature
317
+ x = np.random.randn(100)
318
+ y = x + np.random.randn(100) * 0.5
319
+ result = correlation_permutation_test(x, y, n_permute=5000)
320
+ result["correlation"] # → 0.85 (approximately)
321
+ result["p"] # → 0.0002
322
+
323
+ # Multi-feature: each column pair tested independently
324
+ data1 = np.random.randn(100, 10)
325
+ data2 = data1 + np.random.randn(100, 10) * 0.3
326
+ result = correlation_permutation_test(data1, data2, n_permute=5000)
327
+ result["correlation"].shape # → (10,)
328
+ result["p"].shape # → (10,)
329
+ ```
330
+
331
+ Note:
332
+ Kendall's tau is O(n²) in the number of samples, so it is markedly
333
+ slower than Pearson or Spearman for large samples.
334
+ """
335
+ # Input validation
336
+ data1 = np.asarray(data1, dtype=np.float64)
337
+ data2 = np.asarray(data2, dtype=np.float64)
338
+
339
+ if data1.ndim not in [1, 2]:
340
+ raise ValueError(f"data1 must be 1D or 2D, got shape {data1.shape}")
341
+ if data2.ndim not in [1, 2]:
342
+ raise ValueError(f"data2 must be 1D or 2D, got shape {data2.shape}")
343
+ tail = _validate_tail_parameter(tail)
344
+ if metric not in ["pearson", "spearman", "kendall"]:
345
+ raise ValueError(
346
+ f"metric must be 'pearson', 'spearman', or 'kendall', got '{metric}'"
347
+ )
348
+
349
+ # Handle shape
350
+ single_feature = data1.ndim == 1 and data2.ndim == 1
351
+ if data1.ndim == 1:
352
+ data1 = data1[:, np.newaxis]
353
+ if data2.ndim == 1:
354
+ data2 = data2[:, np.newaxis]
355
+
356
+ # Check dimensions match
357
+ if data1.shape != data2.shape:
358
+ raise ValueError(
359
+ f"data1 and data2 must have same shape, got {data1.shape} and {data2.shape}"
360
+ )
361
+
362
+ return _correlation_permutation_cpu_parallel(
363
+ data1,
364
+ data2,
365
+ n_permute=n_permute,
366
+ metric=metric,
367
+ tail=tail,
368
+ return_null=return_null,
369
+ n_jobs=n_jobs,
370
+ random_state=random_state,
371
+ single_feature=single_feature,
372
+ progress_bar=progress_bar,
373
+ )