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,422 @@
|
|
|
1
|
+
"""Intersubject correlation, functional connectivity, and phase synchrony."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import polars as pl
|
|
5
|
+
from scipy.signal import hilbert
|
|
6
|
+
|
|
7
|
+
from .isc import _isc_permutation_test
|
|
8
|
+
from .matrix import _compute_cross_correlation
|
|
9
|
+
from .utils import _maybe_tqdm
|
|
10
|
+
|
|
11
|
+
from ..signal import (
|
|
12
|
+
_butter_bandpass_filter,
|
|
13
|
+
_phase_mean_angle,
|
|
14
|
+
_phase_rayleigh_p,
|
|
15
|
+
_phase_vector_length,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _as_ndarray(data, name="data"):
|
|
20
|
+
"""Coerce a numpy array, polars DataFrame, or pandas DataFrame to a numpy array."""
|
|
21
|
+
if isinstance(data, np.ndarray):
|
|
22
|
+
return data
|
|
23
|
+
if isinstance(data, pl.DataFrame):
|
|
24
|
+
return data.to_numpy()
|
|
25
|
+
try:
|
|
26
|
+
import pandas as pd
|
|
27
|
+
except ImportError:
|
|
28
|
+
pd = None
|
|
29
|
+
if pd is not None and isinstance(data, pd.DataFrame):
|
|
30
|
+
return data.values
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"{name} must be a numpy array, polars DataFrame, or pandas DataFrame"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def isc(
|
|
37
|
+
data,
|
|
38
|
+
*,
|
|
39
|
+
n_samples=5000,
|
|
40
|
+
summary="median",
|
|
41
|
+
summary_statistic="pairwise",
|
|
42
|
+
method="bootstrap",
|
|
43
|
+
ci_percentile=95,
|
|
44
|
+
exclude_self_corr=True,
|
|
45
|
+
tail=2,
|
|
46
|
+
metric="correlation",
|
|
47
|
+
return_null=False,
|
|
48
|
+
n_jobs=-1,
|
|
49
|
+
random_state=None,
|
|
50
|
+
progress_bar=False,
|
|
51
|
+
):
|
|
52
|
+
"""Compute intersubject correlation across the subject axis of an aligned array.
|
|
53
|
+
|
|
54
|
+
ISC is summarized with the median, as Chen et al. (2016) recommend;
|
|
55
|
+
`summary='mean'` instead averages after the Fisher r-to-z transform and
|
|
56
|
+
converts back, which avoids inflating the estimate.
|
|
57
|
+
|
|
58
|
+
Three null distributions are available. The default subject-wise bootstrap
|
|
59
|
+
(Chen et al., 2016) resamples subjects with replacement and recomputes the
|
|
60
|
+
chosen summary statistic — for `'pairwise'`, the similarity matrix, where a
|
|
61
|
+
subject drawn twice correlates perfectly with itself, so those entries are
|
|
62
|
+
set to NaN when `exclude_self_corr=True`. P-values use the percentile
|
|
63
|
+
method, as in Brainiak. The classic surrogate methods instead circle-shift
|
|
64
|
+
or phase-randomize each time series (Lancaster et al., 2018), preserving
|
|
65
|
+
its temporal autocorrelation, and recompute ISC.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
data (np.ndarray | pl.DataFrame | pd.DataFrame): Observations by
|
|
69
|
+
subjects, shape `(n_observations, n_subjects)`, with ISC computed
|
|
70
|
+
across the columns; or `(n_observations, n_subjects, n_voxels)` for
|
|
71
|
+
a per-voxel result, in which case `'isc'`, `'p'` and both `'ci'`
|
|
72
|
+
bounds are arrays of length `n_voxels`. DataFrame inputs are 2D
|
|
73
|
+
only.
|
|
74
|
+
n_samples (int): Number of bootstrap draws or surrogate permutations.
|
|
75
|
+
Defaults to 5000.
|
|
76
|
+
summary (str): `'median'` (default) or `'mean'`.
|
|
77
|
+
summary_statistic (str): Which cross-subject comparison to summarize.
|
|
78
|
+
`'pairwise'` (default) correlates every pair of subjects,
|
|
79
|
+
`O(n_subjects²)`; `'leave-one-out'` correlates each subject with
|
|
80
|
+
the mean of the others, `O(n_subjects)`. Leave-one-out gives
|
|
81
|
+
systematically larger values because the averaged reference is less
|
|
82
|
+
noisy than a single subject (Chen et al., 2016, Figure 3).
|
|
83
|
+
method (str): `'bootstrap'` (default), `'circle_shift'`, or
|
|
84
|
+
`'phase_randomize'`.
|
|
85
|
+
ci_percentile (int): Confidence-interval width in percent. Defaults to 95.
|
|
86
|
+
exclude_self_corr (bool): Set self-correlations (the same subject
|
|
87
|
+
bootstrapped twice) to NaN. Applies to the pairwise statistic only.
|
|
88
|
+
Defaults to True.
|
|
89
|
+
tail (int | str): `2` or `'two'` (two-tailed, default) or `1` or `'one'`
|
|
90
|
+
(one-tailed, ISC > 0).
|
|
91
|
+
metric (str): Pairwise similarity metric; any metric accepted by
|
|
92
|
+
sklearn's `pairwise_distances`. Applies to the pairwise statistic
|
|
93
|
+
only. Defaults to `'correlation'`.
|
|
94
|
+
return_null (bool): Include the null distribution in the result.
|
|
95
|
+
Defaults to False.
|
|
96
|
+
n_jobs (int): CPU workers for the resamples; -1 (default) picks the
|
|
97
|
+
count from available memory.
|
|
98
|
+
random_state (int | np.random.RandomState | None): Seed or generator for
|
|
99
|
+
the resampling.
|
|
100
|
+
progress_bar (bool): Display a progress bar. Defaults to False.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
dict: Keys `'isc'` (observed ISC), `'p'`, and `'ci'` (tuple
|
|
104
|
+
`(lower, upper)`) — floats for 2D data, arrays of length
|
|
105
|
+
`n_voxels` for 3D — and, when `return_null=True`, `'null_dist'`
|
|
106
|
+
(np.ndarray).
|
|
107
|
+
|
|
108
|
+
Note:
|
|
109
|
+
`exclude_self_corr=True` (the default) sets a subject's correlation
|
|
110
|
+
with itself to NaN when the bootstrap draws that subject twice;
|
|
111
|
+
turning it off inflates ISC. Resamples are counted with `n_samples`
|
|
112
|
+
here, for the surrogate methods as well as the bootstrap —
|
|
113
|
+
`n_permute` belongs to the permutation tests in `nltools.algorithms`
|
|
114
|
+
and is not accepted by this function.
|
|
115
|
+
|
|
116
|
+
References:
|
|
117
|
+
Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C.,
|
|
118
|
+
Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among
|
|
119
|
+
correlations, part I: nonparametric approaches to inter-subject
|
|
120
|
+
correlation analysis at the group level. NeuroImage, 142, 248-259.
|
|
121
|
+
|
|
122
|
+
Hall, P., & Wilson, S. R. (1991). Two guidelines for bootstrap
|
|
123
|
+
hypothesis testing. Biometrics, 757-762.
|
|
124
|
+
|
|
125
|
+
Lancaster, G., Iatsenko, D., Pidde, A., Ticcinelli, V., & Stefanovska,
|
|
126
|
+
A. (2018). Surrogate data for hypothesis testing of physical systems.
|
|
127
|
+
Physics Reports, 748, 1-60.
|
|
128
|
+
"""
|
|
129
|
+
data = _as_ndarray(data)
|
|
130
|
+
|
|
131
|
+
if summary not in ["mean", "median"]:
|
|
132
|
+
raise ValueError("summary must be ['mean', 'median']")
|
|
133
|
+
|
|
134
|
+
# The engine speaks the same canonical vocabulary (summary=, metric=), so
|
|
135
|
+
# this wrapper only maps n_samples -> n_permute.
|
|
136
|
+
return _isc_permutation_test(
|
|
137
|
+
data,
|
|
138
|
+
n_permute=n_samples, # Map n_samples -> n_permute
|
|
139
|
+
summary=summary,
|
|
140
|
+
summary_statistic=summary_statistic,
|
|
141
|
+
method=method,
|
|
142
|
+
ci_percentile=ci_percentile,
|
|
143
|
+
tail=tail,
|
|
144
|
+
n_jobs=n_jobs,
|
|
145
|
+
random_state=random_state,
|
|
146
|
+
return_null=return_null,
|
|
147
|
+
exclude_self_corr=exclude_self_corr,
|
|
148
|
+
metric=metric,
|
|
149
|
+
progress_bar=progress_bar,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def isc_group(
|
|
154
|
+
group1,
|
|
155
|
+
group2,
|
|
156
|
+
*,
|
|
157
|
+
n_samples=5000,
|
|
158
|
+
summary="median",
|
|
159
|
+
method="permute",
|
|
160
|
+
ci_percentile=95,
|
|
161
|
+
exclude_self_corr=True,
|
|
162
|
+
return_null=False,
|
|
163
|
+
tail=2,
|
|
164
|
+
metric="correlation",
|
|
165
|
+
n_jobs=-1,
|
|
166
|
+
random_state=None,
|
|
167
|
+
progress_bar=False,
|
|
168
|
+
):
|
|
169
|
+
"""Test the difference in pairwise intersubject correlation between two groups.
|
|
170
|
+
|
|
171
|
+
ISC within each group is summarized with the median, as Chen et al. (2016)
|
|
172
|
+
recommend (`summary='mean'` averages after the Fisher r-to-z transform), and
|
|
173
|
+
the observed statistic is `group1 - group2`.
|
|
174
|
+
|
|
175
|
+
Two null distributions are available. The default subject-wise permutation
|
|
176
|
+
(Chen et al., 2016) pools the subjects, computes pairwise similarity within
|
|
177
|
+
and between groups, then reshuffles the group labels and recomputes the
|
|
178
|
+
difference. The subject-wise bootstrap instead resamples subjects with
|
|
179
|
+
replacement within each group; a subject drawn twice correlates perfectly
|
|
180
|
+
with itself, so those entries are set to NaN when `exclude_self_corr=True`.
|
|
181
|
+
P-values use the percentile method (Hall & Wilson, 1991).
|
|
182
|
+
|
|
183
|
+
Runs on plain arrays; `_isc_group_permutation_test` exposes the same engine
|
|
184
|
+
with leave-one-out ISC.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
group1 (np.ndarray | pl.DataFrame | pd.DataFrame): Observations by
|
|
188
|
+
subjects for the first group.
|
|
189
|
+
group2 (np.ndarray | pl.DataFrame | pd.DataFrame): Observations by
|
|
190
|
+
subjects for the second group (same number of observations).
|
|
191
|
+
n_samples (int): Number of permutations or bootstrap draws. Defaults to
|
|
192
|
+
5000.
|
|
193
|
+
summary (str): `'median'` (default) or `'mean'`.
|
|
194
|
+
method (str): `'permute'` (default) or `'bootstrap'`.
|
|
195
|
+
ci_percentile (float): Confidence-interval width in percent. Defaults to
|
|
196
|
+
95.
|
|
197
|
+
exclude_self_corr (bool): In the bootstrap, set self-correlations to NaN.
|
|
198
|
+
Defaults to True.
|
|
199
|
+
return_null (bool): Include the null distribution in the result.
|
|
200
|
+
Defaults to False.
|
|
201
|
+
tail (int | str): `2` or `'two'` (two-tailed, default) or `1` or `'one'`
|
|
202
|
+
(one-tailed, group1 > group2).
|
|
203
|
+
metric (str): Pairwise similarity metric; any metric accepted by
|
|
204
|
+
sklearn's `pairwise_distances`. Defaults to `'correlation'`.
|
|
205
|
+
n_jobs (int): CPU workers for the resamples; -1 (default) picks the
|
|
206
|
+
count from available memory.
|
|
207
|
+
random_state (int | np.random.RandomState | None): Random seed for
|
|
208
|
+
reproducibility.
|
|
209
|
+
progress_bar (bool): Display a progress bar. Defaults to False.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
dict: Keys `'isc_group_difference'` (float, observed difference), `'p'`
|
|
213
|
+
(float), `'ci'` (tuple `(lower, upper)`), and — when
|
|
214
|
+
`return_null=True` — `'null_dist'` (np.ndarray).
|
|
215
|
+
|
|
216
|
+
References:
|
|
217
|
+
Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C.,
|
|
218
|
+
Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among
|
|
219
|
+
correlations, part I: nonparametric approaches to inter-subject
|
|
220
|
+
correlation analysis at the group level. NeuroImage, 142, 248-259.
|
|
221
|
+
|
|
222
|
+
Hall, P., & Wilson, S. R. (1991). Two guidelines for bootstrap
|
|
223
|
+
hypothesis testing. Biometrics, 757-762.
|
|
224
|
+
"""
|
|
225
|
+
from .isc import _isc_group_permutation_test
|
|
226
|
+
|
|
227
|
+
group1 = _as_ndarray(group1, name="group1")
|
|
228
|
+
group2 = _as_ndarray(group2, name="group2")
|
|
229
|
+
|
|
230
|
+
if summary not in ["mean", "median"]:
|
|
231
|
+
raise ValueError("summary must be ['mean', 'median']")
|
|
232
|
+
|
|
233
|
+
if group1.shape[0] != group2.shape[0]:
|
|
234
|
+
raise ValueError("group1 has a different number of observations from group2.")
|
|
235
|
+
|
|
236
|
+
if method not in ["permute", "bootstrap"]:
|
|
237
|
+
raise NotImplementedError("method can only be ['permute', 'bootstrap']")
|
|
238
|
+
|
|
239
|
+
# The engine speaks the same canonical vocabulary; only n_samples ->
|
|
240
|
+
# n_permute is mapped here.
|
|
241
|
+
return _isc_group_permutation_test(
|
|
242
|
+
group1,
|
|
243
|
+
group2,
|
|
244
|
+
n_permute=n_samples, # Map parameter name
|
|
245
|
+
summary=summary,
|
|
246
|
+
method=method,
|
|
247
|
+
ci_percentile=ci_percentile,
|
|
248
|
+
tail=tail,
|
|
249
|
+
metric=metric,
|
|
250
|
+
n_jobs=n_jobs,
|
|
251
|
+
random_state=random_state,
|
|
252
|
+
return_null=return_null,
|
|
253
|
+
exclude_self_corr=exclude_self_corr,
|
|
254
|
+
progress_bar=progress_bar,
|
|
255
|
+
summary_statistic="pairwise", # Match old behavior (always pairwise)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def isfc(data, *, method="average", n_jobs=-1, random_state=None, progress_bar=False):
|
|
260
|
+
"""Compute intersubject functional connectivity (ISFC) from per-subject matrices.
|
|
261
|
+
|
|
262
|
+
Uses the leave-one-out approach of Simony et al. (2016): for each subject,
|
|
263
|
+
average the other subjects' data and correlate every voxel/ROI time series
|
|
264
|
+
of the target subject with every voxel/ROI time series of that average.
|
|
265
|
+
Subjects are independent, so they are processed in parallel with joblib
|
|
266
|
+
unless `n_jobs=1`.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
data (list[np.ndarray]): One matrix per subject, each
|
|
270
|
+
`(n_observations, n_features)` with identical shapes.
|
|
271
|
+
method (str): Only `'average'` (leave-one-out) is implemented.
|
|
272
|
+
n_jobs (int): Parallel workers; -1 (default) uses all cores, 1 runs
|
|
273
|
+
serially.
|
|
274
|
+
random_state (int | np.random.RandomState | None): Unused. ISFC's
|
|
275
|
+
leave-one-out computation is deterministic and draws no random
|
|
276
|
+
samples; the parameter exists for signature parity with the rest
|
|
277
|
+
of the ISC family (`isc`, `isc_group`).
|
|
278
|
+
progress_bar (bool): Display a progress bar over subjects. Defaults to
|
|
279
|
+
False.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
list[np.ndarray]: One `(n_features, n_features)` ISFC matrix per
|
|
283
|
+
subject.
|
|
284
|
+
|
|
285
|
+
References:
|
|
286
|
+
Simony, E., Honey, C. J., Chen, J., Lositsky, O., Yeshurun, Y., Wiesel,
|
|
287
|
+
A., & Hasson, U. (2016). Dynamic reconfiguration of the default mode
|
|
288
|
+
network during narrative comprehension. Nature Communications, 7, 12141.
|
|
289
|
+
"""
|
|
290
|
+
if method != "average":
|
|
291
|
+
raise NotImplementedError(
|
|
292
|
+
"Only average method is implemented. Pairwise will be added at some point."
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Convert to numpy arrays if needed (for efficiency)
|
|
296
|
+
data_arrays = [np.asarray(subject_data) for subject_data in data]
|
|
297
|
+
n_subjects = len(data_arrays)
|
|
298
|
+
subjects = np.arange(n_subjects)
|
|
299
|
+
|
|
300
|
+
# Validate all subjects have same shape
|
|
301
|
+
reference_shape = data_arrays[0].shape
|
|
302
|
+
for i, subject_data in enumerate(data_arrays):
|
|
303
|
+
if subject_data.shape != reference_shape:
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"All subject matrices must have the same shape. "
|
|
306
|
+
f"Subject 0 has shape {reference_shape}, subject {i} has shape {subject_data.shape}"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
progress_kwargs = {
|
|
310
|
+
"progress_bar": progress_bar,
|
|
311
|
+
"desc": "ISFC subjects",
|
|
312
|
+
"unit": "subject",
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if n_jobs == 1:
|
|
316
|
+
# Serial execution (for explicit serial control)
|
|
317
|
+
sub_isfc = []
|
|
318
|
+
for target in _maybe_tqdm(subjects, **progress_kwargs):
|
|
319
|
+
m1 = data_arrays[target]
|
|
320
|
+
sub_mean = np.zeros(m1.shape)
|
|
321
|
+
for y in (y for y in subjects if y != target):
|
|
322
|
+
sub_mean += data_arrays[y]
|
|
323
|
+
# Use inference module function for cross-correlation computation
|
|
324
|
+
sub_isfc.append(_compute_cross_correlation(m1, sub_mean / (n_subjects - 1)))
|
|
325
|
+
else:
|
|
326
|
+
# Parallel execution using joblib (default: n_jobs=-1 uses all cores)
|
|
327
|
+
from joblib import Parallel, delayed
|
|
328
|
+
|
|
329
|
+
def _compute_one_subject_isfc(target_idx):
|
|
330
|
+
"""Compute ISFC for one subject (worker function)."""
|
|
331
|
+
m1 = data_arrays[target_idx]
|
|
332
|
+
sub_mean = np.zeros(m1.shape, dtype=m1.dtype)
|
|
333
|
+
for y in (y for y in subjects if y != target_idx):
|
|
334
|
+
sub_mean += data_arrays[y]
|
|
335
|
+
return _compute_cross_correlation(m1, sub_mean / (n_subjects - 1))
|
|
336
|
+
|
|
337
|
+
# Parallelize across subjects
|
|
338
|
+
sub_isfc = Parallel(n_jobs=n_jobs)(
|
|
339
|
+
delayed(_compute_one_subject_isfc)(target)
|
|
340
|
+
for target in _maybe_tqdm(subjects, **progress_kwargs)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return sub_isfc
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def isps(
|
|
347
|
+
data, *, sampling_freq=0.5, low_cut=0.04, high_cut=0.07, order=5, pairwise=False
|
|
348
|
+
):
|
|
349
|
+
"""Compute dynamic intersubject phase synchrony (ISPS) from an observations-by-subjects array.
|
|
350
|
+
|
|
351
|
+
Instantaneous phase synchrony across subjects for a single voxel/ROI time
|
|
352
|
+
series, after Glerean et al. (2012): the data are narrow-band filtered
|
|
353
|
+
(Butterworth) and Hilbert-transformed to get each subject's instantaneous
|
|
354
|
+
phase angle at every time point. Across subjects, the result gives the
|
|
355
|
+
mean phase angle, the mean resultant vector length, and a parametric
|
|
356
|
+
p-value from the Rayleigh test for circular uniformity (Fisher, 1995).
|
|
357
|
+
With `pairwise=True` these are computed on pairwise phase-angle differences
|
|
358
|
+
(inter-site phase coupling in the EEG literature) rather than on the raw
|
|
359
|
+
angles (inter-trial phase coupling).
|
|
360
|
+
|
|
361
|
+
The default band, 0.04-0.07 Hz, follows Glerean et al. (2012). It is close
|
|
362
|
+
to the "slow-4" band (0.025-0.067 Hz; Zuo et al., 2010; Penttonen &
|
|
363
|
+
Buzsáki, 2003) but excludes ~0.03 Hz, which carries aliased respiration
|
|
364
|
+
(Birn et al., 2006).
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
data (np.ndarray | pl.DataFrame | pd.DataFrame): Observations by
|
|
368
|
+
subjects.
|
|
369
|
+
sampling_freq (float): Sampling frequency in Hz. Defaults to 0.5.
|
|
370
|
+
low_cut (float): Lower band-pass cutoff in Hz. Defaults to 0.04.
|
|
371
|
+
high_cut (float): Upper band-pass cutoff in Hz. Defaults to 0.07.
|
|
372
|
+
order (int): Butterworth filter order. Defaults to 5.
|
|
373
|
+
pairwise (bool): Compute on pairwise phase-angle differences instead of
|
|
374
|
+
the raw phase angles. Defaults to False.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
dict: Keys `'average_angle'` (np.ndarray, mean phase angle per time
|
|
378
|
+
point), `'vector_length'` (np.ndarray, mean resultant length per
|
|
379
|
+
time point), and `'p'` (np.ndarray, Rayleigh-test p-value per time
|
|
380
|
+
point).
|
|
381
|
+
|
|
382
|
+
References:
|
|
383
|
+
Birn, R. M., Smith, M. A., Bandettini, P. A., & Diamond, J. B. (2006).
|
|
384
|
+
Separating respiratory-variation-related fluctuations from
|
|
385
|
+
neuronal-activity-related fluctuations in fMRI. NeuroImage, 31,
|
|
386
|
+
1536-1548.
|
|
387
|
+
|
|
388
|
+
Buzsáki, G., & Draguhn, A. (2004). Neuronal oscillations in cortical
|
|
389
|
+
networks. Science, 304(5679), 1926-1929.
|
|
390
|
+
|
|
391
|
+
Fisher, N. I. (1995). Statistical analysis of circular data. Cambridge
|
|
392
|
+
University Press.
|
|
393
|
+
|
|
394
|
+
Glerean, E., Salmi, J., Lahnakoski, J. M., Jääskeläinen, I. P., & Sams,
|
|
395
|
+
M. (2012). Functional magnetic resonance imaging phase synchronization
|
|
396
|
+
as a measure of dynamic functional connectivity. Brain Connectivity,
|
|
397
|
+
2(2), 91-101.
|
|
398
|
+
"""
|
|
399
|
+
data_array = _as_ndarray(data)
|
|
400
|
+
phase = np.angle(
|
|
401
|
+
hilbert(
|
|
402
|
+
_butter_bandpass_filter(
|
|
403
|
+
data_array, low_cut, high_cut, sampling_freq, order=order
|
|
404
|
+
),
|
|
405
|
+
axis=0,
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if pairwise:
|
|
410
|
+
phase = np.array(
|
|
411
|
+
[
|
|
412
|
+
phase[:, i] - phase[:, j]
|
|
413
|
+
for i in range(phase.shape[1])
|
|
414
|
+
for j in range(phase.shape[1])
|
|
415
|
+
if i < j
|
|
416
|
+
]
|
|
417
|
+
).T
|
|
418
|
+
|
|
419
|
+
out = {"average_angle": _phase_mean_angle(phase)}
|
|
420
|
+
out["vector_length"] = _phase_vector_length(phase)
|
|
421
|
+
out["p"] = _phase_rayleigh_p(phase)
|
|
422
|
+
return out
|