Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

inference

inference

GPU-accelerated statistical inference for neuroimaging.

This module provides fast permutation testing and bootstrap resampling using optional GPU acceleration via PyTorch. When GPU is unavailable, efficiently uses CPU parallelization.

Inspired by BROCCOLI’s GPU permutation testing (Eklund et al. 2014).

Key Features
  • 10-100× speedup for permutation tests with GPU

  • Efficient CPU parallelization when GPU unavailable

  • Transparent CPU/GPU support via Backend abstraction

  • Intersubject statistics (isc, isc_group, isfc, isps) built on the same permutation/bootstrap engine

Classes:

NameDescription
OnlineBootstrapStatsMemory-efficient online statistics aggregator for bootstrap samples.

Methods:

NameDescription
circle_shiftCircular shift for time-series data.
correlation_permutation_testCorrelation permutation test.
distance_correlationCompute the distance correlation between 2 arrays to test for multivariate dependence (linear or non-linear).
double_centerDouble center a 2d array.
isc_group_permutation_testCompute ISC difference between groups with permutation testing.
isc_permutation_testCompute intersubject correlation with permutation testing.
matrix_permutation_testMatrix permutation test (Mantel test) for correlating two square matrices.
one_sample_permutation_testOne-sample permutation test using sign-flipping.
phase_randomizeFFT-based phase randomization for time-series data.
timeseries_correlation_permutation_testTime-series correlation permutation test.
two_sample_permutation_testTwo-sample permutation test using group label shuffling.
u_centerU-center a 2d array. U-centering is a bias-corrected form of double-centering.

Modules:

NameDescription
bootstrapBootstrap inference utilities with CPU/GPU support.
correlationCorrelation permutation test implementations.
intersubjectIntersubject correlation, functional connectivity, and phase synchrony.
iscIntersubject Correlation (ISC) with GPU-Accelerated Permutation Testing.
matrixMatrix permutation test implementations (Mantel test).
one_sampleOne-sample permutation test implementations.
timeseriesTime-series permutation test implementations.
two_sampleTwo-sample permutation test implementations.
utilsUtility functions for permutation testing.
validationShared validation utilities for algorithms module.

Examples:

>>> import numpy as np
>>> from nltools.algorithms.inference import one_sample_permutation_test
>>> # Simple one-sample test
>>> data = np.random.randn(30)  # 30 subjects
>>> result = one_sample_permutation_test(data, n_permute=5000)
>>> print(f"p-value: {result['p']:.3f}")
>>> # Voxel-wise test with GPU acceleration
>>> data = np.random.randn(30, 50000)  # 30 subjects, 50K voxels
>>> result = one_sample_permutation_test(data, n_permute=10000, device='gpu')
>>> print(f"Significant voxels: {(result['p'] < 0.05).sum()}")
Performance
  • CPU (NumPy): Good for small problems (< 5K permutations)

  • GPU (PyTorch): Excellent for large problems (> 5K permutations)

  • CPU Parallel (joblib): Efficient fallback when GPU unavailable

  • Select with device=‘cpu’ | ‘gpu’ | None (no ‘auto’ selector)

References

Eklund, A., Dufort, P., Villani, M., & LaConte, S. M. (2014). BROCCOLI: Software for fast fMRI analysis on many-core CPUs and GPUs. Frontiers in Neuroinformatics, 8, 24.

Notes

This module is part of the “functional core” of nltools. For integration with BrainData objects, see nltools.data.brain_data.

Classes

OnlineBootstrapStats

OnlineBootstrapStats(shape: tuple[int, ...], save_samples: bool = False, percentiles: tuple[float, float] = (2.5, 97.5))

Memory-efficient online statistics aggregator for bootstrap samples.

Uses Welford’s algorithm for numerically stable online computation of mean and variance. Optionally stores all samples for exact percentile CIs.

Parameters:

NameTypeDescriptionDefault
shapetuple [ int , ...]Shape of each bootstrap sample.required
save_samplesboolIf True, store all samples for exact percentile confidence intervals. If False, use normal approximation (much more memory efficient). Defaults to False.False
percentilestuple [ float , float ]Percentiles for confidence intervals (e.g., (2.5, 97.5) for 95% CI). Defaults to (2.5, 97.5).(2.5, 97.5)

Attributes:

NameTypeDescription
M2
mean
n
percentiles
samples
save_samples
shape

Methods:

NameDescription
get_resultsCompute final bootstrap statistics.
updateUpdate statistics with a new bootstrap sample.

Examples:

>>> stats = OnlineBootstrapStats(shape=(100,), save_samples=False)
>>> for i in range(1000):
...     sample = np.random.randn(100)
...     stats.update(sample)
>>> results = stats.get_results()
>>> print(results.keys())
dict_keys(['mean', 'std', 'Z', 'p', 'ci_lower', 'ci_upper'])

Methods

get_results
get_results(tail: int | str = 2) -> dict[str, np.ndarray]

Compute final bootstrap statistics.

Parameters:

NameTypeDescriptionDefault
tailint | str2‘two’ (two-tailed, default) or 1

Returns:

TypeDescription
dict [ str , ndarray ]Dictionary containing:
dict [ str , ndarray ]- ‘mean’: Bootstrap mean
dict [ str , ndarray ]- ‘std’: Bootstrap standard deviation
dict [ str , ndarray ]- ‘Z’: Z-scores (mean/std)
dict [ str , ndarray ]- ‘p’: P-values (per tail)
dict [ str , ndarray ]- ‘ci_lower’: Lower confidence bound
dict [ str , ndarray ]- ‘ci_upper’: Upper confidence bound
dict [ str , ndarray ]- ‘samples’: All samples (only if save_samples=True)

Examples:

stats = OnlineBootstrapStats(shape=(100,), save_samples=False)
for _ in range(1000):
    stats.update(np.random.randn(100))
results = stats.get_results()
# results.keys() -> mean, std, Z, p, ci_lower, ci_upper
update
update(sample: np.ndarray) -> None

Update statistics with a new bootstrap sample.

Uses Welford’s algorithm for numerical stability.

Parameters:

NameTypeDescriptionDefault
samplendarrayNew bootstrap sample with shape matching self.shape.required

Methods

circle_shift

circle_shift(data: np.ndarray, shift_amount: int | np.ndarray | None = None, random_state: int | np.random.RandomState | None = None) -> np.ndarray

Circular shift for time-series data.

Performs a circular shift that preserves autocorrelation structure. Useful for permutation tests on autocorrelated time series (e.g., fMRI). For 1D data, shifts by a single amount. For 2D data, shifts each feature (column) independently.

Parameters:

NameTypeDescriptionDefault
datandarrayTime series data, shape (n_samples,) or (n_samples, n_features)required
shift_amountint | ndarray | NoneShift amount(s). If None, random shift is used. For 1D: int specifying shift amount For 2D: array of length n_features with shift per featureNone
random_stateint | RandomState | NoneRandom seed for reproducibility (if shift_amount is None)None

Returns:

TypeDescription
ndarrayCircularly shifted data with same shape as input

Examples:

>>> x = np.array([1, 2, 3, 4, 5])
>>> circle_shift(x, shift_amount=2)
array([4, 5, 1, 2, 3])
>>> X = np.array([[1, 10], [2, 20], [3, 30], [4, 40]])
>>> circle_shift(X, shift_amount=np.array([1, 2]))
array([[ 4, 30],
       [ 1, 40],
       [ 2, 10],
       [ 3, 20]])

correlation_permutation_test

correlation_permutation_test(data1: np.ndarray, data2: np.ndarray, *, n_permute: int = 5000, metric: str = 'pearson', tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None, progress_bar: bool = False) -> dict

Correlation permutation test.

Tests whether the correlation between data1 and data2 is significantly different from zero by randomly permuting data1 and computing correlations.

Assumption: Observations are independent (i.i.d.). For autocorrelated time series, use timeseries_correlation_permutation_test with circle_shift or phase_randomize methods instead.

Parameters:

NameTypeDescriptionDefault
data1ndarrayData to permute - shape (n_samples,) for single feature - shape (n_samples, n_features) for multi-featurerequired
data2ndarrayData to correlate with - shape (n_samples,) for single feature - shape (n_samples, n_features) for multi-featurerequired
n_permuteintNumber of permutations (default: 5000)5000
metricstrCorrelation metric (default: ‘pearson’) - ‘pearson’: Pearson correlation (linear relationships) - ‘spearman’: Spearman rank correlation (monotonic relationships) - ‘kendall’: Kendall tau rank correlation (ordinal association, robust to ties)‘pearson’
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolIf True, return full null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of CPU cores for parallelization (default: -1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloatExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
random_stateintRandom seed for reproducibilityNone
progress_barboolShow a progress bar over permutations (default: False)False

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘correlation’ (float or np.ndarray): Observed correlation(s) - ‘p’ (float or np.ndarray): P-value(s) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True) - ‘device’ (str): Parallelization method used

Examples:

>>> # Single feature (default CPU parallelization)
>>> x = np.random.randn(100)
>>> y = x + np.random.randn(100) * 0.5  # Correlated
>>> result = correlation_permutation_test(x, y, n_permute=5000)
>>> result['correlation']
0.85
>>> result['p']
0.001
>>> # Multi-feature (2D arrays)
>>> data1 = np.random.randn(100, 10)  # 100 samples, 10 features
>>> data2 = data1 + np.random.randn(100, 10) * 0.3  # Correlated
>>> result = correlation_permutation_test(data1, data2, n_permute=5000)
>>> result['correlation'].shape
(10,)
>>> result['p'].shape
(10,)
>>> # GPU acceleration
>>> result = correlation_permutation_test(data1, data2, n_permute=5000, device='gpu')
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): Fastest for large problems with automatic batching

    • Pearson: Fully vectorized across all features (5-20× speedup for multi-feature)

    • Spearman: GPU rank transform (average ties) + vectorized Pearson on ranks

    • Kendall: tie-corrected tau-b via pre-computed pairwise sign tensors; O(n²) memory per permutation, so batches are sized accordingly

  • Single-threaded (device=None): Use for small problems or debugging

  • For multi-feature data, each feature pair tested independently

  • Kendall is O(n^2) complexity, slower than Pearson/Spearman for large samples

distance_correlation

distance_correlation(x: np.ndarray, y: np.ndarray, bias_corrected: bool = True, ttest: bool = False) -> dict

Compute the distance correlation between 2 arrays to test for multivariate dependence (linear or non-linear).

Arrays must match on their first dimension. It’s almost always preferable to compute the bias_corrected version which can also optionally perform a ttest. This ttest operates on a statistic thats ~dcorr^2 and will be also returned.

Explanation: Distance correlation involves computing the normalized covariance of two centered euclidean distance matrices. Each distance matrix is the euclidean distance between rows (if x or y are 2d) or scalars (if x or y are 1d). Each matrix is centered prior to computing the covariance either using double-centering or u-centering, which corrects for bias as the number of dimensions increases. U-centering is almost always preferred in all cases. It also permits inference of the normalized covariance between each distance matrix using a one-tailed directional t-test. (Szekely & Rizzo, 2013). While distance correlation is normally bounded between 0 and 1, u-centering can produce negative estimates, which are never significant.

Validated against the dcor and dcor.ttest functions in the ‘energy’ R package and the dcor.distance_correlation, dcor.udistance_correlation_sqr, and dcor.independence.distance_correlation_t_test functions in the dcor Python package.

Parameters:

NameTypeDescriptionDefault
xndarray1d or 2d numpy array of observations by featuresrequired
yndarray1d or 2d numpy array of observations by featuresrequired
bias_correctedboolif false use double-centering which produces a biased-estimate that converges to 1 as the number of dimensions increase. Otherwise used u-centering to correct this bias. Note this must be True if ttest=True; default TrueTrue
ttestboolperform a ttest using the bias_corrected distance correlation; default FalseFalse

Returns:

NameTypeDescription
resultsdictdictionary of results (correlation, t, p, and df.) Optionally, covariance, x variance, and y variance

Examples:

>>> import numpy as np
>>> x = np.random.randn(20, 3)
>>> y = x + np.random.randn(20, 3) * 0.1  # Strongly correlated
>>> result = distance_correlation(x, y, bias_corrected=True)
>>> 'dcorr' in result
True
>>> 0 <= result['dcorr'] <= 1
True

double_center

double_center(mat: np.ndarray) -> np.ndarray

Double center a 2d array.

Double-centering subtracts row means, column means, and adds the grand mean. This centers both rows and columns around zero.

Parameters:

NameTypeDescriptionDefault
matndarray2d numpy arrayrequired

Returns:

NameTypeDescription
matndarraydouble-centered version of input

Examples:

>>> mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=float)
>>> result = double_center(mat)
>>> np.allclose(result.mean(axis=0), 0)
True
>>> np.allclose(result.mean(axis=1), 0)
True

isc_group_permutation_test

isc_group_permutation_test(group1: np.ndarray, group2: np.ndarray, *, n_permute: int = 5000, summary: Literal['median', 'mean'] = 'median', method: Literal['permute', 'bootstrap'] = 'permute', summary_statistic: Literal['leave-one-out', 'pairwise'] = 'pairwise', ci_percentile: float = 95, tail: int | str = 2, device: Literal['cpu', 'gpu'] | None = 'cpu', n_jobs: int = -1, random_state: int | None = None, return_null: bool = False, progress_bar: bool = False, exclude_self_corr: bool = True, metric: str = 'correlation') -> dict[str, Any]

Compute ISC difference between groups with permutation testing.

Supports both subject-wise permutation and bootstrap methods with efficient CPU-parallel and optional GPU acceleration. Follows the statistical methods from Chen et al. (2016) for correct group comparison inference.

Parameters:

NameTypeDescriptionDefault
group1ndarrayFirst group data with one of the following shapes: - (n_observations, n_subjects1): Single feature - (n_observations, n_subjects1, n_voxels): Voxel-wiserequired
group2ndarraySecond group data with one of the following shapes: - (n_observations, n_subjects2): Single feature - (n_observations, n_subjects2, n_voxels): Voxel-wiserequired
n_permuteintNumber of permutations/bootstrap iterations. Defaults to 5000.5000
summaryLiteral [‘median’, ‘mean’]Summary statistic for aggregating ISC values: - ‘median’: Direct median (robust to outliers) - ‘mean’: Fisher z-transformed mean (unbiased averaging) Defaults to ‘median’.‘median’
methodLiteral [‘permute’, ‘bootstrap’]Resampling method for p-value computation: - ‘permute’: Subject-wise permutation (combines groups, permutes labels) - ‘bootstrap’: Subject-wise bootstrap (resamples within each group) Defaults to ‘permute’.‘permute’
summary_statisticLiteral [‘leave-one-out’, ‘pairwise’]ISC computation method: - ‘pairwise’: Average all pairwise correlations - ‘leave-one-out’: Correlate each subject with mean of others Defaults to ‘pairwise’.‘pairwise’
ci_percentilefloatConfidence interval percentile (e.g., 95 for 95% CI). Defaults to 95.95
tailint | strTwo-tailed (2 or ‘two’, default) or one-tailed (1 or ‘one’, positive direction) p-value.2
deviceLiteral [‘cpu’, ‘gpu’] | NoneParallelization method: - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (10-30× speedup for voxel-wise LOO) - None: Single-threaded NumPy (for debugging/small problems) Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = all cores). Only used when device=‘cpu’. Defaults to -1.-1
random_stateint | NoneRandom seed for reproducibility.None
return_nullboolIf True, return null distribution in result dict. Defaults to False.False
progress_barboolShow progress bar during bootstrap/permutation. Defaults to False.False
exclude_self_corrboolMask self-correlations in bootstrap (pairwise only). Defaults to True.True
metricstrSimilarity metric for pairwise ISC computation. See sklearn.metrics.pairwise_distances for valid options. Only applies when summary_statistic=‘pairwise’. Defaults to ‘correlation’.‘correlation’

Returns:

TypeDescription
dict [ str , Any ]Dictionary with the following keys:
dict [ str , Any ]- ‘isc_group_difference’: Observed ISC difference (float or array per voxel)
dict [ str , Any ]- ‘p’: P-value (Phipson-Smyth corrected)
dict [ str , Any ]- ‘ci’: Confidence interval tuple (lower, upper)
dict [ str , Any ]- ‘device’: Parallelization method used
dict [ str , Any ]- ‘null_dist’: (optional) Bootstrap/permutation distribution

Examples:

>>> # Single-feature ISC group comparison
>>> group1 = np.random.randn(100, 10)  # 10 subjects
>>> group2 = np.random.randn(100, 10)
>>> result = isc_group_permutation_test(group1, group2, n_permute=1000)
>>> print(f"ISC difference: {result['isc_group_difference']:.3f}, p: {result['p']:.3f}")
>>> # Voxel-wise ISC group comparison with GPU acceleration
>>> group1_voxels = np.random.randn(100, 10, 5000)  # 5K voxels
>>> group2_voxels = np.random.randn(100, 10, 5000)
>>> result = isc_group_permutation_test(
...     group1_voxels,
...     group2_voxels,
...     summary_statistic='leave-one-out',
...     device='gpu',  # GPU for LOO computation
...     n_permute=5000
... )
>>> print(f"Significant voxels: {(result['p'] < 0.05).sum()}")
References

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Notes
  • Permutation method combines groups and permutes labels (Chen et al. 2016)

  • Bootstrap method resamples subjects within each group independently

  • Bootstrap distribution is centered by subtracting observed difference

  • GPU acceleration available for voxel-wise LOO computation

isc_permutation_test

isc_permutation_test(data: np.ndarray, *, n_permute: int = 5000, summary: Literal['median', 'mean'] = 'median', summary_statistic: Literal['leave-one-out', 'pairwise'] = 'pairwise', method: Literal['bootstrap', 'circle_shift', 'phase_randomize'] = 'bootstrap', ci_percentile: float = 95, tail: int | str = 2, return_null: bool = False, progress_bar: bool = False, exclude_self_corr: bool = True, metric: str = 'correlation', device: Literal['cpu', 'gpu'] | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict[str, Any]

Compute intersubject correlation with permutation testing.

Supports both leave-one-out and pairwise ISC computation modes with GPU acceleration for large voxel-wise problems and CPU-parallel bootstrap resampling.

Parameters:

NameTypeDescriptionDefault
datandarrayData array with one of the following shapes: - (n_observations, n_subjects): Single feature ISC - (n_observations, n_subjects, n_voxels): Voxel-wise ISCrequired
n_permuteintNumber of bootstrap iterations or permutations. Defaults to 5000.5000
summaryLiteral [‘median’, ‘mean’]Summary statistic to aggregate ISC values. - ‘median’: Direct median (robust to outliers) - ‘mean’: Fisher z-transformed mean (unbiased averaging) Defaults to ‘median’.‘median’
summary_statisticLiteral [‘leave-one-out’, ‘pairwise’]ISC computation method. Options: - ‘leave-one-out’: Correlate each subject with mean of others. O(n_subjects), unbiased, recommended by Chen et al. 2016. - ‘pairwise’: Average all pairwise correlations. O(n_subjects²), captures full correlation structure. Note: These methods are statistically different and monotonically but non-linearly related (see Chen et al. 2016, Figure 3). Defaults to ‘pairwise’.‘pairwise’
methodLiteral [‘bootstrap’, ‘circle_shift’, ‘phase_randomize’]Resampling method for p-value computation: - ‘bootstrap’: Subject-wise bootstrap (default, Chen et al. 2016) - ‘circle_shift’: Circular time-series shift (preserves autocorrelation) - ‘phase_randomize’: FFT phase randomization (preserves power spectrum) Defaults to ‘bootstrap’.‘bootstrap’
ci_percentilefloatConfidence interval percentile (e.g., 95 for 95% CI). Defaults to 95.95
tailint | strTwo-tailed (2 or ‘two’, default) or one-tailed (1 or ‘one’, positive direction) p-value.2
return_nullboolIf True, return bootstrap/permutation distribution in result dict. Defaults to False.False
progress_barboolShow progress bar during bootstrap/permutation. Defaults to False.False
exclude_self_corrboolIf True, mask self-correlations (perfect correlations from duplicate subjects in bootstrap samples) as NaN. If False, include them in the summary statistic. Only applies when method=‘bootstrap’ and summary_statistic=‘pairwise’. Defaults to True.True
metricstrSimilarity metric for pairwise ISC computation. See sklearn.metrics.pairwise_distances for valid options. Only applies when summary_statistic=‘pairwise’. For ‘correlation’, uses optimized np.corrcoef. Other metrics use pairwise_distances. Defaults to ‘correlation’.‘correlation’
deviceLiteral [‘cpu’, ‘gpu’] | NoneParallelization method: - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (10-30× speedup for voxel-wise LOO) - None: Single-threaded NumPy (for debugging/small problems) Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = all cores). Only used when device=‘cpu’. Defaults to -1.-1
max_gpu_memory_gbfloat | NoneGPU working-set budget in GB. For the pairwise GPU bootstrap (device='gpu', summary_statistic='pairwise', method='bootstrap') this bounds the (perm_batch, voxel_chunk, n_subjects, n_subjects) resample tensor, chunking voxels and permutations to fit — so whole-brain runs stay within budget. Not used by the LOO or surrogate (circle_shift/phase_randomize) paths. Defaults to 4.None
random_stateint | NoneRandom seed for reproducibility.None

Returns:

TypeDescription
dict [ str , Any ]Dictionary with the following keys:
dict [ str , Any ]- ‘isc’: Observed ISC value (float or array per voxel)
dict [ str , Any ]- ‘p’: P-value (Phipson-Smyth corrected)
dict [ str , Any ]- ‘ci’: Confidence interval tuple (lower, upper)
dict [ str , Any ]- ‘device’: Parallelization method used
dict [ str , Any ]- ‘null_dist’: (optional) Bootstrap/permutation distribution

Examples:

>>> # Single-feature ISC
>>> data = np.random.randn(100, 10)  # 100 timepoints, 10 subjects
>>> result = isc_permutation_test(data, n_permute=1000)
>>> print(f"ISC: {result['isc']:.3f}, p: {result['p']:.3f}")
>>> # Voxel-wise ISC with GPU acceleration
>>> data_voxels = np.random.randn(100, 50, 5000)  # 5K voxels
>>> result = isc_permutation_test(
...     data_voxels,
...     summary_statistic='leave-one-out',
...     device='gpu',  # GPU for LOO computation
...     n_permute=5000
... )
>>> print(f"Significant voxels: {(result['p'] < 0.05).sum()}")
>>> # Compare LOO vs pairwise
>>> result_loo = isc_permutation_test(data, summary_statistic='leave-one-out')
>>> result_pair = isc_permutation_test(data, summary_statistic='pairwise')
>>> print(f"LOO: {result_loo['isc']:.3f}, Pairwise: {result_pair['isc']:.3f}")
References

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Notes
  • Leave-one-out is 20-30× faster than pairwise for large n_subjects

  • GPU acceleration helps most for voxel-wise LOO (10-30× speedup)

  • Pairwise bootstrap uses correct subject-wise resampling (Chen 2016)

  • Bootstrap distribution is centered by subtracting observed ISC

matrix_permutation_test

matrix_permutation_test(data1: np.ndarray, data2: np.ndarray, *, n_permute: int = 5000, metric: str = 'pearson', how: str = 'upper', include_diag: bool = False, tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, random_state: int | None = None, progress_bar: bool = False) -> dict

Matrix permutation test (Mantel test) for correlating two square matrices.

Tests whether the correlation between elements of two matrices is significant by permuting rows and columns of one matrix symmetrically while keeping the other fixed.

Statistical Method: For each permutation, create random permutation perm, then apply: matrix1[perm][:, perm]. This preserves matrix structure while destroying correlation. Count how often permuted correlation is as extreme as observed.

Assumptions:

Parameters:

NameTypeDescriptionDefault
data1ndarrayFirst square matrix (n×n)required
data2ndarraySecond square matrix (n×n)required
n_permuteintNumber of permutations (default: 5000)5000
metricstrCorrelation metric [‘pearson’‘spearman’
howstrWhich elements to compare [‘upper’‘lower’
include_diagboolInclude diagonal elements (only applies if how=‘full’) (default: False)False
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolReturn null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup)‘cpu’
n_jobsintNumber of parallel workers, -1 = all cores (default: -1) Only used when device=‘cpu’-1
random_stateintRandom seed for reproducibilityNone
progress_barboolShow a progress bar over permutations (default: False)False

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘correlation’ (float): Observed correlation coefficient - ‘p’ (float): P-value using Phipson-Smyth correction - ‘device’ (str): Parallelization method used (‘cpu’ or None) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True)
References

Chen, G. et al. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Mantel, N. (1967). The detection of disease clustering and a generalized regression approach. Cancer Research, 27(2), 209-220.

Examples:

>>> import numpy as np
>>> from nltools.algorithms.inference import matrix_permutation_test
>>>
>>> # Create two correlated similarity matrices
>>> np.random.seed(42)
>>> n = 50
>>> true_pattern = np.random.randn(n)
>>> data1 = np.corrcoef(true_pattern + np.random.randn(n) * 0.1)
>>> data2 = np.corrcoef(true_pattern + np.random.randn(n) * 0.1)
>>>
>>> # Test if matrices are correlated
>>> result = matrix_permutation_test(data1, data2, n_permute=1000)
>>> print(f"Correlation: {result['correlation']:.3f}, p = {result['p']:.4f}")

one_sample_permutation_test

one_sample_permutation_test(data: np.ndarray, *, n_permute: int = 5000, tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None, progress_bar: bool = False) -> dict

One-sample permutation test using sign-flipping.

Tests whether the mean of data is significantly different from zero by randomly flipping the sign of each observation. This is the permutation test equivalent of a one-sample t-test.

Assumption: Symmetric error distribution around zero. For highly skewed distributions, consider alternative methods (e.g., bootstrap resampling).

Parameters:

NameTypeDescriptionDefault
datandarrayData to test - shape (n_samples,) for single feature - shape (n_samples, n_features) for multi-feature (voxel-wise)required
n_permuteintNumber of permutations (default: 5000)5000
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolIf True, return full null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of CPU cores for parallelization (default: -1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloatExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
random_stateintRandom seed for reproducibilityNone
progress_barboolWhether to display a progress bar (default: False)False

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘mean’ (float or np.ndarray): Observed mean(s) - ‘p’ (float or np.ndarray): P-value(s) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True) - ‘device’ (str): Parallelization method used

Examples:

>>> # Single feature (default CPU parallelization)
>>> data = np.random.randn(30)
>>> result = one_sample_permutation_test(data, n_permute=5000)
>>> result['p']
0.23
>>> # Voxel-wise test with GPU
>>> data = np.random.randn(30, 10000)  # 30 subjects, 10K voxels
>>> result = one_sample_permutation_test(data, n_permute=5000, device='gpu')
>>> result['mean'].shape
(10000,)
>>> result['p'].shape
(10000,)
>>> # Single-threaded (for debugging)
>>> result = one_sample_permutation_test(data, n_permute=5000, device=None)
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): Fastest for large problems with automatic batching

  • Single-threaded (device=None): Use for small problems or debugging

  • For voxel-wise tests, each voxel tested independently

  • Progress bars show completion for both CPU parallel and GPU batched modes

phase_randomize

phase_randomize(data: np.ndarray, *, device: str | None = 'cpu', random_state: int | np.random.RandomState | None = None) -> np.ndarray

FFT-based phase randomization for time-series data.

Preserves the power spectrum (autocorrelation) but destroys nonlinear temporal structure by randomizing Fourier phases. Used to test whether data was generated by a linear Gaussian process or contains nonlinear dynamics.

Algorithm
  1. Compute FFT of input signal

  2. Generate random phases [0, 2π] for positive frequencies

  3. Apply phase shifts to positive frequencies: multiply by exp(i*φ)

  4. Apply conjugate phase shifts to negative frequencies (for real output)

  5. Compute inverse FFT to get phase-randomized signal

Parameters:

NameTypeDescriptionDefault
datandarrayTime series data, shape (n_samples,) or (n_samples, n_features)required
devicestr | NoneCompute device. - ‘cpu’ / None: NumPy FFT (default, float64 precision) - ‘gpu’: PyTorch FFT on CUDA/MPS (float32 precision, 5-20× faster for large data) - ‘auto’: use a GPU if present, else CPU‘cpu’
random_stateint | RandomState | NoneRandom seed for reproducibilityNone

Returns:

TypeDescription
ndarrayPhase-randomized data with same shape as input
Notes
  • CRITICAL: Preserves power spectrum exactly (within numerical precision)

  • Precision: the CPU path uses float64, the GPU path float32

  • Conjugate symmetry is maintained for real-valued output

Examples:

>>> x = np.sin(np.linspace(0, 10*np.pi, 100))  # Sine wave
>>> x_rand = phase_randomize(x, random_state=42)
>>> # Power spectrum preserved:
>>> np.allclose(np.abs(np.fft.rfft(x))**2, np.abs(np.fft.rfft(x_rand))**2)
True
>>> # GPU acceleration for large datasets:
>>> x_large = np.random.randn(10000)
>>> x_rand_gpu = phase_randomize(x_large, device='gpu', random_state=42)

timeseries_correlation_permutation_test

timeseries_correlation_permutation_test(data1: np.ndarray, data2: np.ndarray, *, method: Literal['circle_shift', 'phase_randomize'] = 'circle_shift', n_permute: int = 5000, metric: Literal['pearson', 'spearman', 'kendall'] = 'pearson', tail: int | str = 2, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, return_null: bool = False, random_state: int | np.random.RandomState | None = None, progress_bar: bool = False) -> dict

Time-series correlation permutation test.

Unlike standard permutation tests that shuffle data independently, this test uses time-series-aware permutation methods that preserve temporal structure (circle_shift) or power spectrum (phase_randomize).

Use this test when data contains temporal autocorrelation. Standard permutation tests inflate Type I error for autocorrelated data.

Parameters:

NameTypeDescriptionDefault
data1ndarrayFirst time series, shape (n_samples,) or (n_samples, 1)required
data2ndarraySecond time series, shape (n_samples,) or (n_samples, 1)required
methodLiteral [‘circle_shift’, ‘phase_randomize’]Permutation method: - ‘circle_shift’: Circular shift (preserves autocorrelation) - ‘phase_randomize’: FFT-based (preserves power spectrum)‘circle_shift’
n_permuteintNumber of permutations5000
metricLiteral [‘pearson’, ‘spearman’, ‘kendall’]Correlation type (‘pearson’, ‘spearman’, ‘kendall’)‘pearson’
tailint | strTest type (default: 2) - 2 or ‘two’: Two-tailed test (default) - 1 or ‘one’: One-tailed test in the test’s positive direction (to test the negative direction, negate the data / swap groups)2
devicestr | NoneParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of parallel jobs (-1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloat | NoneExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
return_nullboolWhether to return null distributionFalse
random_stateint | RandomState | NoneRandom seed for reproducibilityNone
progress_barboolShow a progress bar over permutations (default: False)False

Returns:

TypeDescription
dictDictionary with keys: - ‘correlation’: Observed correlation coefficient - ‘p’: P-value - ‘null_dist’: (if return_null=True) Null distribution - ‘device’: Parallelization method used

Examples:

>>> x = np.sin(np.linspace(0, 10*np.pi, 100))
>>> y = np.cos(np.linspace(0, 10*np.pi, 100))
>>> result = timeseries_correlation_permutation_test(
...     x, y, method='circle_shift', n_permute=1000, random_state=42
... )
>>> result['correlation']  # Strong negative correlation
-0.999...
>>> result['p'] < 0.05  # Significant
True
>>> # GPU acceleration
>>> result = timeseries_correlation_permutation_test(
...     x, y, method='phase_randomize', device='gpu', n_permute=5000
... )
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): 5-20× faster for large problems (n_samples > 1000)

  • Single-threaded (device=None): Use for small problems or debugging

  • For independent data, use regular correlation_permutation_test

  • circle_shift is faster and suitable for most fMRI time series

  • phase_randomize preserves power spectrum exactly (tests nonlinearity)

  • Only data1 is randomized; data2 remains fixed to test correlation

  • phase_randomize benefits most from GPU (FFT acceleration)

two_sample_permutation_test

two_sample_permutation_test(data1: np.ndarray, data2: np.ndarray, *, n_permute: int = 5000, tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None, progress_bar: bool = False) -> dict

Two-sample permutation test using group label shuffling.

Tests whether two independent groups have different means by randomly permuting group labels. This is the permutation test equivalent of an independent samples t-test.

Assumption: Exchangeability under the null hypothesis (group assignments are arbitrary). Valid for independent samples from similar distributions.

Parameters:

NameTypeDescriptionDefault
data1ndarrayGroup 1 data - shape (n_samples1,) for single feature - shape (n_samples1, n_features) for multi-feature (voxel-wise)required
data2ndarrayGroup 2 data - shape (n_samples2,) for single feature - shape (n_samples2, n_features) for multi-feature (voxel-wise)required
n_permuteintNumber of permutations (default: 5000)5000
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolIf True, return full null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of CPU cores for parallelization (default: -1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloatExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
random_stateintRandom seed for reproducibilityNone

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘mean_diff’ (float or np.ndarray): Observed mean difference (data1 - data2) - ‘p’ (float or np.ndarray): P-value(s) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True) - ‘device’ (str): Parallelization method used

Examples:

>>> # Single feature (default CPU parallelization)
>>> data1 = np.random.randn(20)  # Group 1: 20 subjects
>>> data2 = np.random.randn(25)  # Group 2: 25 subjects
>>> result = two_sample_permutation_test(data1, data2, n_permute=5000)
>>> result['p']
0.45
>>> # Voxel-wise test with GPU
>>> data1 = np.random.randn(20, 10000)  # 20 subjects, 10K voxels
>>> data2 = np.random.randn(25, 10000)  # 25 subjects, 10K voxels
>>> result = two_sample_permutation_test(data1, data2, n_permute=5000, device='gpu')
>>> result['mean_diff'].shape
(10000,)
>>> result['p'].shape
(10000,)
>>> # Single-threaded (for debugging)
>>> result = two_sample_permutation_test(data1, data2, n_permute=5000, device=None)
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): Fastest for large problems with automatic batching

  • Single-threaded (device=None): Use for small problems or debugging

  • For voxel-wise tests, each voxel tested independently

  • Group sizes can be unequal

u_center

u_center(mat: np.ndarray) -> np.ndarray

U-center a 2d array. U-centering is a bias-corrected form of double-centering.

U-centering corrects for bias that occurs with double-centering as the number of dimensions increases. The diagonal is explicitly set to zero.

Parameters:

NameTypeDescriptionDefault
matndarray2d numpy arrayrequired

Returns:

NameTypeDescription
matndarrayu-centered version of input

Examples:

>>> mat = np.random.randn(5, 5)
>>> result = u_center(mat)
>>> np.allclose(np.diag(result), 0)
True

Modules

bootstrap

Bootstrap inference utilities with CPU/GPU support.

Attributes:

NameTypeDescription
FITTED_METHODS
SIMPLE_METHODS

Classes:

NameDescription
OnlineBootstrapStatsMemory-efficient online statistics aggregator for bootstrap samples.

Classes

OnlineBootstrapStats
OnlineBootstrapStats(shape: tuple[int, ...], save_samples: bool = False, percentiles: tuple[float, float] = (2.5, 97.5))

Memory-efficient online statistics aggregator for bootstrap samples.

Uses Welford’s algorithm for numerically stable online computation of mean and variance. Optionally stores all samples for exact percentile CIs.

Parameters:

NameTypeDescriptionDefault
shapetuple [ int , ...]Shape of each bootstrap sample.required
save_samplesboolIf True, store all samples for exact percentile confidence intervals. If False, use normal approximation (much more memory efficient). Defaults to False.False
percentilestuple [ float , float ]Percentiles for confidence intervals (e.g., (2.5, 97.5) for 95% CI). Defaults to (2.5, 97.5).(2.5, 97.5)

Attributes:

NameTypeDescription
M2
mean
n
percentiles
samples
save_samples
shape

####### Attributes##

Methods:

NameDescription
get_resultsCompute final bootstrap statistics.
updateUpdate statistics with a new bootstrap sample.

Examples:

>>> stats = OnlineBootstrapStats(shape=(100,), save_samples=False)
>>> for i in range(1000):
...     sample = np.random.randn(100)
...     stats.update(sample)
>>> results = stats.get_results()
>>> print(results.keys())
dict_keys(['mean', 'std', 'Z', 'p', 'ci_lower', 'ci_upper'])
M2
M2 = np.zeros(shape, dtype=(np.float64))

######## mean

mean = np.zeros(shape, dtype=(np.float64))

######## n

n = 0

######## percentiles

percentiles = percentiles

######## samples

samples = [] if save_samples else None

######## save_samples

save_samples = save_samples

######## shape

shape = shape

####### Functions##

get_results
get_results(tail: int | str = 2) -> dict[str, np.ndarray]

Compute final bootstrap statistics.

Parameters:

NameTypeDescriptionDefault
tailint | str2‘two’ (two-tailed, default) or 1

Parameters:

NameTypeDescriptionDefault
samplendarrayNew bootstrap sample with shape matching self.shape.required

Returns:

TypeDescription
dict [ str , ndarray ]Dictionary containing:
dict [ str , ndarray ]- ‘mean’: Bootstrap mean
dict [ str , ndarray ]- ‘std’: Bootstrap standard deviation
dict [ str , ndarray ]- ‘Z’: Z-scores (mean/std)
dict [ str , ndarray ]- ‘p’: P-values (per tail)
dict [ str , ndarray ]- ‘ci_lower’: Lower confidence bound
dict [ str , ndarray ]- ‘ci_upper’: Upper confidence bound
dict [ str , ndarray ]- ‘samples’: All samples (only if save_samples=True)

Examples:

stats = OnlineBootstrapStats(shape=(100,), save_samples=False)
for _ in range(1000):
    stats.update(np.random.randn(100))
results = stats.get_results()
# results.keys() -> mean, std, Z, p, ci_lower, ci_upper

######## update

update(sample: np.ndarray) -> None

Update statistics with a new bootstrap sample.

Uses Welford’s algorithm for numerical stability.

Methods

correlation

Correlation permutation test implementations.

This module provides CPU-parallel and GPU-batched implementations of correlation permutation tests for assessing statistical significance of correlations.

Methods:

NameDescription
correlation_permutation_testCorrelation permutation test.

Classes

Methods

correlation_permutation_test
correlation_permutation_test(data1: np.ndarray, data2: np.ndarray, *, n_permute: int = 5000, metric: str = 'pearson', tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None, progress_bar: bool = False) -> dict

Correlation permutation test.

Tests whether the correlation between data1 and data2 is significantly different from zero by randomly permuting data1 and computing correlations.

Assumption: Observations are independent (i.i.d.). For autocorrelated time series, use timeseries_correlation_permutation_test with circle_shift or phase_randomize methods instead.

Parameters:

NameTypeDescriptionDefault
data1ndarrayData to permute - shape (n_samples,) for single feature - shape (n_samples, n_features) for multi-featurerequired
data2ndarrayData to correlate with - shape (n_samples,) for single feature - shape (n_samples, n_features) for multi-featurerequired
n_permuteintNumber of permutations (default: 5000)5000
metricstrCorrelation metric (default: ‘pearson’) - ‘pearson’: Pearson correlation (linear relationships) - ‘spearman’: Spearman rank correlation (monotonic relationships) - ‘kendall’: Kendall tau rank correlation (ordinal association, robust to ties)‘pearson’
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolIf True, return full null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of CPU cores for parallelization (default: -1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloatExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
random_stateintRandom seed for reproducibilityNone
progress_barboolShow a progress bar over permutations (default: False)False

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘correlation’ (float or np.ndarray): Observed correlation(s) - ‘p’ (float or np.ndarray): P-value(s) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True) - ‘device’ (str): Parallelization method used

Examples:

>>> # Single feature (default CPU parallelization)
>>> x = np.random.randn(100)
>>> y = x + np.random.randn(100) * 0.5  # Correlated
>>> result = correlation_permutation_test(x, y, n_permute=5000)
>>> result['correlation']
0.85
>>> result['p']
0.001
>>> # Multi-feature (2D arrays)
>>> data1 = np.random.randn(100, 10)  # 100 samples, 10 features
>>> data2 = data1 + np.random.randn(100, 10) * 0.3  # Correlated
>>> result = correlation_permutation_test(data1, data2, n_permute=5000)
>>> result['correlation'].shape
(10,)
>>> result['p'].shape
(10,)
>>> # GPU acceleration
>>> result = correlation_permutation_test(data1, data2, n_permute=5000, device='gpu')
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): Fastest for large problems with automatic batching

    • Pearson: Fully vectorized across all features (5-20× speedup for multi-feature)

    • Spearman: GPU rank transform (average ties) + vectorized Pearson on ranks

    • Kendall: tie-corrected tau-b via pre-computed pairwise sign tensors; O(n²) memory per permutation, so batches are sized accordingly

  • Single-threaded (device=None): Use for small problems or debugging

  • For multi-feature data, each feature pair tested independently

  • Kendall is O(n^2) complexity, slower than Pearson/Spearman for large samples

intersubject

Intersubject correlation, functional connectivity, and phase synchrony.

Methods:

NameDescription
iscCompute pairwise intersubject correlation from observations by subjects array.
isc_groupCompute difference in intersubject correlation between groups.
isfcCompute intersubject functional connectivity (ISFC) from a list of observation x feature matrices.
ispsCompute dynamic intersubject phase synchrony (ISPS) from an observations-by-subjects array.

Methods

isc
isc(data, *, n_samples = 5000, summary = 'median', method = 'bootstrap', ci_percentile = 95, exclude_self_corr = True, tail = 2, metric = 'correlation', return_null = False, n_jobs = -1, random_state = None, progress_bar = False)

Compute pairwise intersubject correlation from observations by subjects array.

This function computes pairwise intersubject correlations (ISC) using the median as recommended by Chen et al., 2016). However, if the mean is preferred, we compute the mean correlation after performing the fisher r-to-z transformation and then convert back to correlations to minimize artificially inflating the correlation values.

There are currently three different methods to compute p-values. These include the classic methods for computing permuted time-series by either circle-shifting the data or phase-randomizing the data (see Lancaster et al., 2018). These methods create random surrogate data while preserving the temporal autocorrelation inherent to the signal. By default, we use the subject-wise bootstrap method from Chen et al., 2016. Instead of recomputing the pairwise ISC using circle_shift or phase_randomization methods, this approach uses the computationally more efficient method of bootstrapping the subjects and computing a new pairwise similarity matrix with randomly selected subjects with replacement. If the same subject is selected multiple times, we set the perfect correlation to a nan with (exclude_self_corr=True). We compute the p-values using the percentile method using the same method in Brainiak.

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Hall, P., & Wilson, S. R. (1991). Two guidelines for bootstrap hypothesis testing. Biometrics, 757-762.

Lancaster, G., Iatsenko, D., Pidde, A., Ticcinelli, V., & Stefanovska, A. (2018). Surrogate data for hypothesis testing of physical systems. Physics Reports, 748, 1-60.

This function is a wrapper around isc_permutation_test from the inference module, which provides optimized implementations with CPU-parallel and GPU acceleration support.

Parameters:

NameTypeDescriptionDefault
data(pd.DataFrame, np.array) observations by subjects where isc is computed across subjectsrequired
n_samples(int) number of random samples/bootstraps5000
summary(str) type of isc summary statistic [‘mean’,‘median’] (default: median)‘median’
method(str) method to compute p-values [‘bootstrap’, ‘circle_shift’,‘phase_randomize’] (default: bootstrap)‘bootstrap’
ci_percentile(int) confidence-interval width in percent for the bootstrap CI (default: 95)95
exclude_self_corr(bool) set self-correlations (same subject bootstrapped twice) to nan (default: True)True
tail(intstr) 2
metric(str) pairwise distance metric. See sklearn’s pairwise_distances for valid inputs (default: correlation)‘correlation’
return_null(bool) Return the permutation distribution along with the p-value; default FalseFalse
n_jobs(int) The number of CPUs to use to do the computation. -1 means all CPUs.-1
random_state(int, np.random.RandomState, or None) seed or generator for the resampling; default NoneNone
progress_bar(bool) If True, display a progress bar. Default False.False

Returns:

NameTypeDescription
stats(dict) dictionary of permutation results [‘isc’, ‘p’, ‘ci’, ‘null_dist’]
isc_group
isc_group(group1, group2, *, n_samples = 5000, summary = 'median', method = 'permute', ci_percentile = 95, exclude_self_corr = True, return_null = False, tail = 2, metric = 'correlation', n_jobs = -1, random_state = None, progress_bar = False)

Compute difference in intersubject correlation between groups.

This function computes pairwise intersubject correlations (ISC) using the median as recommended by Chen et al., 2016). However, if the mean is preferred, we compute the mean correlation after performing the fisher r-to-z transformation and then convert back to correlations to minimize artificially inflating the correlation values.

There are currently two different methods to compute p-values. By default, we use the subject-wise permutation method recommended Chen et al., 2016. This method combines the two groups and computes pairwise similarity both within and between the groups. Then the group labels are permuted and the mean difference between the two groups are recomputed to generate a null distribution. The second method uses subject-wise bootstrapping, where a new pairwise similarity matrix with randomly selected subjects with replacement is created separately for each group and the ISC difference between these groups is used to generate a null distribution. If the same subject is selected multiple times, we set the perfect correlation to a nan with (exclude_self_corr=True). We compute the p-values using the percentile method (Hall & Wilson, 1991).

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Hall, P., & Wilson, S. R. (1991). Two guidelines for bootstrap hypothesis testing. Biometrics, 757-762.

This function is a thin wrapper around isc_group_permutation_test from the inference module (which provides optimized CPU parallelization and optional GPU acceleration), pinning the classic pairwise behavior and the n_samples vocabulary.

Parameters:

NameTypeDescriptionDefault
group1(pd.DataFrame, np.array) observations by subjects where isc is computed across subjectsrequired
group2(pd.DataFrame, np.array) observations by subjects where isc is computed across subjectsrequired
n_samples(int) number of samples for permutation or bootstrapping5000
summary(str) type of isc summary statistic [‘mean’,‘median’] (default: median)‘median’
method(str) method to compute p-values [‘permute’, ‘bootstrap’] (default: permute)‘permute’
ci_percentile(float) confidence interval percentile (default: 95)95
exclude_self_corr(bool) exclude self-correlations in bootstrap (default: True)True
return_null(bool) Return the permutation distribution along with the p-value; default FalseFalse
tail(intstr) 2
metric(str) pairwise distance metric. See sklearn’s pairwise_distances for valid inputs (default: correlation)‘correlation’
n_jobs(int) The number of CPUs to use to do the computation. -1 means all CPUs.-1
random_state(int or RandomState) Random seed for reproducibilityNone
progress_bar(bool) If True, display a progress bar. Default False.False

Returns:

NameTypeDescription
stats(dict) dictionary of permutation results with keys: - ‘isc_group_difference’: Observed ISC difference (float or array) - ‘p’: P-value (float or array) - ‘ci’: Confidence interval tuple (lower, upper) - ‘null_dist’: Null distribution (if return_null=True)
isfc
isfc(data, method = 'average', n_jobs = -1)

Compute intersubject functional connectivity (ISFC) from a list of observation x feature matrices.

This function uses the leave one out approach to compute ISFC (Simony et al., 2016). For each subject, compute the cross-correlation between each voxel/roi with the average of the rest of the subjects data. In other words, compute the mean voxel/ROI response for all participants except the target subject. Then compute the correlation between each ROI within the target subject with the mean ROI response in the group average.

Simony, E., Honey, C. J., Chen, J., Lositsky, O., Yeshurun, Y., Wiesel, A., & Hasson, U. (2016). Dynamic reconfiguration of the default mode network during narrative comprehension. Nature communications, 7, 12141.

This function now uses the optimized implementation from the inference module, which provides efficient cross-correlation computation between matrix columns. CPU parallelization is available via joblib when n_jobs > 1 or n_jobs=-1. Each subject’s ISFC computation is independent and can be parallelized efficiently.

Parameters:

NameTypeDescriptionDefault
datalist of subject matrices (observations x voxels/rois)required
methodapproach to computing ISFC. ‘average’ uses leave one out‘average’
n_jobs(int) Number of parallel jobs to use. -1 means all available cores. Default is -1 (parallel execution by default, consistent with other stats functions).-1

Returns:

TypeDescription
list of subject ISFC matrices
isps
isps(data, *, sampling_freq = 0.5, low_cut = 0.04, high_cut = 0.07, order = 5, pairwise = False)

Compute dynamic intersubject phase synchrony (ISPS) from an observations-by-subjects array.

This function computes the instantaneous intersubject phase synchrony for a single voxel/roi timeseries. Requires multiple subjects. This method is largely based on that described by Glerean et al., 2012 and performs a hilbert transform on narrow bandpass filtered timeseries (butterworth) data to get the instantaneous phase angle. The function returns a dictionary containing the average phase angle, the average vector length, and parametric p-values computed using the rayleigh test using circular statistics (Fisher, 1993). If pairwise=True, then it will compute these on the pairwise phase angle differences, if pairwise=False, it will compute these on the actual phase angles. This is called inter-site phase coupling or inter-trial phase coupling respectively in the EEG literatures.

This function requires narrow band filtering your data. As a default we use the recommendations by (Glerean et al., 2012) of .04-.07Hz. This is similar to the “slow-4” band (0.025–0.067 Hz) described by (Zuo et al., 2010; Penttonen & Buzsáki, 2003), but excludes the .03 band, which has been demonstrated to contain aliased respiration signals (Birn, 2006).

Birn RM, Smith MA, Bandettini PA, Diamond JB. 2006. Separating respiratory-variation-related fluctuations from neuronal-activity- related fluctuations in fMRI. Neuroimage 31:1536–1548.

Buzsáki, G., & Draguhn, A. (2004). Neuronal oscillations in cortical networks. Science, 304(5679), 1926-1929.

Fisher, N. I. (1995). Statistical analysis of circular data. cambridge university press.

Glerean, E., Salmi, J., Lahnakoski, J. M., Jääskeläinen, I. P., & Sams, M. (2012). Functional magnetic resonance imaging phase synchronization as a measure of dynamic functional connectivity. Brain connectivity, 2(2), 91-101.

Parameters:

NameTypeDescriptionDefault
data(pd.DataFrame, np.ndarray) observations x subjects datarequired
sampling_freq(float) sampling freqency of data in Hz0.5
low_cut(float) lower bound cutoff for high pass filter0.04
high_cut(float) upper bound cutoff for low pass filter0.07
order(int) filter order for butterworth bandpass5
pairwise(bool) compute phase angle coherence on pairwise phase angle differences or on raw phase angle.False

Returns:

TypeDescription
dictionary with mean phase angle, vector length, and rayleigh statistic

isc

Intersubject Correlation (ISC) with GPU-Accelerated Permutation Testing.

This module provides both leave-one-out (LOO) and pairwise ISC computation with efficient CPU-parallel and GPU-batched implementations. Follows the statistical methods from Chen et al. (2016) for correct bootstrap resampling of correlation matrices.

Key Features
  • Two ISC modes: leave-one-out and pairwise (statistically different)

  • GPU acceleration for voxel-wise computation (10-30× speedup)

  • CPU-parallel bootstrap with joblib

  • Correct subject-wise bootstrap (Chen et al. 2016)

  • Memory-efficient condensed matrix storage

References

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Notes

Leave-one-out and pairwise ISC are monotonically correlated but statistically different. LOO is computationally more efficient and provides unbiased estimates. Pairwise captures full correlation structure but is O(n²) in subjects.

Methods:

NameDescription
isc_group_permutation_testCompute ISC difference between groups with permutation testing.
isc_permutation_testCompute intersubject correlation with permutation testing.

Methods

isc_group_permutation_test
isc_group_permutation_test(group1: np.ndarray, group2: np.ndarray, *, n_permute: int = 5000, summary: Literal['median', 'mean'] = 'median', method: Literal['permute', 'bootstrap'] = 'permute', summary_statistic: Literal['leave-one-out', 'pairwise'] = 'pairwise', ci_percentile: float = 95, tail: int | str = 2, device: Literal['cpu', 'gpu'] | None = 'cpu', n_jobs: int = -1, random_state: int | None = None, return_null: bool = False, progress_bar: bool = False, exclude_self_corr: bool = True, metric: str = 'correlation') -> dict[str, Any]

Compute ISC difference between groups with permutation testing.

Supports both subject-wise permutation and bootstrap methods with efficient CPU-parallel and optional GPU acceleration. Follows the statistical methods from Chen et al. (2016) for correct group comparison inference.

Parameters:

NameTypeDescriptionDefault
group1ndarrayFirst group data with one of the following shapes: - (n_observations, n_subjects1): Single feature - (n_observations, n_subjects1, n_voxels): Voxel-wiserequired
group2ndarraySecond group data with one of the following shapes: - (n_observations, n_subjects2): Single feature - (n_observations, n_subjects2, n_voxels): Voxel-wiserequired
n_permuteintNumber of permutations/bootstrap iterations. Defaults to 5000.5000
summaryLiteral [‘median’, ‘mean’]Summary statistic for aggregating ISC values: - ‘median’: Direct median (robust to outliers) - ‘mean’: Fisher z-transformed mean (unbiased averaging) Defaults to ‘median’.‘median’
methodLiteral [‘permute’, ‘bootstrap’]Resampling method for p-value computation: - ‘permute’: Subject-wise permutation (combines groups, permutes labels) - ‘bootstrap’: Subject-wise bootstrap (resamples within each group) Defaults to ‘permute’.‘permute’
summary_statisticLiteral [‘leave-one-out’, ‘pairwise’]ISC computation method: - ‘pairwise’: Average all pairwise correlations - ‘leave-one-out’: Correlate each subject with mean of others Defaults to ‘pairwise’.‘pairwise’
ci_percentilefloatConfidence interval percentile (e.g., 95 for 95% CI). Defaults to 95.95
tailint | strTwo-tailed (2 or ‘two’, default) or one-tailed (1 or ‘one’, positive direction) p-value.2
deviceLiteral [‘cpu’, ‘gpu’] | NoneParallelization method: - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (10-30× speedup for voxel-wise LOO) - None: Single-threaded NumPy (for debugging/small problems) Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = all cores). Only used when device=‘cpu’. Defaults to -1.-1
random_stateint | NoneRandom seed for reproducibility.None
return_nullboolIf True, return null distribution in result dict. Defaults to False.False
progress_barboolShow progress bar during bootstrap/permutation. Defaults to False.False
exclude_self_corrboolMask self-correlations in bootstrap (pairwise only). Defaults to True.True
metricstrSimilarity metric for pairwise ISC computation. See sklearn.metrics.pairwise_distances for valid options. Only applies when summary_statistic=‘pairwise’. Defaults to ‘correlation’.‘correlation’

Returns:

TypeDescription
dict [ str , Any ]Dictionary with the following keys:
dict [ str , Any ]- ‘isc_group_difference’: Observed ISC difference (float or array per voxel)
dict [ str , Any ]- ‘p’: P-value (Phipson-Smyth corrected)
dict [ str , Any ]- ‘ci’: Confidence interval tuple (lower, upper)
dict [ str , Any ]- ‘device’: Parallelization method used
dict [ str , Any ]- ‘null_dist’: (optional) Bootstrap/permutation distribution

Examples:

>>> # Single-feature ISC group comparison
>>> group1 = np.random.randn(100, 10)  # 10 subjects
>>> group2 = np.random.randn(100, 10)
>>> result = isc_group_permutation_test(group1, group2, n_permute=1000)
>>> print(f"ISC difference: {result['isc_group_difference']:.3f}, p: {result['p']:.3f}")
>>> # Voxel-wise ISC group comparison with GPU acceleration
>>> group1_voxels = np.random.randn(100, 10, 5000)  # 5K voxels
>>> group2_voxels = np.random.randn(100, 10, 5000)
>>> result = isc_group_permutation_test(
...     group1_voxels,
...     group2_voxels,
...     summary_statistic='leave-one-out',
...     device='gpu',  # GPU for LOO computation
...     n_permute=5000
... )
>>> print(f"Significant voxels: {(result['p'] < 0.05).sum()}")
References

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Notes
  • Permutation method combines groups and permutes labels (Chen et al. 2016)

  • Bootstrap method resamples subjects within each group independently

  • Bootstrap distribution is centered by subtracting observed difference

  • GPU acceleration available for voxel-wise LOO computation

isc_permutation_test
isc_permutation_test(data: np.ndarray, *, n_permute: int = 5000, summary: Literal['median', 'mean'] = 'median', summary_statistic: Literal['leave-one-out', 'pairwise'] = 'pairwise', method: Literal['bootstrap', 'circle_shift', 'phase_randomize'] = 'bootstrap', ci_percentile: float = 95, tail: int | str = 2, return_null: bool = False, progress_bar: bool = False, exclude_self_corr: bool = True, metric: str = 'correlation', device: Literal['cpu', 'gpu'] | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict[str, Any]

Compute intersubject correlation with permutation testing.

Supports both leave-one-out and pairwise ISC computation modes with GPU acceleration for large voxel-wise problems and CPU-parallel bootstrap resampling.

Parameters:

NameTypeDescriptionDefault
datandarrayData array with one of the following shapes: - (n_observations, n_subjects): Single feature ISC - (n_observations, n_subjects, n_voxels): Voxel-wise ISCrequired
n_permuteintNumber of bootstrap iterations or permutations. Defaults to 5000.5000
summaryLiteral [‘median’, ‘mean’]Summary statistic to aggregate ISC values. - ‘median’: Direct median (robust to outliers) - ‘mean’: Fisher z-transformed mean (unbiased averaging) Defaults to ‘median’.‘median’
summary_statisticLiteral [‘leave-one-out’, ‘pairwise’]ISC computation method. Options: - ‘leave-one-out’: Correlate each subject with mean of others. O(n_subjects), unbiased, recommended by Chen et al. 2016. - ‘pairwise’: Average all pairwise correlations. O(n_subjects²), captures full correlation structure. Note: These methods are statistically different and monotonically but non-linearly related (see Chen et al. 2016, Figure 3). Defaults to ‘pairwise’.‘pairwise’
methodLiteral [‘bootstrap’, ‘circle_shift’, ‘phase_randomize’]Resampling method for p-value computation: - ‘bootstrap’: Subject-wise bootstrap (default, Chen et al. 2016) - ‘circle_shift’: Circular time-series shift (preserves autocorrelation) - ‘phase_randomize’: FFT phase randomization (preserves power spectrum) Defaults to ‘bootstrap’.‘bootstrap’
ci_percentilefloatConfidence interval percentile (e.g., 95 for 95% CI). Defaults to 95.95
tailint | strTwo-tailed (2 or ‘two’, default) or one-tailed (1 or ‘one’, positive direction) p-value.2
return_nullboolIf True, return bootstrap/permutation distribution in result dict. Defaults to False.False
progress_barboolShow progress bar during bootstrap/permutation. Defaults to False.False
exclude_self_corrboolIf True, mask self-correlations (perfect correlations from duplicate subjects in bootstrap samples) as NaN. If False, include them in the summary statistic. Only applies when method=‘bootstrap’ and summary_statistic=‘pairwise’. Defaults to True.True
metricstrSimilarity metric for pairwise ISC computation. See sklearn.metrics.pairwise_distances for valid options. Only applies when summary_statistic=‘pairwise’. For ‘correlation’, uses optimized np.corrcoef. Other metrics use pairwise_distances. Defaults to ‘correlation’.‘correlation’
deviceLiteral [‘cpu’, ‘gpu’] | NoneParallelization method: - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (10-30× speedup for voxel-wise LOO) - None: Single-threaded NumPy (for debugging/small problems) Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = all cores). Only used when device=‘cpu’. Defaults to -1.-1
max_gpu_memory_gbfloat | NoneGPU working-set budget in GB. For the pairwise GPU bootstrap (device='gpu', summary_statistic='pairwise', method='bootstrap') this bounds the (perm_batch, voxel_chunk, n_subjects, n_subjects) resample tensor, chunking voxels and permutations to fit — so whole-brain runs stay within budget. Not used by the LOO or surrogate (circle_shift/phase_randomize) paths. Defaults to 4.None
random_stateint | NoneRandom seed for reproducibility.None

Returns:

TypeDescription
dict [ str , Any ]Dictionary with the following keys:
dict [ str , Any ]- ‘isc’: Observed ISC value (float or array per voxel)
dict [ str , Any ]- ‘p’: P-value (Phipson-Smyth corrected)
dict [ str , Any ]- ‘ci’: Confidence interval tuple (lower, upper)
dict [ str , Any ]- ‘device’: Parallelization method used
dict [ str , Any ]- ‘null_dist’: (optional) Bootstrap/permutation distribution

Examples:

>>> # Single-feature ISC
>>> data = np.random.randn(100, 10)  # 100 timepoints, 10 subjects
>>> result = isc_permutation_test(data, n_permute=1000)
>>> print(f"ISC: {result['isc']:.3f}, p: {result['p']:.3f}")
>>> # Voxel-wise ISC with GPU acceleration
>>> data_voxels = np.random.randn(100, 50, 5000)  # 5K voxels
>>> result = isc_permutation_test(
...     data_voxels,
...     summary_statistic='leave-one-out',
...     device='gpu',  # GPU for LOO computation
...     n_permute=5000
... )
>>> print(f"Significant voxels: {(result['p'] < 0.05).sum()}")
>>> # Compare LOO vs pairwise
>>> result_loo = isc_permutation_test(data, summary_statistic='leave-one-out')
>>> result_pair = isc_permutation_test(data, summary_statistic='pairwise')
>>> print(f"LOO: {result_loo['isc']:.3f}, Pairwise: {result_pair['isc']:.3f}")
References

Chen, G., Shin, Y. W., Taylor, P. A., Glen, D. R., Reynolds, R. C., Israel, R. B., & Cox, R. W. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Notes
  • Leave-one-out is 20-30× faster than pairwise for large n_subjects

  • GPU acceleration helps most for voxel-wise LOO (10-30× speedup)

  • Pairwise bootstrap uses correct subject-wise resampling (Chen 2016)

  • Bootstrap distribution is centered by subtracting observed ISC

matrix

Matrix permutation test implementations (Mantel test).

This module provides CPU-parallel implementations of matrix permutation tests for testing correlation between two square matrices, as well as matrix utility functions for distance correlation and matrix centering operations.

Attributes:

NameTypeDescription
MAX_INT

Methods:

NameDescription
distance_correlationCompute the distance correlation between 2 arrays to test for multivariate dependence (linear or non-linear).
double_centerDouble center a 2d array.
matrix_permutation_testMatrix permutation test (Mantel test) for correlating two square matrices.
u_centerU-center a 2d array. U-centering is a bias-corrected form of double-centering.

Methods

distance_correlation
distance_correlation(x: np.ndarray, y: np.ndarray, bias_corrected: bool = True, ttest: bool = False) -> dict

Compute the distance correlation between 2 arrays to test for multivariate dependence (linear or non-linear).

Arrays must match on their first dimension. It’s almost always preferable to compute the bias_corrected version which can also optionally perform a ttest. This ttest operates on a statistic thats ~dcorr^2 and will be also returned.

Explanation: Distance correlation involves computing the normalized covariance of two centered euclidean distance matrices. Each distance matrix is the euclidean distance between rows (if x or y are 2d) or scalars (if x or y are 1d). Each matrix is centered prior to computing the covariance either using double-centering or u-centering, which corrects for bias as the number of dimensions increases. U-centering is almost always preferred in all cases. It also permits inference of the normalized covariance between each distance matrix using a one-tailed directional t-test. (Szekely & Rizzo, 2013). While distance correlation is normally bounded between 0 and 1, u-centering can produce negative estimates, which are never significant.

Validated against the dcor and dcor.ttest functions in the ‘energy’ R package and the dcor.distance_correlation, dcor.udistance_correlation_sqr, and dcor.independence.distance_correlation_t_test functions in the dcor Python package.

Parameters:

NameTypeDescriptionDefault
xndarray1d or 2d numpy array of observations by featuresrequired
yndarray1d or 2d numpy array of observations by featuresrequired
bias_correctedboolif false use double-centering which produces a biased-estimate that converges to 1 as the number of dimensions increase. Otherwise used u-centering to correct this bias. Note this must be True if ttest=True; default TrueTrue
ttestboolperform a ttest using the bias_corrected distance correlation; default FalseFalse

Returns:

NameTypeDescription
resultsdictdictionary of results (correlation, t, p, and df.) Optionally, covariance, x variance, and y variance

Examples:

>>> import numpy as np
>>> x = np.random.randn(20, 3)
>>> y = x + np.random.randn(20, 3) * 0.1  # Strongly correlated
>>> result = distance_correlation(x, y, bias_corrected=True)
>>> 'dcorr' in result
True
>>> 0 <= result['dcorr'] <= 1
True
double_center
double_center(mat: np.ndarray) -> np.ndarray

Double center a 2d array.

Double-centering subtracts row means, column means, and adds the grand mean. This centers both rows and columns around zero.

Parameters:

NameTypeDescriptionDefault
matndarray2d numpy arrayrequired

Returns:

NameTypeDescription
matndarraydouble-centered version of input

Examples:

>>> mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=float)
>>> result = double_center(mat)
>>> np.allclose(result.mean(axis=0), 0)
True
>>> np.allclose(result.mean(axis=1), 0)
True
matrix_permutation_test
matrix_permutation_test(data1: np.ndarray, data2: np.ndarray, *, n_permute: int = 5000, metric: str = 'pearson', how: str = 'upper', include_diag: bool = False, tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, random_state: int | None = None, progress_bar: bool = False) -> dict

Matrix permutation test (Mantel test) for correlating two square matrices.

Tests whether the correlation between elements of two matrices is significant by permuting rows and columns of one matrix symmetrically while keeping the other fixed.

Statistical Method: For each permutation, create random permutation perm, then apply: matrix1[perm][:, perm]. This preserves matrix structure while destroying correlation. Count how often permuted correlation is as extreme as observed.

Assumptions:

Parameters:

NameTypeDescriptionDefault
data1ndarrayFirst square matrix (n×n)required
data2ndarraySecond square matrix (n×n)required
n_permuteintNumber of permutations (default: 5000)5000
metricstrCorrelation metric [‘pearson’‘spearman’
howstrWhich elements to compare [‘upper’‘lower’
include_diagboolInclude diagonal elements (only applies if how=‘full’) (default: False)False
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolReturn null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup)‘cpu’
n_jobsintNumber of parallel workers, -1 = all cores (default: -1) Only used when device=‘cpu’-1
random_stateintRandom seed for reproducibilityNone
progress_barboolShow a progress bar over permutations (default: False)False

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘correlation’ (float): Observed correlation coefficient - ‘p’ (float): P-value using Phipson-Smyth correction - ‘device’ (str): Parallelization method used (‘cpu’ or None) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True)
References

Chen, G. et al. (2016). Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level. NeuroImage, 142, 248-259.

Mantel, N. (1967). The detection of disease clustering and a generalized regression approach. Cancer Research, 27(2), 209-220.

Examples:

>>> import numpy as np
>>> from nltools.algorithms.inference import matrix_permutation_test
>>>
>>> # Create two correlated similarity matrices
>>> np.random.seed(42)
>>> n = 50
>>> true_pattern = np.random.randn(n)
>>> data1 = np.corrcoef(true_pattern + np.random.randn(n) * 0.1)
>>> data2 = np.corrcoef(true_pattern + np.random.randn(n) * 0.1)
>>>
>>> # Test if matrices are correlated
>>> result = matrix_permutation_test(data1, data2, n_permute=1000)
>>> print(f"Correlation: {result['correlation']:.3f}, p = {result['p']:.4f}")
u_center
u_center(mat: np.ndarray) -> np.ndarray

U-center a 2d array. U-centering is a bias-corrected form of double-centering.

U-centering corrects for bias that occurs with double-centering as the number of dimensions increases. The diagonal is explicitly set to zero.

Parameters:

NameTypeDescriptionDefault
matndarray2d numpy arrayrequired

Returns:

NameTypeDescription
matndarrayu-centered version of input

Examples:

>>> mat = np.random.randn(5, 5)
>>> result = u_center(mat)
>>> np.allclose(np.diag(result), 0)
True

one_sample

One-sample permutation test implementations.

This module provides CPU-parallel and GPU-batched implementations of the one-sample permutation test (sign-flipping test).

Methods:

NameDescription
one_sample_permutation_testOne-sample permutation test using sign-flipping.

Classes

Methods

one_sample_permutation_test
one_sample_permutation_test(data: np.ndarray, *, n_permute: int = 5000, tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None, progress_bar: bool = False) -> dict

One-sample permutation test using sign-flipping.

Tests whether the mean of data is significantly different from zero by randomly flipping the sign of each observation. This is the permutation test equivalent of a one-sample t-test.

Assumption: Symmetric error distribution around zero. For highly skewed distributions, consider alternative methods (e.g., bootstrap resampling).

Parameters:

NameTypeDescriptionDefault
datandarrayData to test - shape (n_samples,) for single feature - shape (n_samples, n_features) for multi-feature (voxel-wise)required
n_permuteintNumber of permutations (default: 5000)5000
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolIf True, return full null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of CPU cores for parallelization (default: -1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloatExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
random_stateintRandom seed for reproducibilityNone
progress_barboolWhether to display a progress bar (default: False)False

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘mean’ (float or np.ndarray): Observed mean(s) - ‘p’ (float or np.ndarray): P-value(s) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True) - ‘device’ (str): Parallelization method used

Examples:

>>> # Single feature (default CPU parallelization)
>>> data = np.random.randn(30)
>>> result = one_sample_permutation_test(data, n_permute=5000)
>>> result['p']
0.23
>>> # Voxel-wise test with GPU
>>> data = np.random.randn(30, 10000)  # 30 subjects, 10K voxels
>>> result = one_sample_permutation_test(data, n_permute=5000, device='gpu')
>>> result['mean'].shape
(10000,)
>>> result['p'].shape
(10000,)
>>> # Single-threaded (for debugging)
>>> result = one_sample_permutation_test(data, n_permute=5000, device=None)
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): Fastest for large problems with automatic batching

  • Single-threaded (device=None): Use for small problems or debugging

  • For voxel-wise tests, each voxel tested independently

  • Progress bars show completion for both CPU parallel and GPU batched modes

timeseries

Time-series permutation test implementations.

This module provides GPU-accelerated implementations of time-series permutation tests that preserve temporal structure:

References

Theiler, J., Galdrikian, B., Longtin, A., Eubank, S., & Farmer, J. D. (1991). Testing for nonlinearity in time series: the method of surrogate data (No. LA-UR-91-3343; CONF-9108181-1). Los Alamos National Lab., NM (United States).

Lancaster, G., Iatsenko, D., Pidde, A., Ticcinelli, V., & Stefanovska, A. (2018). Surrogate data for hypothesis testing of physical systems. Physics Reports, 748, 1-60.

Methods:

NameDescription
circle_shiftCircular shift for time-series data.
phase_randomizeFFT-based phase randomization for time-series data.
timeseries_correlation_permutation_testTime-series correlation permutation test.

Classes

Methods

circle_shift
circle_shift(data: np.ndarray, shift_amount: int | np.ndarray | None = None, random_state: int | np.random.RandomState | None = None) -> np.ndarray

Circular shift for time-series data.

Performs a circular shift that preserves autocorrelation structure. Useful for permutation tests on autocorrelated time series (e.g., fMRI). For 1D data, shifts by a single amount. For 2D data, shifts each feature (column) independently.

Parameters:

NameTypeDescriptionDefault
datandarrayTime series data, shape (n_samples,) or (n_samples, n_features)required
shift_amountint | ndarray | NoneShift amount(s). If None, random shift is used. For 1D: int specifying shift amount For 2D: array of length n_features with shift per featureNone
random_stateint | RandomState | NoneRandom seed for reproducibility (if shift_amount is None)None

Returns:

TypeDescription
ndarrayCircularly shifted data with same shape as input

Examples:

>>> x = np.array([1, 2, 3, 4, 5])
>>> circle_shift(x, shift_amount=2)
array([4, 5, 1, 2, 3])
>>> X = np.array([[1, 10], [2, 20], [3, 30], [4, 40]])
>>> circle_shift(X, shift_amount=np.array([1, 2]))
array([[ 4, 30],
       [ 1, 40],
       [ 2, 10],
       [ 3, 20]])
phase_randomize
phase_randomize(data: np.ndarray, *, device: str | None = 'cpu', random_state: int | np.random.RandomState | None = None) -> np.ndarray

FFT-based phase randomization for time-series data.

Preserves the power spectrum (autocorrelation) but destroys nonlinear temporal structure by randomizing Fourier phases. Used to test whether data was generated by a linear Gaussian process or contains nonlinear dynamics.

Algorithm
  1. Compute FFT of input signal

  2. Generate random phases [0, 2π] for positive frequencies

  3. Apply phase shifts to positive frequencies: multiply by exp(i*φ)

  4. Apply conjugate phase shifts to negative frequencies (for real output)

  5. Compute inverse FFT to get phase-randomized signal

Parameters:

NameTypeDescriptionDefault
datandarrayTime series data, shape (n_samples,) or (n_samples, n_features)required
devicestr | NoneCompute device. - ‘cpu’ / None: NumPy FFT (default, float64 precision) - ‘gpu’: PyTorch FFT on CUDA/MPS (float32 precision, 5-20× faster for large data) - ‘auto’: use a GPU if present, else CPU‘cpu’
random_stateint | RandomState | NoneRandom seed for reproducibilityNone

Returns:

TypeDescription
ndarrayPhase-randomized data with same shape as input
Notes
  • CRITICAL: Preserves power spectrum exactly (within numerical precision)

  • Precision: the CPU path uses float64, the GPU path float32

  • Conjugate symmetry is maintained for real-valued output

Examples:

>>> x = np.sin(np.linspace(0, 10*np.pi, 100))  # Sine wave
>>> x_rand = phase_randomize(x, random_state=42)
>>> # Power spectrum preserved:
>>> np.allclose(np.abs(np.fft.rfft(x))**2, np.abs(np.fft.rfft(x_rand))**2)
True
>>> # GPU acceleration for large datasets:
>>> x_large = np.random.randn(10000)
>>> x_rand_gpu = phase_randomize(x_large, device='gpu', random_state=42)
timeseries_correlation_permutation_test
timeseries_correlation_permutation_test(data1: np.ndarray, data2: np.ndarray, *, method: Literal['circle_shift', 'phase_randomize'] = 'circle_shift', n_permute: int = 5000, metric: Literal['pearson', 'spearman', 'kendall'] = 'pearson', tail: int | str = 2, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, return_null: bool = False, random_state: int | np.random.RandomState | None = None, progress_bar: bool = False) -> dict

Time-series correlation permutation test.

Unlike standard permutation tests that shuffle data independently, this test uses time-series-aware permutation methods that preserve temporal structure (circle_shift) or power spectrum (phase_randomize).

Use this test when data contains temporal autocorrelation. Standard permutation tests inflate Type I error for autocorrelated data.

Parameters:

NameTypeDescriptionDefault
data1ndarrayFirst time series, shape (n_samples,) or (n_samples, 1)required
data2ndarraySecond time series, shape (n_samples,) or (n_samples, 1)required
methodLiteral [‘circle_shift’, ‘phase_randomize’]Permutation method: - ‘circle_shift’: Circular shift (preserves autocorrelation) - ‘phase_randomize’: FFT-based (preserves power spectrum)‘circle_shift’
n_permuteintNumber of permutations5000
metricLiteral [‘pearson’, ‘spearman’, ‘kendall’]Correlation type (‘pearson’, ‘spearman’, ‘kendall’)‘pearson’
tailint | strTest type (default: 2) - 2 or ‘two’: Two-tailed test (default) - 1 or ‘one’: One-tailed test in the test’s positive direction (to test the negative direction, negate the data / swap groups)2
devicestr | NoneParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of parallel jobs (-1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloat | NoneExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
return_nullboolWhether to return null distributionFalse
random_stateint | RandomState | NoneRandom seed for reproducibilityNone
progress_barboolShow a progress bar over permutations (default: False)False

Returns:

TypeDescription
dictDictionary with keys: - ‘correlation’: Observed correlation coefficient - ‘p’: P-value - ‘null_dist’: (if return_null=True) Null distribution - ‘device’: Parallelization method used

Examples:

>>> x = np.sin(np.linspace(0, 10*np.pi, 100))
>>> y = np.cos(np.linspace(0, 10*np.pi, 100))
>>> result = timeseries_correlation_permutation_test(
...     x, y, method='circle_shift', n_permute=1000, random_state=42
... )
>>> result['correlation']  # Strong negative correlation
-0.999...
>>> result['p'] < 0.05  # Significant
True
>>> # GPU acceleration
>>> result = timeseries_correlation_permutation_test(
...     x, y, method='phase_randomize', device='gpu', n_permute=5000
... )
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): 5-20× faster for large problems (n_samples > 1000)

  • Single-threaded (device=None): Use for small problems or debugging

  • For independent data, use regular correlation_permutation_test

  • circle_shift is faster and suitable for most fMRI time series

  • phase_randomize preserves power spectrum exactly (tests nonlinearity)

  • Only data1 is randomized; data2 remains fixed to test correlation

  • phase_randomize benefits most from GPU (FFT acceleration)

two_sample

Two-sample permutation test implementations.

This module provides CPU-parallel and GPU-batched implementations of the two-sample permutation test (group permutation test).

Methods:

NameDescription
two_sample_permutation_testTwo-sample permutation test using group label shuffling.

Classes

Methods

two_sample_permutation_test
two_sample_permutation_test(data1: np.ndarray, data2: np.ndarray, *, n_permute: int = 5000, tail: int | str = 2, return_null: bool = False, device: str | None = 'cpu', n_jobs: int = -1, max_gpu_memory_gb: float | None = None, random_state: int | None = None, progress_bar: bool = False) -> dict

Two-sample permutation test using group label shuffling.

Tests whether two independent groups have different means by randomly permuting group labels. This is the permutation test equivalent of an independent samples t-test.

Assumption: Exchangeability under the null hypothesis (group assignments are arbitrary). Valid for independent samples from similar distributions.

Parameters:

NameTypeDescriptionDefault
data1ndarrayGroup 1 data - shape (n_samples1,) for single feature - shape (n_samples1, n_features) for multi-feature (voxel-wise)required
data2ndarrayGroup 2 data - shape (n_samples2,) for single feature - shape (n_samples2, n_features) for multi-feature (voxel-wise)required
n_permuteintNumber of permutations (default: 5000)5000
tailint | strTest type — 2‘two’ (two-tailed, default) or 1
return_nullboolIf True, return full null distribution (default: False)False
devicestrParallelization method (default: ‘cpu’) - None: Single-threaded NumPy (for debugging/small problems) - ‘cpu’: CPU parallelization via joblib (default, 4-8× speedup) - ‘gpu’: GPU acceleration via PyTorch (fastest for large problems)‘cpu’
n_jobsintNumber of CPU cores for parallelization (default: -1 = all cores) Only used when device=‘cpu’-1
max_gpu_memory_gbfloatExplicit GPU memory budget in GB. None (default) measures the device’s available memory. Controls automatic batching to prevent OOM errors. Only used with device=‘gpu’. Larger values allow more permutations per batch but risk OOM on smaller GPUs.None
random_stateintRandom seed for reproducibilityNone

Returns:

NameTypeDescription
dictdictDictionary with keys: - ‘mean_diff’ (float or np.ndarray): Observed mean difference (data1 - data2) - ‘p’ (float or np.ndarray): P-value(s) - ‘null_dist’ (np.ndarray): Null distribution (if return_null=True) - ‘device’ (str): Parallelization method used

Examples:

>>> # Single feature (default CPU parallelization)
>>> data1 = np.random.randn(20)  # Group 1: 20 subjects
>>> data2 = np.random.randn(25)  # Group 2: 25 subjects
>>> result = two_sample_permutation_test(data1, data2, n_permute=5000)
>>> result['p']
0.45
>>> # Voxel-wise test with GPU
>>> data1 = np.random.randn(20, 10000)  # 20 subjects, 10K voxels
>>> data2 = np.random.randn(25, 10000)  # 25 subjects, 10K voxels
>>> result = two_sample_permutation_test(data1, data2, n_permute=5000, device='gpu')
>>> result['mean_diff'].shape
(10000,)
>>> result['p'].shape
(10000,)
>>> # Single-threaded (for debugging)
>>> result = two_sample_permutation_test(data1, data2, n_permute=5000, device=None)
Notes
  • Default (device=‘cpu’): CPU parallelization with joblib (4-8× speedup)

  • GPU parallelization (‘gpu’): Fastest for large problems with automatic batching

  • Single-threaded (device=None): Use for small problems or debugging

  • For voxel-wise tests, each voxel tested independently

  • Group sizes can be unequal

utils

Utility functions for permutation testing.

This module contains shared helper functions used across different permutation test implementations.

Attributes:

NameTypeDescription
EPSILON

Methods

validation

Shared validation utilities for algorithms module.

This module provides common validation functions to reduce code duplication and ensure consistent error handling across the algorithms module.

Usage

These functions are used throughout the algorithms module to validate input parameters. They provide consistent error messages and behavior.

Example: >>> from nltools.algorithms.validation import validate_device_parameter >>> validate_device_parameter(“cpu”) # OK >>> validate_device_parameter(“invalid”) # Raises ValueError

Methods:

NameDescription
validate_array_shapeValidate array dimensionality.
validate_array_shape_rangeValidate array dimensionality is within a range.
validate_bootstrap_dataValidate input data for bootstrapping.
validate_bootstrap_methodValidate bootstrap method name.
validate_device_parameterValidate device parameter.
validate_device_parameter_matrixValidate device parameter for matrix operations.
validate_how_parameterValidate ‘how’ parameter for matrix operations.
validate_metric_parameterValidate metric parameter.
validate_percentilesValidate percentile values for confidence intervals.
validate_same_shapeValidate two arrays have same shape.
validate_shape_compatibilityValidate that X and y have compatible shapes for regression.
validate_square_matrixValidate matrix is square.
validate_tail_parameterValidate the public tail vocabulary and normalize to the internal form.

Methods

validate_array_shape
validate_array_shape(array: np.ndarray, expected_ndim: int, name: str = 'array') -> None

Validate array dimensionality.

Parameters:

NameTypeDescriptionDefault
arrayndarrayArray to validaterequired
expected_ndimintExpected number of dimensionsrequired
namestrName of array for error message‘array’
validate_array_shape_range
validate_array_shape_range(array: np.ndarray, min_ndim: int, max_ndim: int, name: str = 'array') -> None

Validate array dimensionality is within a range.

Parameters:

NameTypeDescriptionDefault
arrayndarrayArray to validaterequired
min_ndimintMinimum number of dimensions (inclusive)required
max_ndimintMaximum number of dimensions (inclusive)required
namestrName of array for error message‘array’
validate_bootstrap_data
validate_bootstrap_data(data: np.ndarray, method: str) -> None

Validate input data for bootstrapping.

Parameters:

NameTypeDescriptionDefault
datandarrayData to validaterequired
methodstrBootstrap methodrequired
validate_bootstrap_method
validate_bootstrap_method(method: str, simple_methods: list[str], fitted_methods: list[str]) -> None

Validate bootstrap method name.

Parameters:

NameTypeDescriptionDefault
methodstrMethod name to validaterequired
simple_methodslist [ str ]List of simple method namesrequired
fitted_methodslist [ str ]List of fitted method namesrequired
validate_device_parameter
validate_device_parameter(device: str | None, *, allow_auto: bool = False) -> None

Validate device parameter.

Parameters:

NameTypeDescriptionDefault
devicestr | NoneDevice parameter value (None, ‘cpu’, or ‘gpu’)required
allow_autoboolAlso accept ‘auto’ (entry points that resolve the device themselves, e.g. phase_randomize)False
validate_device_parameter_matrix
validate_device_parameter_matrix(device: str | None) -> None

Validate device parameter for matrix operations.

Parameters:

NameTypeDescriptionDefault
devicestr | NoneParallel parameter valuerequired
validate_how_parameter
validate_how_parameter(how: str) -> None

Validate ‘how’ parameter for matrix operations.

Parameters:

NameTypeDescriptionDefault
howstrHow parameter valuerequired
validate_metric_parameter
validate_metric_parameter(metric: str, allowed: list[str], name: str = 'metric') -> None

Validate metric parameter.

Parameters:

NameTypeDescriptionDefault
metricstrMetric parameter valuerequired
allowedlist [ str ]List of allowed metric valuesrequired
namestrName of parameter for error message‘metric’
validate_percentiles
validate_percentiles(percentiles: tuple[float, float]) -> None

Validate percentile values for confidence intervals.

Parameters:

NameTypeDescriptionDefault
percentilestuple [ float , float ]Percentile values (lower, upper)required
validate_same_shape
validate_same_shape(array1: np.ndarray, array2: np.ndarray, name1: str = 'array1', name2: str = 'array2') -> None

Validate two arrays have same shape.

Parameters:

NameTypeDescriptionDefault
array1ndarrayFirst arrayrequired
array2ndarraySecond arrayrequired
name1strName of first array for error message‘array1’
name2strName of second array for error message‘array2’
validate_shape_compatibility
validate_shape_compatibility(X: np.ndarray, y: np.ndarray, X_name: str = 'X', y_name: str = 'y') -> None

Validate that X and y have compatible shapes for regression.

Parameters:

NameTypeDescriptionDefault
XndarrayFeature matrixrequired
yndarrayTarget vector or matrixrequired
X_namestrName of X for error message‘X’
y_namestrName of y for error message‘y’
validate_square_matrix
validate_square_matrix(matrix: np.ndarray, name: str = 'matrix') -> None

Validate matrix is square.

Parameters:

NameTypeDescriptionDefault
matrixndarrayMatrix to validaterequired
namestrName of matrix for error message‘matrix’
validate_tail_parameter
validate_tail_parameter(tail: int | str) -> str

Validate the public tail vocabulary and normalize to the internal form.

The public vocabulary (v0.6.0) is deliberately two-valued — the direction of a one-tailed test is fixed by the test’s convention, never chosen from the data (a data-driven direction would silently halve every p-value):

Parameters:

NameTypeDescriptionDefault
tailint | strTail parameter value. Can be: - 2 or ‘two’ (default everywhere): two-tailed test (obs

Returns:

TypeDescription
strNormalized internal tail string: ‘two’ or ‘upper’
Notes

For multiple comparisons correction (FDR, Bonferroni) a fixed direction across all tests is essential — which is exactly why the direction is part of the vocabulary, not the data. See GH #315.