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.

algorithms

algorithms

nltools.algorithms — the functional core of nltools.

Every user-facing statistical function and algorithm is importable flat from here (from nltools.algorithms import fdr, zscore, isc), organized into focused submodules underneath:

Classes:

NameDescription
DetSRMDeterministic Shared Response Model (DetSRM).
HyperAlignmentHyperalignment using iterative Procrustes alignment.
LocalAlignmentLocal (neighborhood-based) functional alignment across subjects.
SRMProbabilistic Shared Response Model (SRM).

Methods:

NameDescription
alignAlign subject data into a common response model.
align_statesAlign state weight maps by minimizing pairwise distance between group states.
calc_bpmCalculate instantaneous BPM from beat to beat interval.
circle_shiftCircular shift for time-series data.
compute_multivariate_similarityCompute multivariate similarity via OLS regression.
compute_similarityCompute similarity between two data arrays.
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.
downsampleDownsample a Polars DataFrame/Series to a new target frequency or number of samples using averaging.
fdrDetermine an FDR threshold for an array of p-values.
find_spikesIdentify spikes (motion artifacts, intensity outliers) in 4D fMRI data.
fisher_r_to_zUse Fisher transformation to convert correlation to z score.
fisher_z_to_rConvert Fisher z back to a correlation coefficient.
glover_dispersion_derivativeImplement the Glover dispersion derivative :term:HRF model.
glover_hrfImplement the Glover :term:HRF model.
glover_time_derivativeImplement the Glover time derivative :term:HRF (dhrf) model.
holm_bonfCompute Holm-Bonferroni-corrected p-values.
iscCompute pairwise intersubject correlation from observations by subjects array.
isc_groupCompute difference in intersubject correlation between groups.
isc_group_permutation_testCompute ISC difference between groups with permutation testing.
isc_permutation_testCompute intersubject correlation with permutation testing.
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.
make_cosine_basisCreate basis functions for a discrete cosine transform.
matrix_permutation_testMatrix permutation test (Mantel test) for correlating two square matrices.
multi_thresholdThreshold test image by multiple p-values from p image.
one_sample_permutation_testOne-sample permutation test using sign-flipping.
phase_randomizeFFT-based phase randomization for time-series data.
procrustes_distanceTest matrix similarity using Procrustes superposition.
regressFit an OLS regression of Y on X.
ridge_cvRidge regression with cross-validation for hyperparameter selection.
ridge_svdSolve ridge regression using Singular Value Decomposition.
spm_dispersion_derivativeImplement the :term:SPM dispersion derivative :term:HRF model.
spm_hrfImplement the :term:SPM :term:HRF model.
spm_time_derivativeImplement the :term:SPM time derivative :term:HRF (dhrf) model.
thresholdThreshold test image by p-value from p image.
timeseries_correlation_permutation_testTime-series correlation permutation test.
transform_pairwiseTransform data into pairs with balanced labels for ranking.
trimTrim a Polars DataFrame/Series by replacing outlier values with NaNs.
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.
upsampleUpsample a Polars DataFrame/Series to a new target frequency or number of samples using interpolation.
winsorizeWinsorize a Polars DataFrame/Series with the largest/lowest value not considered outlier.
zscoreZ-score every column of a Polars or pandas DataFrame/Series.

Modules:

NameDescription
alignmentMulti-subject functional alignment algorithms.
backendsBackend abstraction for CPU/GPU operations.
correctionsMultiple comparison corrections and thresholding.
hrfHemodynamic response functions — re-exported from nilearn.
inferenceGPU-accelerated statistical inference for neuroimaging.
outliersOutlier detection, robust statistics, and data normalization.
procrustesData alignment — SRM, Procrustes, and state alignment.
randomShared random-state utilities for deterministic parallel execution.
regressionStandalone OLS regression on numpy arrays.
ridgeRidge regression algorithms and utilities.
shape_utilsShared shape-manipulation helpers for triangle extraction and symmetric permutation.
signalTemporal signal processing — resampling, filtering, and basis functions.
similaritySimilarity metrics and correlation.

Classes

DetSRM

DetSRM(*, n_iter: int = 10, features: int = 50, rand_seed: int = 0) -> None

Bases: BaseEstimator, TransformerMixin

Deterministic Shared Response Model (DetSRM).

Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform W per subject:

XiWiS,i=1NX_i \approx W_i S, \forall i=1 \dots N

Parameters:

NameTypeDescriptionDefault
n_iterint, default=10Number of iterations to run the algorithm.10
featuresint, default=50Number of features to compute.50
rand_seedint, default=0Seed for initializing the random number generator.0

Attributes:

NameTypeDescription
w_list of array, element i has shape=[voxels_i, features]The orthogonal transforms (mappings) for each subject.
s_array, shape=[features, samples]The shared response.
random_state_RandomStateRandom number generator initialized using rand_seed
Note

The number of voxels may be different between subjects. However, the number of samples must be the same across subjects.

The Deterministic Shared Response Model is approximated using the Block Coordinate Descent (BCD) algorithm proposed in Chen2015.

This is a single node version.

The run-time complexity is O(I (V T K + V K^2)) and the memory complexity is O(V T) with I - the number of iterations, V - the sum of voxels from all subjects, T - the number of samples, K - the number of features (typically, V \gg T \gg K), and N - the number of subjects.

Methods:

NameDescription
fitCompute the Deterministic Shared Response Model.
transformUse the model to transform data to the Shared Response subspace.
transform_subjectTransform a new subject using the existing model.

Examples:

Basic multi-subject DetSRM fitting:

>>> from nltools.algorithms import DetSRM
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit DetSRM with CPU parallelization (default)
>>> detsrm = DetSRM(n_iter=10, features=50)
>>> detsrm.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to shared response space
>>> shared_responses = detsrm.transform(data)
>>>
>>> # Access fitted model components
>>> w = detsrm.w_  # Subject-specific transforms
>>> s = detsrm.s_  # Shared response

Methods

fit
fit(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> DetSRM

Compute the Deterministic Shared Response Model.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples]Each element in the list contains the fMRI data of one subject.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Returns:

NameTypeDescription
selfDetSRMFitted model
transform
transform(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray]

Use the model to transform data to the Shared Response subspace.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples_i]Each element in the list contains the fMRI data of one subject.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Returns:

NameTypeDescription
slist of 2D arrays, element i has shape=[features_i, samples_i]Shared responses from input data (X)
transform_subject
transform_subject(X: np.ndarray) -> np.ndarray

Transform a new subject using the existing model.

The subject is assumed to have received equivalent stimulation.

Parameters:

NameTypeDescriptionDefault
X2D array, shape=[voxels, timepoints]The fMRI data of the new subject.required

Returns:

NameTypeDescription
w2D array, shape=[voxels, features]Orthogonal mapping W_{new} for new subject

HyperAlignment

HyperAlignment(n_iter: int = 2, auto_pad: bool = True) -> None

Bases: BaseEstimator, TransformerMixin

Hyperalignment using iterative Procrustes alignment.

Three-stage iterative process for aligning multi-subject data:

  1. Create initial average template

  2. Refine template through n_iter iterations

  3. Final alignment of all subjects to refined template

This implements the Procrustes-based hyperalignment method commonly used in multi-subject neuroimaging analysis.

Parameters:

NameTypeDescriptionDefault
n_iterint, default=2Number of template refinement iterations (stages 1-2).2
auto_padbool, default=TrueIf True, automatically zero-pad matrices to standardize sizes. If False, caller must ensure all matrices have same dimensions.True

Parameters:

NameTypeDescriptionDefault
n_iterint, default=2Number of template refinement iterations2
auto_padbool, default=TrueWhether to automatically pad matrices to same sizeTrue

Attributes:

NameTypeDescription
w_list of ndarray, element i has shape=[features_i, features]The transformation matrices (rotation + reflection) for each subject.
s_ndarray, shape=[features, samples]The aligned common template (shared response).
disparity_list of floatDisparity (sum of squared differences) for each subject.
scale_list of floatScale factors for each subject.
Note

common_model_ property provides alias for s_ (backward compatibility).

Methods:

NameDescription
fitFit hyperalignment model to data.
transformTransform data to common space using fitted transformations.
transform_subjectAlign a new subject to the common space.

Examples:

Basic multi-subject alignment:

>>> from nltools.algorithms import HyperAlignment
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit hyperalignment with CPU parallelization (default)
>>> hyper = HyperAlignment(n_iter=2)
>>> hyper.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to common space
>>> aligned = hyper.transform(data)
>>>
>>> # Access common template
>>> template = hyper.s_  # or hyper.common_model_
>>>
>>> # Align a new subject
>>> new_subject = np.random.randn(100, 50)
>>> new_transform = hyper.transform_subject(new_subject)
Note

When to use parallel processing:

  • Use parallel="cpu" (default) for datasets with 3+ subjects to speed up pairwise Procrustes operations during template refinement.

  • Use parallel=None for debugging or small datasets (<3 subjects) where parallelization overhead isn’t beneficial.

  • Parallel processing is most beneficial when subjects have many voxels (>10K) and template refinement requires multiple iterations.

References

Haxby, J. V., Guntupalli, J. S., Connolly, A. C., Halchenko, Y. O., Conroy, B. R., Gobbini, M. I., ... & Ramadge, P. J. (2011). A common, high-dimensional model of the representational space in human ventral temporal cortex. Neuron, 72(2), 404-416.

Methods

fit
fit(data: list[np.ndarray], *, parallel: str | None = 'cpu', n_jobs: int = -1) -> HyperAlignment

Fit hyperalignment model to data.

Parameters:

NameTypeDescriptionDefault
datalist of ndarrayList of data matrices, each with shape (n_features, n_samples). Different subjects can have different numbers of features if auto_pad=True.required
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Returns:

NameTypeDescription
selfHyperAlignmentFitted model
transform
transform(data: list[np.ndarray], *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray]

Transform data to common space using fitted transformations.

Parameters:

NameTypeDescriptionDefault
datalist of ndarrayList of data matrices to transform. Should be the same data used for fitting (or have compatible dimensions).required
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Returns:

NameTypeDescription
transformedlist of ndarrayList of transformed data matrices in common space
transform_subject
transform_subject(subject_data: np.ndarray) -> tuple[np.ndarray, np.ndarray, float, float]

Align a new subject to the common space.

Parameters:

NameTypeDescriptionDefault
subject_data( ndarray , shape ( n_features , n_samples ))Data from a new subject to align to the common templaterequired

Returns:

NameTypeDescription
transformedndarrayAligned data in common space
RndarrayTransformation matrix used
disparityfloatAlignment quality (sum of squared differences)
scalefloatScale factor used

LocalAlignment

LocalAlignment(spatial_scale: str = 'searchlight', method: str = 'procrustes', radius_mm: float = 10.0, roi_mask: nib.Nifti1Image | None = None, n_features: int | None = None, n_iter: int = 3, aggregation: str = 'center', parallel: str | None = 'cpu', n_jobs: int = -1, progress_bar: bool = False, n_neighborhoods_batch: int | None = None, max_memory_gb: float | None = None, transforms_: dict[int, list[np.ndarray]] | None = None, template_: dict[int, np.ndarray] | None = None, neighborhoods_: SphereNeighborhoods | dict[int, np.ndarray] | None = None, n_voxels_: int | None = None, mask_: nib.Nifti1Image | None = None, backend_: Backend | None = None) -> None

Local (neighborhood-based) functional alignment across subjects.

Learns alignment transforms within local neighborhoods (searchlight spheres or parcels) and applies center-only aggregation to preserve orthogonality.

Parameters:

NameTypeDescriptionDefault
spatial_scalestrSpatial scale, either ‘searchlight’ (overlapping spheres) or ‘roi’ (non-overlapping parcels). Defaults to ‘searchlight’.‘searchlight’
methodstrAlignment method, one of ‘procrustes’, ‘srm’, or ‘hyperalignment’. Defaults to ‘procrustes’.‘procrustes’
radius_mmfloatSphere radius in millimeters for the searchlight scale. Defaults to 10.0.10.0
roi_maskNifti1Image | NoneParcellation image for the ROI scale. Required if spatial_scale='roi'. Defaults to None.None
n_featuresint | NoneNumber of features for SRM. None uses full Procrustes (preserves dims). Defaults to None.None
n_iterintNumber of iterations for alignment refinement. Defaults to 3.3
aggregationstrAggregation method: ‘center’ (center-only, preserves orthogonality) or ‘all’. Defaults to ‘center’.‘center’
parallelstr | NoneParallelization mode. None runs single-threaded numpy, ‘cpu’ uses joblib CPU parallelization, and ‘gpu’ uses PyTorch. GPU acceleration applies only to method='procrustes'; requesting ‘gpu’ with the ‘srm’ or ‘hyperalignment’ methods raises NotImplementedError (an explicit GPU request never silently runs on CPU). Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of jobs for CPU parallelization. Defaults to -1.-1
progress_barboolWhether to display tqdm progress bars during fit and transform. Defaults to False.False
n_neighborhoods_batchint | NoneNumber of neighborhoods to process per batch on the GPU. None auto-calculates a batch size from max_memory_gb. Defaults to None.None
max_memory_gbfloat | NoneExplicit memory budget (in GB) used to auto-size GPU batches when n_neighborhoods_batch is None. None (default) measures the device’s available memory.None

Attributes:

NameTypeDescription
transforms_dict [ int , list [ ndarray ]]Per-neighborhood transforms. Keys are center voxel indices, values are lists of transform matrices (one per subject).
template_dict [ int , ndarray ]Per-neighborhood templates used for alignment.
neighborhoods_SphereNeighborhoods | dictComputed neighborhoods (searchlight or roi).
n_voxels_intTotal number of voxels in the mask.
mask_Nifti1ImageBrain mask used for fitting.

Methods:

NameDescription
fitFit local alignment on multi-subject data.
fit_transformFit alignment and transform data in one step.
transformApply local transforms to data.

Examples:

>>> import numpy as np
>>> import nibabel as nib
>>> from nltools.algorithms.alignment import LocalAlignment
>>> # Create synthetic multi-subject data (voxels, samples)
>>> data = [np.random.randn(1000, 100) for _ in range(5)]
>>> # Build a mask whose nonzero voxels match the 1000-voxel data
>>> mask = nib.Nifti1Image(np.ones((10, 10, 10), dtype=np.int8), np.eye(4))
>>> la = LocalAlignment(spatial_scale='searchlight', method='procrustes', radius_mm=10.0)
>>> la.fit(data, mask)
>>> aligned = la.transform(data)
Note

Based on Bazeille et al. 2021 “An empirical evaluation of functional alignment using inter-subject decoding”. Center-only aggregation is used to preserve local orthogonality of transforms.

Methods

fit
fit(data: list[np.ndarray], mask: nib.Nifti1Image) -> LocalAlignment

Fit local alignment on multi-subject data.

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples). Subjects can have different numbers of samples - the underlying alignment methods (SRM, HyperAlignment) handle this via zero-padding.required
maskNifti1ImageBrain mask defining the voxel space.required

Returns:

NameTypeDescription
LocalAlignmentLocalAlignmentThe fitted alignment model (self).
fit_transform
fit_transform(data: list[np.ndarray], mask: nib.Nifti1Image) -> list[np.ndarray]

Fit alignment and transform data in one step.

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples).required
maskNifti1ImageBrain mask defining the voxel space.required

Returns:

TypeDescription
list [ ndarray ]list[np.ndarray]: Aligned data for each subject.
transform
transform(data: list[np.ndarray]) -> list[np.ndarray]

Apply local transforms to data.

For the searchlight scale with center-only aggregation: each voxel uses the transform from the neighborhood where it was the center.

For the roi scale: all voxels in each parcel use the same transform.

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples).required

Returns:

TypeDescription
list [ ndarray ]list[np.ndarray]: Aligned data for each subject, each shape (n_voxels, n_samples).

SRM

SRM(*, n_iter: int = 10, features: int = 50, rand_seed: int = 0) -> None

Bases: BaseEstimator, TransformerMixin

Probabilistic Shared Response Model (SRM).

Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform W per subject:

XiWiS,i=1NX_i \approx W_i S, \forall i=1 \dots N

Parameters:

NameTypeDescriptionDefault
n_iterint, default=10Number of iterations to run the algorithm.10
featuresint, default=50Number of features to compute.50
rand_seedint, default=0Seed for initializing the random number generator.0

Attributes:

NameTypeDescription
w_list of array, element i has shape=[voxels_i, features]The orthogonal transforms (mappings) for each subject.
s_array, shape=[features, samples]The shared response.
sigma_s_array, shape=[features, features]The covariance of the shared response Normal distribution.
mu_list of array, element i has shape=[voxels_i]The voxel means over the samples for each subject.
rho2_array, shape=[subjects]The estimated noise variance ρi2\rho_i^2 for each subject
random_state_RandomStateRandom number generator initialized using rand_seed
Note

The number of voxels may be different between subjects. However, the number of samples must be the same across subjects.

The probabilistic Shared Response Model is approximated using the Expectation Maximization (EM) algorithm proposed in Chen2015. The implementation follows the optimizations published in Anderson2016.

This is a single node version.

The run-time complexity is O(I (V T K + V K^2 + K^3)) and the memory complexity is O(V T) with I - the number of iterations, V - the sum of voxels from all subjects, T - the number of samples, and K - the number of features (typically, V \gg T \gg K).

Methods:

NameDescription
fitCompute the probabilistic Shared Response Model.
transformUse the model to transform matrix to Shared Response space.
transform_subjectTransform a new subject using the existing model.

Examples:

Basic multi-subject SRM fitting:

>>> from nltools.algorithms import SRM
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit SRM with CPU parallelization (default)
>>> srm = SRM(n_iter=10, features=50)
>>> srm.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to shared response space
>>> shared_responses = srm.transform(data)
>>>
>>> # Access fitted model components
>>> w = srm.w_  # Subject-specific transforms
>>> s = srm.s_  # Shared response

Methods

fit
fit(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1, pad_samples: bool = True) -> SRM

Compute the probabilistic Shared Response Model.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples]Each element in the list contains the fMRI data of one subject. Subjects can have different numbers of samples if pad_samples=True.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1
pad_samplesboolIf True (default), automatically zero-pad subjects with fewer samples to match the longest subject. This allows fitting SRM on data with unequal numbers of time points across subjects.True

Returns:

NameTypeDescription
selfSRMFitted model
transform
transform(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray | None]

Use the model to transform matrix to Shared Response space.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples_i]Each element in the list contains the fMRI data of one subject. Note that number of voxels and samples can vary across subjects.required
yAny | Nonenot used (as it is unsupervised learning)None
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Returns:

NameTypeDescription
slist of 2D arrays, element i has shape=[features_i, samples_i]Shared responses from input data (X)
transform_subject
transform_subject(X: np.ndarray) -> np.ndarray

Transform a new subject using the existing model.

The subject is assumed to have received equivalent stimulation.

Parameters:

NameTypeDescriptionDefault
X2D array, shape=[voxels, timepoints]The fMRI data of the new subject.required

Returns:

NameTypeDescription
w2D array, shape=[voxels, features]Orthogonal mapping W_{new} for new subject

Methods

align

align(data, method = 'deterministic_srm', n_features = None, axis = 0, *args, **kwargs)

Align subject data into a common response model.

This function is a convenience wrapper around HyperAlignment and SRM classes.

Can be used to hyperalign source data to target data using Hyperalignment from Dartmouth (i.e., procrustes transformation; see nltools.algorithms.procrustes) or Shared Response Model from Princeton (see nltools.algorithms.srm). (see nltools.data.BrainData.align for aligning a single Brain object to another). Common Model is shared response model or centered target data. Transformed data can be back projected to original data using Tranformation matrix. Inputs must be a list of BrainData instances or numpy arrays (observations by features).

Parameters:

NameTypeDescriptionDefault
data(list) A list of BrainData objectsrequired
method(str) alignment method to use [‘probabilistic_srm’,‘deterministic_srm’,‘procrustes’]‘deterministic_srm’
n_features(int) number of features to align to common space. If None then will select number of voxelsNone
axis(int) axis to align on0

Returns:

NameTypeDescription
out(dict) a dictionary containing a list of transformed subject matrices, a list of transformation matrices, the shared response matrix, and the intersubject correlation of the shared responses

Examples:

align_states

align_states(reference, target, *, metric = 'correlation', return_index = False, replace_zero_variance = False)

Align state weight maps by minimizing pairwise distance between group states.

This function uses the Hungarian algorithm for state alignment, which is different from aligning multiple subjects’ data.

Parameters:

NameTypeDescriptionDefault
reference(np.array) reference pattern x state matrixrequired
target(np.array) target pattern x state matrix to align to referencerequired
metric(str) distance metric to use‘correlation’
return_index(bool) return index if True, return remapped data if FalseFalse
replace_zero_variance(bool) transform a vector with zero variance to random numbers from a uniform distribution. Useful for when using correlation as a distance metric to avoid NaNs.False

Returns: If return_index=False (default): target[:, remapping], a single ndarray of the target’s columns reordered to match the reference, oriented pattern x state (same shape as target). If return_index=True: the remapping index array (ndarray) that reorders the target’s state columns.

calc_bpm

calc_bpm(beat_interval, sampling_freq)

Calculate instantaneous BPM from beat to beat interval.

Parameters:

NameTypeDescriptionDefault
beat_interval(int) number of samples in between each beat (typically R-R Interval)required
sampling_freq(float) sampling frequency in Hzrequired

Returns:

NameTypeDescription
bpm(float) beats per minute for time interval

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]])

compute_multivariate_similarity

compute_multivariate_similarity(y, X, method = 'ols', tail = 2)

Compute multivariate similarity via OLS regression.

This is the functional core implementation for multivariate similarity computation. Used by BrainData.multivariate_similarity() to delegate computation to the functional core.

Predicts spatial distribution of y from linear combination of X columns. Computes OLS regression statistics including beta coefficients, t-statistics, p-values, and residuals.

Parameters:

NameTypeDescriptionDefault
yndarrayTarget data, shape (n_features,) - single imagerequired
XndarrayPredictor data, shape (n_features, n_predictors) where first column should be intercept (ones) if intercept is desired. If X does not include intercept, an intercept will be added automatically.required
methodstrRegression method (currently only ‘ols’ supported)‘ols’

Returns:

NameTypeDescription
dictDictionary with keys: - ‘beta’: Regression coefficients including intercept, shape (n_predictors+1,) - ‘t’: t-statistics, shape (n_predictors+1,) - ‘p’: p-values, shape (n_predictors+1,) - ‘df’: Degrees of freedom (int) - ‘sigma’: Residual standard deviation (float) - ‘residual’: Residuals, shape (n_features,)

Examples:

>>> y = np.random.randn(100)
>>> X = np.random.randn(100, 5)
>>> result = compute_multivariate_similarity(y, X, method='ols')
>>> 'beta' in result
True
>>> result['beta'].shape
(6,)  # 5 predictors + intercept

compute_similarity

compute_similarity(data1, data2, metric = 'correlation')

Compute similarity between two data arrays.

This is the functional core implementation for similarity computation. Used by BrainData.similarity() to delegate computation to the functional core.

Parameters:

NameTypeDescriptionDefault
data1ndarrayFirst data array, shape (n_samples1, n_features)required
data2ndarraySecond data array, shape (n_samples2, n_features)required
metricstrType of similarity metric - ‘correlation’ or ‘pearson’: Pearson correlation - ‘spearman’ or ‘rank_correlation’: Spearman rank correlation - ‘dot_product’: Dot product - ‘cosine’: Cosine similarity‘correlation’

Returns:

TypeDescription
np.ndarray: Similarity matrix or vector - If data1.shape[0] == 1 and data2.shape[0] == 1: scalar - If data1.shape[0] == 1 or data2.shape[0] == 1: 1D array - Otherwise: 2D array shape (n_samples1, n_samples2)

Examples:

>>> data1 = np.random.randn(10, 100)
>>> data2 = np.random.randn(5, 100)
>>> sim = compute_similarity(data1, data2, metric='correlation')
>>> sim.shape
(10, 5)

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

downsample

downsample(data, *, sampling_freq = None, target = None, target_type = 'samples', method = 'mean')

Downsample a Polars DataFrame/Series to a new target frequency or number of samples using averaging.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to downsamplerequired
sampling_freq(float) Sampling frequency of data in hertzNone
target(float) downsampling targetNone
target_typetype of target can be [samples,seconds,hz]‘samples’
method(str) type of downsample method [‘mean’,‘median’], default: mean‘mean’

Returns:

NameTypeDescription
out(pl.DataFrame, pl.Series) downsampled data (same type as input)

fdr

fdr(p, q = 0.05)

Determine an FDR threshold for an array of p-values.

Uses the desired false discovery rate q. Written by Tal Yarkoni.

Parameters:

NameTypeDescriptionDefault
p(np.array) vector of p-valuesrequired
q(float) false discovery rate level0.05

Returns:

NameTypeDescription
fdr_p(float) p-value threshold based on independence or positive dependence

find_spikes

find_spikes(data, global_spike_cutoff = 3, diff_spike_cutoff = 3, *, TR: float | None = None, sampling_freq: float | None = None)

Identify spikes (motion artifacts, intensity outliers) in 4D fMRI data.

Parameters:

NameTypeDescriptionDefault
dataBrainData or nibabel instancerequired
global_spike_cutoff(int, None) cutoff in std-deviations for spikes in the per-TR global signal. None to skip.3
diff_spike_cutoff(int, None) cutoff in std-deviations for spikes in the per-TR mean absolute frame-to-frame difference. None to skip.3
TRfloat | NoneRepetition time in seconds. Sets the returned DesignMatrix’s sampling_freq for downstream .append(...) / .convolve(). Pass exactly one of TR or sampling_freq.None
sampling_freqfloat | NoneSampling frequency in Hz (= 1/TR). See TR.None

Returns:

NameTypeDescription
DesignMatrixone indicator column per detected spike TR, named
.nl_global_spike{n} / .nl_diff_spike{n} in the reserved
namespace for generated columns (see RESERVED_PREFIX), with all
spike columns pre-marked as confounds. The two detectors run
independently, so a single bad volume is routinely caught by both;
those detections are bitwise-identical one-hot columns, and only one
is kept (the .nl_global_spike* name, a deterministic tie-break —
the column values are the same either way). Row position is the time
axis (no separate TR index column — that was a pandas-era
artifact). When TR / sampling_freq aren’t provided the DM has
sampling_freq=None; you can still .append() it onto a DM that
does have one.

fisher_r_to_z

fisher_r_to_z(r)

Use Fisher transformation to convert correlation to z score.

Parameters:

NameTypeDescriptionDefault
rcorrelation coefficient(s)required

Returns:

NameTypeDescription
zFisher z-transformed correlation(s)

fisher_z_to_r

fisher_z_to_r(z)

Convert Fisher z back to a correlation coefficient.

Parameters:

NameTypeDescriptionDefault
zFisher z-transformed value(s)required

Returns:

NameTypeDescription
rcorrelation coefficient(s)

glover_dispersion_derivative

glover_dispersion_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the Glover dispersion derivative :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor in seconds.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response in seconds.

Returns

dhrf : array of shape (length / t_r * oversampling), dtype=float dhrf sampling on the oversampled time grid

Examples

import numpy as np from nilearn.glm.first_level import glover_dispersion_derivative ddhrf = glover_dispersion_derivative( ... t_r=2.0, oversampling=1, time_length=20.0 ... ) np.round(ddhrf, 3).tolist() [0.0, -0.0, -0.373, 0.282, 0.295, -0.04, -0.094, -0.048, -0.017, -0.005]

glover_hrf

glover_hrf(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the Glover :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response.

Returns

hrf : array of shape (length / t_r * oversampling, dtype=float) :term:HRF sampling on the oversampled time grid.

Examples

import numpy as np from nilearn.glm.first_level import glover_hrf hrf = glover_hrf(t_r=2.0, oversampling=1, time_length=20.0) np.round(hrf, 3).tolist() [0.0, 0.0, 0.226, 0.741, 0.5, 0.037, -0.181, -0.176, -0.103, -0.045]

glover_time_derivative

glover_time_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the Glover time derivative :term:HRF (dhrf) model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response.

Returns

dhrf : array of shape (length / t_r), dtype=float dhrf sampling on the provided grid

Examples

import numpy as np from nilearn.glm.first_level import glover_time_derivative dhrf = glover_time_derivative( ... t_r=2.0, oversampling=1, time_length=20.0 ... ) np.round(dhrf, 3).tolist() [0.0, 0.0, 0.267, 0.076, -0.215, -0.168, -0.039, 0.027, 0.033, 0.019]

holm_bonf

holm_bonf(p, alpha = 0.05)

Compute Holm-Bonferroni-corrected p-values.

This step-down procedure applies iteratively less correction to the highest p-values. It is a bit more conservative than FDR, but much more powerful than vanilla Bonferroni correction.

Parameters:

NameTypeDescriptionDefault
p(np.array) vector of p-valuesrequired
alpha(float) alpha level0.05

Returns:

NameTypeDescription
bonf_p(float) p-value threshold based on bonferroni step-down procedure

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)

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

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

make_cosine_basis

make_cosine_basis(nsamples, sampling_freq, filter_length, unit_scale = True, drop = 0)

Create basis functions for a discrete cosine transform.

Based on the implementation in spm_filter and spm_dctmtx because scipy DCT can only apply transforms but not return the basis functions. Like SPM, this does not add a constant (i.e. intercept), but does retain the first basis (i.e. sigmoidal/linear drift).

Parameters:

NameTypeDescriptionDefault
nsamplesintnumber of observations (e.g. TRs)required
sampling_freqfloatsampling frequency in hertz (i.e. 1 / TR)required
filter_lengthintlength of filter in secondsrequired
unit_scaleboolassure that the basis functions are on the normalized range [-1, 1]; default TrueTrue
dropintindex of which early/slow bases to drop if any; default is to drop constant (i.e. intercept) like SPM. Unlike SPM, retains first basis (i.e. linear/sigmoidal). Will cumulatively drop bases up to and inclusive of index provided (e.g. 2, drops bases 1 and 2)0

Returns:

NameTypeDescription
outndarraynsamples x number of basis sets numpy array

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}")

multi_threshold

multi_threshold(t_map, p_map, thresh)

Threshold test image by multiple p-values from p image.

Parameters:

NameTypeDescriptionDefault
t_map(BrainData) BrainData instance of statistic metric (e.g., t-statistic, beta, etc)required
p_map(BrainData) BrainData instance of p-valuesrequired
thresh(list) list of p-values to threshold stat imagerequired

Returns:

NameTypeDescription
outThresholded BrainData instance with cumulative map - Positive values indicate how many thresholds were passed for positive stats - Negative values indicate how many thresholds were passed for negative stats
Note

This function provides unique cumulative threshold map functionality:

  • Creates a single map showing which thresholds were passed

  • Different from calling threshold() multiple times (which would give separate images)

  • Useful for visualizing threshold hierarchies

  • nilearn.threshold_img() does not support cumulative multi-threshold maps

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)

procrustes_distance

procrustes_distance(mat1, mat2, *, n_permute = 5000, tail = 2, n_jobs = -1, random_state = None)

Test matrix similarity using Procrustes superposition.

Matrices need to match in size on their first dimension only, as the smaller matrix on the second dimension will be padded with zeros. After aligning two matrices using the Procrustes transformation, use the computed disparity between them (sum of squared error of elements) as a similarity metric. Shuffle the rows of one of the matrices and recompute the disparity to perform inference (Peres-Neto & Jackson, 2001).

Parameters:

NameTypeDescriptionDefault
mat1ndarray2d numpy array; must have same number of rows as mat2required
mat2ndarray1d or 2d numpy array; must have same number of rows as mat1required
n_permuteintnumber of permutation iterations to perform5000
tailint | str2‘two’ (two-tailed, default) or 1
n_jobsintThe number of CPUs to use to do permutation; default -1 (all)-1
random_stateint, np.random.RandomState, or Noneseed or generator for the permutation shuffling; default NoneNone

Returns:

NameTypeDescription
dictresults with keys similarity (float in [0, 1]) and p (permuted p-value)

regress

regress(X, Y, *, method: str = 'ols', stats: str = 'full', tail: int | str = 2)

Fit an OLS regression of Y on X.

Does not add an intercept — include one in X explicitly. If Y is 2D, a separate regression is fit to each column.

Parameters:

NameTypeDescriptionDefault
XDesign matrix, shape (n_samples, n_regressors).required
YResponse, shape (n_samples,) or (n_samples, n_targets).required
methodstrOnly 'ols' is supported in v0.6.0. The legacy 'robust' and 'arma' methods were dropped; use statsmodels or a dedicated package if you need them.‘ols’
statsstr'full' returns the 6-tuple below; 'betas' returns just b; 'tstats' returns (b, t).‘full’
tailint | str2‘two’ (two-tailed, default) or 1

Returns:

NameTypeDescription
tuple(b, se, t, p, df, res) when stats='full':
- b: coefficients
- se: standard errors
- t: t-statistics
- p: p-values (per tail)
- df: residual degrees of freedom
- res: residuals

ridge_cv

ridge_cv(X: np.ndarray, y: np.ndarray, *, alphas: np.ndarray | None = None, cv: int | BaseCrossValidator = 5, fit_intercept: bool = False, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict

Ridge regression with cross-validation for hyperparameter selection.

Performs k-fold cross-validation to select the best alpha parameter, then fits a final model on all data using the selected alpha.

Parameters:

NameTypeDescriptionDefault
XndarrayTraining data features with shape (n_samples, n_features)required
yndarrayTarget values with shape (n_samples,) or (n_samples, n_targets)required
alphasndarrayArray of alpha values to try. If None, uses default range: np.logspace(-2, 4, 20) = [0.01, 0.015, ..., 10000]None
cvint or sklearn CV splitterNumber of folds (int) or an sklearn cross-validator (anything with .split(X) and .get_n_splits(), e.g. KFold(5, shuffle=True) or GroupKFold(8)). Splitters are honored for the actual fold iteration, so leave-one-run-out and shuffled-K-fold give different results from contiguous K-fold. Defaults to 5.5
fit_interceptboolIf True, center X and y on the training mean before fitting and recover the intercept after. The returned coef is on the centered scale; the recovered intercept is returned under the intercept key. Defaults to False.False
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU-only using NumPy (default) - “gpu”: GPU acceleration via PyTorch. Requires torch installed (raises ImportError otherwise); degrades to torch-CPU only when no GPU device is present. Use “auto” for torch-optional CPU fallback. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloatGPU memory budget in GB (only used if parallel=‘gpu’). Defaults to 4.0.None
random_stateintRandom seed (not currently used, kept for consistency). Defaults to None.None

Returns:

NameTypeDescription
dictdictDictionary containing:
- ‘alpha’ (float): Best alpha value selected by CV - ‘coef’ (np.ndarray): Coefficients using best alpha on full dataset - ‘cv_scores’ (np.ndarray): Cross-validation R**2 scores for each fold, alpha, and target with shape (n_folds, n_alphas, n_targets) - ‘backend’ (str): Backend used for computation

Examples:

>>> X = np.random.randn(100, 50)
>>> y = np.random.randn(100)
>>> result = ridge_cv(X, y, cv=3)
>>> result['alpha']  # Best alpha selected
1.0
>>> result['coef'].shape
(50,)
Notes
  • Uses R**2 (coefficient of determination) as the scoring metric

  • For multi-target regression, selects alpha that maximizes mean R**2 across targets

  • parallel=‘gpu’ requires torch installed; with torch present but no GPU device it runs on torch-CPU. It does not fall back to NumPy when torch is absent — use parallel=‘auto’ for that.

ridge_svd

ridge_svd(X: np.ndarray, y: np.ndarray, *, alpha: float = 1.0, parallel: str | None = None, max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> np.ndarray

Solve ridge regression using Singular Value Decomposition.

This function implements ridge regression using SVD, which provides numerical stability and efficiency for high-dimensional problems. The implementation is inspired by the himalaya library.

Algorithm

The ridge regression solution is: beta = (X.T @ X + alpha*I)^(-1) @ X.T @ y

Using SVD of X = U @ diag(s) @ V.T, this becomes: beta = V @ diag(s / (s**2 + alpha)) @ U.T @ y

This formulation avoids explicit matrix inversion and is numerically stable. The shrinkage factor s / (s**2 + alpha) regularizes small singular values.

Performance
  • Time complexity: O(n_samples × n_features × min(n_samples, n_features))

  • Space complexity: O(n_samples × n_features)

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

  • See solve_ridge_cv() for cross-validation with GPU support

Parameters:

NameTypeDescriptionDefault
XndarrayTraining data features with shape (n_samples, n_features)required
yndarrayTarget values with shape (n_samples,) or (n_samples, n_targets). Can be 1D for single-target or 2D for multi-targetrequired
alphafloatRegularization strength. Must be positive. Higher values increase regularization (shrink coefficients toward zero). Defaults to 1.0.1.0
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU-only using NumPy (default) - “gpu”: GPU acceleration via PyTorch. Requires torch installed (raises ImportError otherwise); degrades to torch-CPU only when no GPU device is present. Use “auto” for torch-optional CPU fallback. Defaults to None.None
max_gpu_memory_gbfloatGPU memory budget in GB (only used if parallel=‘gpu’). Defaults to 4.0.None
random_stateintRandom seed (not currently used, kept for consistency). Defaults to None.None

Returns:

TypeDescription
ndarraynp.ndarray: Ridge regression coefficients - shape (n_features,) for single-target regression - shape (n_features, n_targets) for multi-target regression

Examples:

>>> X = np.random.randn(100, 50)
>>> y = np.random.randn(100)
>>> beta = ridge_svd(X, y, alpha=1.0)
>>> beta.shape
(50,)
>>> # Multi-target regression
>>> Y = np.random.randn(100, 5)
>>> beta = ridge_svd(X, Y, alpha=1.0)
>>> beta.shape
(50, 5)
Notes
  • Time complexity: O(n_samples * n_features * min(n_samples, n_features))

  • Space complexity: O(n_samples * n_features)

  • For alpha→0, this reduces to ordinary least squares (OLS). Use alpha=1e-6 for OLS in practice (more numerically stable than alpha=0)

  • Supports both CPU (NumPy) and GPU (PyTorch) backends

  • See nltools.algorithms.ridge.solvers.solve_ridge_cv() for cross-validation

  • See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern

spm_dispersion_derivative

spm_dispersion_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the :term:SPM dispersion derivative :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor in seconds.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response in seconds.

Returns

dhrf : array of shape (length / tr * oversampling), dtype=float dhrf sampling on the oversampled time grid

Examples

import numpy as np from nilearn.glm.first_level import glover_dispersion_derivative ddhrf = glover_dispersion_derivative( ... t_r=2.0, oversampling=1, time_length=20.0 ... ) np.round(ddhrf, 3).tolist() [0.0, -0.0, -0.373, 0.282, 0.295, -0.04, -0.094, -0.048, -0.017, -0.005]

spm_hrf

spm_hrf(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the :term:SPM :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

:term:HRF onset time, in seconds.

Returns

hrf : array of shape (length / t_r * oversampling, dtype=float) :term:HRF sampling on the oversampled time grid

Examples

import numpy as np from nilearn.glm.first_level import spm_hrf hrf = spm_hrf(t_r=2.0, oversampling=1, time_length=20.0) np.round(hrf, 3).tolist() [0.0, 0.0, 0.161, 0.443, 0.335, 0.139, 0.022, -0.028, -0.04, -0.033]

spm_time_derivative

spm_time_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the :term:SPM time derivative :term:HRF (dhrf) model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response in seconds.

Returns

dhrf : array of shape (length / t_r, dtype=float) dhrf sampling on the provided grid

Examples

import numpy as np from nilearn.glm.first_level import spm_time_derivative dhrf = spm_time_derivative(t_r=2.0, oversampling=1, time_length=20.0) np.round(dhrf, 3).tolist() [0.0, 0.0, 0.167, 0.04, -0.091, -0.072, -0.035, -0.013, -0.0, 0.005]

threshold

threshold(stat, p, thr = 0.05, return_mask = False)

Threshold test image by p-value from p image.

Parameters:

NameTypeDescriptionDefault
stat(BrainData) BrainData instance of arbitrary statistic metric (e.g., beta, t, etc)required
p(BrainData) BrainData instance of p-valuesrequired
thr(float) p-value threshold to apply0.05
return_mask(bool) optionally return the thresholding mask; default FalseFalse

Returns:

NameTypeDescription
outThresholded BrainData instance
mask(optional) BrainData instance of thresholding mask if return_mask=True
Note

This function provides unique functionality not available in nilearn:

  • Thresholds stat image based on p-values from separate p-value image

  • Neither nilearn.threshold_img nor BrainData.threshold() support this

  • BrainData.threshold() thresholds based on stat values themselves

  • nilearn.threshold_img() thresholds based on image intensity values

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)

transform_pairwise

transform_pairwise(X, y)

Transform data into pairs with balanced labels for ranking.

Transforms a n-class ranking problem into a two-class classification problem. Subclasses implementing particular strategies for choosing pairs should override this method. In this method, all pairs are choosen, except for those that have the same target value. The output is an array of balanced classes, i.e. there are the same number of -1 as +1

Reference: “Large Margin Rank Boundaries for Ordinal Regression”, R. Herbrich, T. Graepel, K. Obermayer. Authors: Fabian Pedregosa fabian@fseoane.net Alexandre Gramfort alexandre.gramfort@inria.fr

Parameters:

NameTypeDescriptionDefault
X(np.array), shape (n_samples, n_features) The datarequired
y(np.array), shape (n_samples,) or (n_samples, 2) Target labels. If it’s a 2D array, the second column represents the grouping of samples, i.e., samples with different groups will not be considered.required

Returns:

NameTypeDescription
X_trans(np.array), shape (k, n_features) Data as pairs, where k = n_samples * (n_samples-1)) / 2 if grouping values were not passed. If grouping variables exist, then returns values computed for each group.
y_trans(np.array), shape (k,) Output class labels, where classes have values {-1, +1} If y was shape (n_samples, 2), then returns (k, 2) with groups on the second dimension.

trim

trim(data, cutoff = None)

Trim a Polars DataFrame/Series by replacing outlier values with NaNs.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to trimrequired
cutoff(dict) a dictionary with keys {‘std’:[low,high]} or {‘quantile’:[low,high]}None

Returns: out: (pl.DataFrame, pl.Series) trimmed data (same type as input)

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

upsample

upsample(data, *, sampling_freq = None, target = None, target_type = 'samples', method = 'linear')

Upsample a Polars DataFrame/Series to a new target frequency or number of samples using interpolation.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to upsample (Note: will drop non-numeric columns from DataFrame)required
sampling_freqSampling frequency of data in hertzNone
target(float) upsampling targetNone
target_type(str) type of target can be [samples,seconds,hz]‘samples’
method(str) [‘linear’, ‘nearest’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’] where ‘zero’, ‘slinear’, ‘quadratic’ and ‘cubic’ refer to a spline interpolation of zeroth, first, second or third order (default: linear)‘linear’

Returns: upsampled Polars DataFrame or Series (same type as input)

winsorize

winsorize(data, cutoff = None, replace_with_cutoff = True)

Winsorize a Polars DataFrame/Series with the largest/lowest value not considered outlier.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to winsorizerequired
cutoff(dict) a dictionary with keys {‘std’:[low,high]} or {‘quantile’:[low,high]}None
replace_with_cutoff(bool) If True, replace outliers with cutoff. If False, replaces outliers with closest existing values; (default: True)True

Returns: out: (pl.DataFrame, pl.Series) winsorized data (same type as input)

zscore

zscore(data)

Z-score every column of a Polars or pandas DataFrame/Series.

Accepts pandas inputs at the boundary for convenience and converts to Polars internally. Always returns Polars output (DataFrame or Series, matching the input shape).

Parameters:

NameTypeDescriptionDefault
datapl.DataFrame, pl.Series, pd.DataFrame, or pd.Series.required

Returns:

TypeDescription
pl.DataFrame or pl.Series with each column z-scored using sample
standard deviation (ddof=1), matching the input shape.

Modules

alignment

Multi-subject functional alignment algorithms.

This package provides algorithms for aligning functional data across subjects:

Classes:

NameDescription
DetSRMDeterministic Shared Response Model (DetSRM).
HyperAlignmentHyperalignment using iterative Procrustes alignment.
LocalAlignmentLocal (neighborhood-based) functional alignment across subjects.
SRMProbabilistic Shared Response Model (SRM).

Methods:

NameDescription
alignAlign subject data into a common response model.
align_statesAlign state weight maps by minimizing pairwise distance between group states.
procrustes_distanceTest matrix similarity using Procrustes superposition.

Modules:

NameDescription
hyperalignmentHyperAlignment: Multi-subject cortical surface alignment using iterative Procrustes refinement.
localLocalAlignment: Neighborhood-based functional alignment.
procrustesData alignment — SRM, Procrustes, and state alignment.
srmShared Response Model (SRM) for multi-subject fMRI alignment.

Classes

DetSRM
DetSRM(*, n_iter: int = 10, features: int = 50, rand_seed: int = 0) -> None

Bases: BaseEstimator, TransformerMixin

Deterministic Shared Response Model (DetSRM).

Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform W per subject:

XiWiS,i=1NX_i \approx W_i S, \forall i=1 \dots N

Parameters:

NameTypeDescriptionDefault
n_iterint, default=10Number of iterations to run the algorithm.10
featuresint, default=50Number of features to compute.50
rand_seedint, default=0Seed for initializing the random number generator.0

Attributes:

NameTypeDescription
w_list of array, element i has shape=[voxels_i, features]The orthogonal transforms (mappings) for each subject.
s_array, shape=[features, samples]The shared response.
random_state_RandomStateRandom number generator initialized using rand_seed
Note

The number of voxels may be different between subjects. However, the number of samples must be the same across subjects.

The Deterministic Shared Response Model is approximated using the Block Coordinate Descent (BCD) algorithm proposed in Chen2015.

This is a single node version.

The run-time complexity is O(I (V T K + V K^2)) and the memory complexity is O(V T) with I - the number of iterations, V - the sum of voxels from all subjects, T - the number of samples, K - the number of features (typically, V \gg T \gg K), and N - the number of subjects.

Methods:

NameDescription
fitCompute the Deterministic Shared Response Model.
transformUse the model to transform data to the Shared Response subspace.
transform_subjectTransform a new subject using the existing model.

####### Attributes##

Examples:

Basic multi-subject DetSRM fitting:

>>> from nltools.algorithms import DetSRM
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit DetSRM with CPU parallelization (default)
>>> detsrm = DetSRM(n_iter=10, features=50)
>>> detsrm.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to shared response space
>>> shared_responses = detsrm.transform(data)
>>>
>>> # Access fitted model components
>>> w = detsrm.w_  # Subject-specific transforms
>>> s = detsrm.s_  # Shared response
features
features = features

######## n_iter

n_iter = n_iter

######## rand_seed

rand_seed = rand_seed

####### Functions##

fit
fit(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> DetSRM

Compute the Deterministic Shared Response Model.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples]Each element in the list contains the fMRI data of one subject.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples_i]Each element in the list contains the fMRI data of one subject.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
X2D array, shape=[voxels, timepoints]The fMRI data of the new subject.required

Returns:

NameTypeDescription
selfDetSRMFitted model

######## transform

transform(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray]

Use the model to transform data to the Shared Response subspace.

Returns:

NameTypeDescription
slist of 2D arrays, element i has shape=[features_i, samples_i]Shared responses from input data (X)

######## transform_subject

transform_subject(X: np.ndarray) -> np.ndarray

Transform a new subject using the existing model.

The subject is assumed to have received equivalent stimulation.

Returns:

NameTypeDescription
w2D array, shape=[voxels, features]Orthogonal mapping W_{new} for new subject
HyperAlignment
HyperAlignment(n_iter: int = 2, auto_pad: bool = True) -> None

Bases: BaseEstimator, TransformerMixin

Hyperalignment using iterative Procrustes alignment.

Three-stage iterative process for aligning multi-subject data:

  1. Create initial average template

  2. Refine template through n_iter iterations

  3. Final alignment of all subjects to refined template

This implements the Procrustes-based hyperalignment method commonly used in multi-subject neuroimaging analysis.

Parameters:

NameTypeDescriptionDefault
n_iterint, default=2Number of template refinement iterations (stages 1-2).2
auto_padbool, default=TrueIf True, automatically zero-pad matrices to standardize sizes. If False, caller must ensure all matrices have same dimensions.True

Parameters:

NameTypeDescriptionDefault
n_iterint, default=2Number of template refinement iterations2
auto_padbool, default=TrueWhether to automatically pad matrices to same sizeTrue

####### Attributes##

Attributes:

NameTypeDescription
w_list of ndarray, element i has shape=[features_i, features]The transformation matrices (rotation + reflection) for each subject.
s_ndarray, shape=[features, samples]The aligned common template (shared response).
disparity_list of floatDisparity (sum of squared differences) for each subject.
scale_list of floatScale factors for each subject.
Note

common_model_ property provides alias for s_ (backward compatibility).

Methods:

NameDescription
fitFit hyperalignment model to data.
transformTransform data to common space using fitted transformations.
transform_subjectAlign a new subject to the common space.

Examples:

Basic multi-subject alignment:

>>> from nltools.algorithms import HyperAlignment
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit hyperalignment with CPU parallelization (default)
>>> hyper = HyperAlignment(n_iter=2)
>>> hyper.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to common space
>>> aligned = hyper.transform(data)
>>>
>>> # Access common template
>>> template = hyper.s_  # or hyper.common_model_
>>>
>>> # Align a new subject
>>> new_subject = np.random.randn(100, 50)
>>> new_transform = hyper.transform_subject(new_subject)
Note

When to use parallel processing:

  • Use parallel="cpu" (default) for datasets with 3+ subjects to speed up pairwise Procrustes operations during template refinement.

  • Use parallel=None for debugging or small datasets (<3 subjects) where parallelization overhead isn’t beneficial.

  • Parallel processing is most beneficial when subjects have many voxels (>10K) and template refinement requires multiple iterations.

References

Haxby, J. V., Guntupalli, J. S., Connolly, A. C., Halchenko, Y. O., Conroy, B. R., Gobbini, M. I., ... & Ramadge, P. J. (2011). A common, high-dimensional model of the representational space in human ventral temporal cortex. Neuron, 72(2), 404-416.

auto_pad
auto_pad = auto_pad

######## common_model_

common_model_

Alias for s_ (common template).

######## n_iter

n_iter = n_iter

####### Functions##

fit
fit(data: list[np.ndarray], *, parallel: str | None = 'cpu', n_jobs: int = -1) -> HyperAlignment

Fit hyperalignment model to data.

Parameters:

NameTypeDescriptionDefault
datalist of ndarrayList of data matrices, each with shape (n_features, n_samples). Different subjects can have different numbers of features if auto_pad=True.required
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
datalist of ndarrayList of data matrices to transform. Should be the same data used for fitting (or have compatible dimensions).required
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
subject_data( ndarray , shape ( n_features , n_samples ))Data from a new subject to align to the common templaterequired

Returns:

NameTypeDescription
selfHyperAlignmentFitted model

######## transform

transform(data: list[np.ndarray], *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray]

Transform data to common space using fitted transformations.

Returns:

NameTypeDescription
transformedlist of ndarrayList of transformed data matrices in common space

######## transform_subject

transform_subject(subject_data: np.ndarray) -> tuple[np.ndarray, np.ndarray, float, float]

Align a new subject to the common space.

Returns:

NameTypeDescription
transformedndarrayAligned data in common space
RndarrayTransformation matrix used
disparityfloatAlignment quality (sum of squared differences)
scalefloatScale factor used
LocalAlignment
LocalAlignment(spatial_scale: str = 'searchlight', method: str = 'procrustes', radius_mm: float = 10.0, roi_mask: nib.Nifti1Image | None = None, n_features: int | None = None, n_iter: int = 3, aggregation: str = 'center', parallel: str | None = 'cpu', n_jobs: int = -1, progress_bar: bool = False, n_neighborhoods_batch: int | None = None, max_memory_gb: float | None = None, transforms_: dict[int, list[np.ndarray]] | None = None, template_: dict[int, np.ndarray] | None = None, neighborhoods_: SphereNeighborhoods | dict[int, np.ndarray] | None = None, n_voxels_: int | None = None, mask_: nib.Nifti1Image | None = None, backend_: Backend | None = None) -> None

Local (neighborhood-based) functional alignment across subjects.

Learns alignment transforms within local neighborhoods (searchlight spheres or parcels) and applies center-only aggregation to preserve orthogonality.

Parameters:

NameTypeDescriptionDefault
spatial_scalestrSpatial scale, either ‘searchlight’ (overlapping spheres) or ‘roi’ (non-overlapping parcels). Defaults to ‘searchlight’.‘searchlight’
methodstrAlignment method, one of ‘procrustes’, ‘srm’, or ‘hyperalignment’. Defaults to ‘procrustes’.‘procrustes’
radius_mmfloatSphere radius in millimeters for the searchlight scale. Defaults to 10.0.10.0
roi_maskNifti1Image | NoneParcellation image for the ROI scale. Required if spatial_scale='roi'. Defaults to None.None
n_featuresint | NoneNumber of features for SRM. None uses full Procrustes (preserves dims). Defaults to None.None
n_iterintNumber of iterations for alignment refinement. Defaults to 3.3
aggregationstrAggregation method: ‘center’ (center-only, preserves orthogonality) or ‘all’. Defaults to ‘center’.‘center’
parallelstr | NoneParallelization mode. None runs single-threaded numpy, ‘cpu’ uses joblib CPU parallelization, and ‘gpu’ uses PyTorch. GPU acceleration applies only to method='procrustes'; requesting ‘gpu’ with the ‘srm’ or ‘hyperalignment’ methods raises NotImplementedError (an explicit GPU request never silently runs on CPU). Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of jobs for CPU parallelization. Defaults to -1.-1
progress_barboolWhether to display tqdm progress bars during fit and transform. Defaults to False.False
n_neighborhoods_batchint | NoneNumber of neighborhoods to process per batch on the GPU. None auto-calculates a batch size from max_memory_gb. Defaults to None.None
max_memory_gbfloat | NoneExplicit memory budget (in GB) used to auto-size GPU batches when n_neighborhoods_batch is None. None (default) measures the device’s available memory.None

Attributes:

NameTypeDescription
transforms_dict [ int , list [ ndarray ]]Per-neighborhood transforms. Keys are center voxel indices, values are lists of transform matrices (one per subject).
template_dict [ int , ndarray ]Per-neighborhood templates used for alignment.
neighborhoods_SphereNeighborhoods | dictComputed neighborhoods (searchlight or roi).
n_voxels_intTotal number of voxels in the mask.
mask_Nifti1ImageBrain mask used for fitting.

Methods:

NameDescription
fitFit local alignment on multi-subject data.
fit_transformFit alignment and transform data in one step.
transformApply local transforms to data.

####### Attributes##

Examples:

>>> import numpy as np
>>> import nibabel as nib
>>> from nltools.algorithms.alignment import LocalAlignment
>>> # Create synthetic multi-subject data (voxels, samples)
>>> data = [np.random.randn(1000, 100) for _ in range(5)]
>>> # Build a mask whose nonzero voxels match the 1000-voxel data
>>> mask = nib.Nifti1Image(np.ones((10, 10, 10), dtype=np.int8), np.eye(4))
>>> la = LocalAlignment(spatial_scale='searchlight', method='procrustes', radius_mm=10.0)
>>> la.fit(data, mask)
>>> aligned = la.transform(data)
Note

Based on Bazeille et al. 2021 “An empirical evaluation of functional alignment using inter-subject decoding”. Center-only aggregation is used to preserve local orthogonality of transforms.

aggregation
aggregation: str = 'center'

######## backend_

backend_: Backend | None = field(default=None, repr=False)

######## mask_

mask_: nib.Nifti1Image | None = field(default=None, repr=False)

######## max_memory_gb

max_memory_gb: float | None = None

######## method

method: str = 'procrustes'

######## n_features

n_features: int | None = None

######## n_iter

n_iter: int = 3

######## n_jobs

n_jobs: int = -1

######## n_neighborhoods_batch

n_neighborhoods_batch: int | None = None

######## n_voxels_

n_voxels_: int | None = field(default=None, repr=False)

######## neighborhoods_

neighborhoods_: SphereNeighborhoods | dict[int, np.ndarray] | None = field(default=None, repr=False)

######## parallel

parallel: str | None = 'cpu'

######## progress_bar

progress_bar: bool = False

######## radius_mm

radius_mm: float = 10.0

######## roi_mask

roi_mask: nib.Nifti1Image | None = None

######## spatial_scale

spatial_scale: str = 'searchlight'

######## template_

template_: dict[int, np.ndarray] | None = field(default=None, repr=False)

######## transforms_

transforms_: dict[int, list[np.ndarray]] | None = field(default=None, repr=False)

####### Functions##

fit
fit(data: list[np.ndarray], mask: nib.Nifti1Image) -> LocalAlignment

Fit local alignment on multi-subject data.

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples). Subjects can have different numbers of samples - the underlying alignment methods (SRM, HyperAlignment) handle this via zero-padding.required
maskNifti1ImageBrain mask defining the voxel space.required

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples).required
maskNifti1ImageBrain mask defining the voxel space.required

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples).required

Returns:

NameTypeDescription
LocalAlignmentLocalAlignmentThe fitted alignment model (self).

######## fit_transform

fit_transform(data: list[np.ndarray], mask: nib.Nifti1Image) -> list[np.ndarray]

Fit alignment and transform data in one step.

Returns:

TypeDescription
list [ ndarray ]list[np.ndarray]: Aligned data for each subject.

######## transform

transform(data: list[np.ndarray]) -> list[np.ndarray]

Apply local transforms to data.

For the searchlight scale with center-only aggregation: each voxel uses the transform from the neighborhood where it was the center.

For the roi scale: all voxels in each parcel use the same transform.

Returns:

TypeDescription
list [ ndarray ]list[np.ndarray]: Aligned data for each subject, each shape (n_voxels, n_samples).
SRM
SRM(*, n_iter: int = 10, features: int = 50, rand_seed: int = 0) -> None

Bases: BaseEstimator, TransformerMixin

Probabilistic Shared Response Model (SRM).

Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform W per subject:

XiWiS,i=1NX_i \approx W_i S, \forall i=1 \dots N

Parameters:

NameTypeDescriptionDefault
n_iterint, default=10Number of iterations to run the algorithm.10
featuresint, default=50Number of features to compute.50
rand_seedint, default=0Seed for initializing the random number generator.0

Attributes:

NameTypeDescription
w_list of array, element i has shape=[voxels_i, features]The orthogonal transforms (mappings) for each subject.
s_array, shape=[features, samples]The shared response.
sigma_s_array, shape=[features, features]The covariance of the shared response Normal distribution.
mu_list of array, element i has shape=[voxels_i]The voxel means over the samples for each subject.
rho2_array, shape=[subjects]The estimated noise variance ρi2\rho_i^2 for each subject
random_state_RandomStateRandom number generator initialized using rand_seed
Note

The number of voxels may be different between subjects. However, the number of samples must be the same across subjects.

The probabilistic Shared Response Model is approximated using the Expectation Maximization (EM) algorithm proposed in Chen2015. The implementation follows the optimizations published in Anderson2016.

This is a single node version.

The run-time complexity is O(I (V T K + V K^2 + K^3)) and the memory complexity is O(V T) with I - the number of iterations, V - the sum of voxels from all subjects, T - the number of samples, and K - the number of features (typically, V \gg T \gg K).

Methods:

NameDescription
fitCompute the probabilistic Shared Response Model.
transformUse the model to transform matrix to Shared Response space.
transform_subjectTransform a new subject using the existing model.

####### Attributes##

Examples:

Basic multi-subject SRM fitting:

>>> from nltools.algorithms import SRM
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit SRM with CPU parallelization (default)
>>> srm = SRM(n_iter=10, features=50)
>>> srm.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to shared response space
>>> shared_responses = srm.transform(data)
>>>
>>> # Access fitted model components
>>> w = srm.w_  # Subject-specific transforms
>>> s = srm.s_  # Shared response
features
features = features

######## n_iter

n_iter = n_iter

######## rand_seed

rand_seed = rand_seed

####### Functions##

fit
fit(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1, pad_samples: bool = True) -> SRM

Compute the probabilistic Shared Response Model.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples]Each element in the list contains the fMRI data of one subject. Subjects can have different numbers of samples if pad_samples=True.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1
pad_samplesboolIf True (default), automatically zero-pad subjects with fewer samples to match the longest subject. This allows fitting SRM on data with unequal numbers of time points across subjects.True

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples_i]Each element in the list contains the fMRI data of one subject. Note that number of voxels and samples can vary across subjects.required
yAny | Nonenot used (as it is unsupervised learning)None
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
X2D array, shape=[voxels, timepoints]The fMRI data of the new subject.required

Returns:

NameTypeDescription
selfSRMFitted model

######## transform

transform(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray | None]

Use the model to transform matrix to Shared Response space.

Returns:

NameTypeDescription
slist of 2D arrays, element i has shape=[features_i, samples_i]Shared responses from input data (X)

######## transform_subject

transform_subject(X: np.ndarray) -> np.ndarray

Transform a new subject using the existing model.

The subject is assumed to have received equivalent stimulation.

Returns:

NameTypeDescription
w2D array, shape=[voxels, features]Orthogonal mapping W_{new} for new subject

Methods

align
align(data, method = 'deterministic_srm', n_features = None, axis = 0, *args, **kwargs)

Align subject data into a common response model.

This function is a convenience wrapper around HyperAlignment and SRM classes.

Can be used to hyperalign source data to target data using Hyperalignment from Dartmouth (i.e., procrustes transformation; see nltools.algorithms.procrustes) or Shared Response Model from Princeton (see nltools.algorithms.srm). (see nltools.data.BrainData.align for aligning a single Brain object to another). Common Model is shared response model or centered target data. Transformed data can be back projected to original data using Tranformation matrix. Inputs must be a list of BrainData instances or numpy arrays (observations by features).

Parameters:

NameTypeDescriptionDefault
data(list) A list of BrainData objectsrequired
method(str) alignment method to use [‘probabilistic_srm’,‘deterministic_srm’,‘procrustes’]‘deterministic_srm’
n_features(int) number of features to align to common space. If None then will select number of voxelsNone
axis(int) axis to align on0

Returns:

NameTypeDescription
out(dict) a dictionary containing a list of transformed subject matrices, a list of transformation matrices, the shared response matrix, and the intersubject correlation of the shared responses

Examples:

align_states
align_states(reference, target, *, metric = 'correlation', return_index = False, replace_zero_variance = False)

Align state weight maps by minimizing pairwise distance between group states.

This function uses the Hungarian algorithm for state alignment, which is different from aligning multiple subjects’ data.

Parameters:

NameTypeDescriptionDefault
reference(np.array) reference pattern x state matrixrequired
target(np.array) target pattern x state matrix to align to referencerequired
metric(str) distance metric to use‘correlation’
return_index(bool) return index if True, return remapped data if FalseFalse
replace_zero_variance(bool) transform a vector with zero variance to random numbers from a uniform distribution. Useful for when using correlation as a distance metric to avoid NaNs.False

Returns: If return_index=False (default): target[:, remapping], a single ndarray of the target’s columns reordered to match the reference, oriented pattern x state (same shape as target). If return_index=True: the remapping index array (ndarray) that reorders the target’s state columns.

procrustes_distance
procrustes_distance(mat1, mat2, *, n_permute = 5000, tail = 2, n_jobs = -1, random_state = None)

Test matrix similarity using Procrustes superposition.

Matrices need to match in size on their first dimension only, as the smaller matrix on the second dimension will be padded with zeros. After aligning two matrices using the Procrustes transformation, use the computed disparity between them (sum of squared error of elements) as a similarity metric. Shuffle the rows of one of the matrices and recompute the disparity to perform inference (Peres-Neto & Jackson, 2001).

Parameters:

NameTypeDescriptionDefault
mat1ndarray2d numpy array; must have same number of rows as mat2required
mat2ndarray1d or 2d numpy array; must have same number of rows as mat1required
n_permuteintnumber of permutation iterations to perform5000
tailint | str2‘two’ (two-tailed, default) or 1
n_jobsintThe number of CPUs to use to do permutation; default -1 (all)-1
random_stateint, np.random.RandomState, or Noneseed or generator for the permutation shuffling; default NoneNone

Returns:

NameTypeDescription
dictresults with keys similarity (float in [0, 1]) and p (permuted p-value)

Modules

hyperalignment

HyperAlignment: Multi-subject cortical surface alignment using iterative Procrustes refinement.

Hyperalignment finds a common representational space across subjects by iteratively refining pairwise Procrustes transformations. Unlike simple alignment, hyperalignment preserves both spatial structure and representational similarity.

Algorithm overview
  1. Initialize template (first subject or group average)

  2. For each iteration:

    • Align each subject to template (Procrustes transformation)

    • Update template (average in aligned space)

  3. Converge when transformations stabilize or max iterations reached

  4. Final alignment: Apply learned transformations to all subjects

Performance
  • Time complexity: O(n_iter × n_subjects² × n_voxels × n_samples)

  • Memory complexity: O(n_subjects × n_voxels × n_features)

  • Parallelization: ~4-8× speedup with CPU-parallel (parallel=“cpu”)

  • Most beneficial when subjects have many voxels (>10K) and multiple iterations

When to use hyperalignment
  • Multi-subject alignment preserving spatial structure

  • Alternative to SRM when spatial structure is important

  • See nltools.algorithms.srm.SRM for dimension-reduction approach

  • See nltools.algorithms.procrustes() for single-subject alignment

This module implements the hyperalignment technique described in:

Haxby, J. V., Guntupalli, J. S., Connolly, A. C., Halchenko, Y. O., Conroy, B. R., Gobbini, M. I., ... & Ramadge, P. J. (2011). A common, high-dimensional model of the representational space in human ventral temporal cortex. Neuron, 72(2), 404-416.

Classes:

NameDescription
HyperAlignmentHyperalignment using iterative Procrustes alignment.

####### Classes##

HyperAlignment
HyperAlignment(n_iter: int = 2, auto_pad: bool = True) -> None

Bases: BaseEstimator, TransformerMixin

Hyperalignment using iterative Procrustes alignment.

Three-stage iterative process for aligning multi-subject data:

  1. Create initial average template

  2. Refine template through n_iter iterations

  3. Final alignment of all subjects to refined template

This implements the Procrustes-based hyperalignment method commonly used in multi-subject neuroimaging analysis.

Parameters:

NameTypeDescriptionDefault
n_iterint, default=2Number of template refinement iterations (stages 1-2).2
auto_padbool, default=TrueIf True, automatically zero-pad matrices to standardize sizes. If False, caller must ensure all matrices have same dimensions.True

Parameters:

NameTypeDescriptionDefault
n_iterint, default=2Number of template refinement iterations2
auto_padbool, default=TrueWhether to automatically pad matrices to same sizeTrue

######### Attributes####

Attributes:

NameTypeDescription
w_list of ndarray, element i has shape=[features_i, features]The transformation matrices (rotation + reflection) for each subject.
s_ndarray, shape=[features, samples]The aligned common template (shared response).
disparity_list of floatDisparity (sum of squared differences) for each subject.
scale_list of floatScale factors for each subject.
Note

common_model_ property provides alias for s_ (backward compatibility).

Methods:

NameDescription
fitFit hyperalignment model to data.
transformTransform data to common space using fitted transformations.
transform_subjectAlign a new subject to the common space.

Examples:

Basic multi-subject alignment:

>>> from nltools.algorithms import HyperAlignment
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit hyperalignment with CPU parallelization (default)
>>> hyper = HyperAlignment(n_iter=2)
>>> hyper.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to common space
>>> aligned = hyper.transform(data)
>>>
>>> # Access common template
>>> template = hyper.s_  # or hyper.common_model_
>>>
>>> # Align a new subject
>>> new_subject = np.random.randn(100, 50)
>>> new_transform = hyper.transform_subject(new_subject)
Note

When to use parallel processing:

  • Use parallel="cpu" (default) for datasets with 3+ subjects to speed up pairwise Procrustes operations during template refinement.

  • Use parallel=None for debugging or small datasets (<3 subjects) where parallelization overhead isn’t beneficial.

  • Parallel processing is most beneficial when subjects have many voxels (>10K) and template refinement requires multiple iterations.

References

Haxby, J. V., Guntupalli, J. S., Connolly, A. C., Halchenko, Y. O., Conroy, B. R., Gobbini, M. I., ... & Ramadge, P. J. (2011). A common, high-dimensional model of the representational space in human ventral temporal cortex. Neuron, 72(2), 404-416.

auto_pad
auto_pad = auto_pad

########## common_model_

common_model_

Alias for s_ (common template).

########## n_iter

n_iter = n_iter

######### Functions####

fit
fit(data: list[np.ndarray], *, parallel: str | None = 'cpu', n_jobs: int = -1) -> HyperAlignment

Fit hyperalignment model to data.

Parameters:

NameTypeDescriptionDefault
datalist of ndarrayList of data matrices, each with shape (n_features, n_samples). Different subjects can have different numbers of features if auto_pad=True.required
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
datalist of ndarrayList of data matrices to transform. Should be the same data used for fitting (or have compatible dimensions).required
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
subject_data( ndarray , shape ( n_features , n_samples ))Data from a new subject to align to the common templaterequired

Returns:

NameTypeDescription
selfHyperAlignmentFitted model

########## transform

transform(data: list[np.ndarray], *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray]

Transform data to common space using fitted transformations.

Returns:

NameTypeDescription
transformedlist of ndarrayList of transformed data matrices in common space

########## transform_subject

transform_subject(subject_data: np.ndarray) -> tuple[np.ndarray, np.ndarray, float, float]

Align a new subject to the common space.

Returns:

NameTypeDescription
transformedndarrayAligned data in common space
RndarrayTransformation matrix used
disparityfloatAlignment quality (sum of squared differences)
scalefloatScale factor used
local

LocalAlignment: Neighborhood-based functional alignment.

Implements the 'searchlight' and 'roi' spatial scales (the searchlight and piecewise schemes of Bazeille et al. 2021). Uses center-only aggregation to preserve orthogonality of local transforms.

Classes:

NameDescription
LocalAlignmentLocal (neighborhood-based) functional alignment across subjects.

####### Attributes

####### Classes##

LocalAlignment
LocalAlignment(spatial_scale: str = 'searchlight', method: str = 'procrustes', radius_mm: float = 10.0, roi_mask: nib.Nifti1Image | None = None, n_features: int | None = None, n_iter: int = 3, aggregation: str = 'center', parallel: str | None = 'cpu', n_jobs: int = -1, progress_bar: bool = False, n_neighborhoods_batch: int | None = None, max_memory_gb: float | None = None, transforms_: dict[int, list[np.ndarray]] | None = None, template_: dict[int, np.ndarray] | None = None, neighborhoods_: SphereNeighborhoods | dict[int, np.ndarray] | None = None, n_voxels_: int | None = None, mask_: nib.Nifti1Image | None = None, backend_: Backend | None = None) -> None

Local (neighborhood-based) functional alignment across subjects.

Learns alignment transforms within local neighborhoods (searchlight spheres or parcels) and applies center-only aggregation to preserve orthogonality.

Parameters:

NameTypeDescriptionDefault
spatial_scalestrSpatial scale, either ‘searchlight’ (overlapping spheres) or ‘roi’ (non-overlapping parcels). Defaults to ‘searchlight’.‘searchlight’
methodstrAlignment method, one of ‘procrustes’, ‘srm’, or ‘hyperalignment’. Defaults to ‘procrustes’.‘procrustes’
radius_mmfloatSphere radius in millimeters for the searchlight scale. Defaults to 10.0.10.0
roi_maskNifti1Image | NoneParcellation image for the ROI scale. Required if spatial_scale='roi'. Defaults to None.None
n_featuresint | NoneNumber of features for SRM. None uses full Procrustes (preserves dims). Defaults to None.None
n_iterintNumber of iterations for alignment refinement. Defaults to 3.3
aggregationstrAggregation method: ‘center’ (center-only, preserves orthogonality) or ‘all’. Defaults to ‘center’.‘center’
parallelstr | NoneParallelization mode. None runs single-threaded numpy, ‘cpu’ uses joblib CPU parallelization, and ‘gpu’ uses PyTorch. GPU acceleration applies only to method='procrustes'; requesting ‘gpu’ with the ‘srm’ or ‘hyperalignment’ methods raises NotImplementedError (an explicit GPU request never silently runs on CPU). Defaults to ‘cpu’.‘cpu’
n_jobsintNumber of jobs for CPU parallelization. Defaults to -1.-1
progress_barboolWhether to display tqdm progress bars during fit and transform. Defaults to False.False
n_neighborhoods_batchint | NoneNumber of neighborhoods to process per batch on the GPU. None auto-calculates a batch size from max_memory_gb. Defaults to None.None
max_memory_gbfloat | NoneExplicit memory budget (in GB) used to auto-size GPU batches when n_neighborhoods_batch is None. None (default) measures the device’s available memory.None

Attributes:

NameTypeDescription
transforms_dict [ int , list [ ndarray ]]Per-neighborhood transforms. Keys are center voxel indices, values are lists of transform matrices (one per subject).
template_dict [ int , ndarray ]Per-neighborhood templates used for alignment.
neighborhoods_SphereNeighborhoods | dictComputed neighborhoods (searchlight or roi).
n_voxels_intTotal number of voxels in the mask.
mask_Nifti1ImageBrain mask used for fitting.

Methods:

NameDescription
fitFit local alignment on multi-subject data.
fit_transformFit alignment and transform data in one step.
transformApply local transforms to data.

######### Attributes####

Examples:

>>> import numpy as np
>>> import nibabel as nib
>>> from nltools.algorithms.alignment import LocalAlignment
>>> # Create synthetic multi-subject data (voxels, samples)
>>> data = [np.random.randn(1000, 100) for _ in range(5)]
>>> # Build a mask whose nonzero voxels match the 1000-voxel data
>>> mask = nib.Nifti1Image(np.ones((10, 10, 10), dtype=np.int8), np.eye(4))
>>> la = LocalAlignment(spatial_scale='searchlight', method='procrustes', radius_mm=10.0)
>>> la.fit(data, mask)
>>> aligned = la.transform(data)
Note

Based on Bazeille et al. 2021 “An empirical evaluation of functional alignment using inter-subject decoding”. Center-only aggregation is used to preserve local orthogonality of transforms.

aggregation
aggregation: str = 'center'

########## backend_

backend_: Backend | None = field(default=None, repr=False)

########## mask_

mask_: nib.Nifti1Image | None = field(default=None, repr=False)

########## max_memory_gb

max_memory_gb: float | None = None

########## method

method: str = 'procrustes'

########## n_features

n_features: int | None = None

########## n_iter

n_iter: int = 3

########## n_jobs

n_jobs: int = -1

########## n_neighborhoods_batch

n_neighborhoods_batch: int | None = None

########## n_voxels_

n_voxels_: int | None = field(default=None, repr=False)

########## neighborhoods_

neighborhoods_: SphereNeighborhoods | dict[int, np.ndarray] | None = field(default=None, repr=False)

########## parallel

parallel: str | None = 'cpu'

########## progress_bar

progress_bar: bool = False

########## radius_mm

radius_mm: float = 10.0

########## roi_mask

roi_mask: nib.Nifti1Image | None = None

########## spatial_scale

spatial_scale: str = 'searchlight'

########## template_

template_: dict[int, np.ndarray] | None = field(default=None, repr=False)

########## transforms_

transforms_: dict[int, list[np.ndarray]] | None = field(default=None, repr=False)

######### Functions####

fit
fit(data: list[np.ndarray], mask: nib.Nifti1Image) -> LocalAlignment

Fit local alignment on multi-subject data.

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples). Subjects can have different numbers of samples - the underlying alignment methods (SRM, HyperAlignment) handle this via zero-padding.required
maskNifti1ImageBrain mask defining the voxel space.required

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples).required
maskNifti1ImageBrain mask defining the voxel space.required

Parameters:

NameTypeDescriptionDefault
datalist [ ndarray ]List of subject data arrays, each shape (n_voxels, n_samples).required

Returns:

NameTypeDescription
LocalAlignmentLocalAlignmentThe fitted alignment model (self).

########## fit_transform

fit_transform(data: list[np.ndarray], mask: nib.Nifti1Image) -> list[np.ndarray]

Fit alignment and transform data in one step.

Returns:

TypeDescription
list [ ndarray ]list[np.ndarray]: Aligned data for each subject.

########## transform

transform(data: list[np.ndarray]) -> list[np.ndarray]

Apply local transforms to data.

For the searchlight scale with center-only aggregation: each voxel uses the transform from the neighborhood where it was the center.

For the roi scale: all voxels in each parcel use the same transform.

Returns:

TypeDescription
list [ ndarray ]list[np.ndarray]: Aligned data for each subject, each shape (n_voxels, n_samples).

####### Functions

procrustes

Data alignment — SRM, Procrustes, and state alignment.

Methods:

NameDescription
alignAlign subject data into a common response model.
align_statesAlign state weight maps by minimizing pairwise distance between group states.
procrustesPerform a Procrustes similarity analysis on two data sets.
procrustes_distanceTest matrix similarity using Procrustes superposition.

####### Classes

####### Functions##

align
align(data, method = 'deterministic_srm', n_features = None, axis = 0, *args, **kwargs)

Align subject data into a common response model.

This function is a convenience wrapper around HyperAlignment and SRM classes.

Can be used to hyperalign source data to target data using Hyperalignment from Dartmouth (i.e., procrustes transformation; see nltools.algorithms.procrustes) or Shared Response Model from Princeton (see nltools.algorithms.srm). (see nltools.data.BrainData.align for aligning a single Brain object to another). Common Model is shared response model or centered target data. Transformed data can be back projected to original data using Tranformation matrix. Inputs must be a list of BrainData instances or numpy arrays (observations by features).

Parameters:

NameTypeDescriptionDefault
data(list) A list of BrainData objectsrequired
method(str) alignment method to use [‘probabilistic_srm’,‘deterministic_srm’,‘procrustes’]‘deterministic_srm’
n_features(int) number of features to align to common space. If None then will select number of voxelsNone
axis(int) axis to align on0

Parameters:

NameTypeDescriptionDefault
reference(np.array) reference pattern x state matrixrequired
target(np.array) target pattern x state matrix to align to referencerequired
metric(str) distance metric to use‘correlation’
return_index(bool) return index if True, return remapped data if FalseFalse
replace_zero_variance(bool) transform a vector with zero variance to random numbers from a uniform distribution. Useful for when using correlation as a distance metric to avoid NaNs.False

Returns: If return_index=False (default): target[:, remapping], a single ndarray of the target’s columns reordered to match the reference, oriented pattern x state (same shape as target). If return_index=True: the remapping index array (ndarray) that reorders the target’s state columns.

######## procrustes

procrustes(data1, data2)

Perform a Procrustes similarity analysis on two data sets.

For more comprehensive Procrustes-based alignment tasks, use HyperAlignment and align() instead.

Each input matrix is a set of points or vectors (the rows of the matrix). The dimension of the space is the number of columns of each matrix. Given two identically sized matrices, procrustes standardizes both such that:

Parameters:

NameTypeDescriptionDefault
data1Matrix whose n rows represent points in k (columns) space. data1 is the reference data; after it is standardized, the data from data2 will be transformed to fit the pattern in data1 (must have >1 unique points).required
data2n rows of data in k space to be fit to data1. Must be the same shape (numrows, numcols) as data1 (must have >1 unique points).required

Parameters:

NameTypeDescriptionDefault
mat1ndarray2d numpy array; must have same number of rows as mat2required
mat2ndarray1d or 2d numpy array; must have same number of rows as mat1required
n_permuteintnumber of permutation iterations to perform5000
tailint | str2‘two’ (two-tailed, default) or 1
n_jobsintThe number of CPUs to use to do permutation; default -1 (all)-1
random_stateint, np.random.RandomState, or Noneseed or generator for the permutation shuffling; default NoneNone

Returns:

NameTypeDescription
out(dict) a dictionary containing a list of transformed subject matrices, a list of transformation matrices, the shared response matrix, and the intersubject correlation of the shared responses

Examples:

######## align_states

align_states(reference, target, *, metric = 'correlation', return_index = False, replace_zero_variance = False)

Align state weight maps by minimizing pairwise distance between group states.

This function uses the Hungarian algorithm for state alignment, which is different from aligning multiple subjects’ data.

Returns:

NameTypeDescription
mtx1A standardized version of data1.
mtx2The orientation of data2 that best fits data1. Centered, but not necessarily tr(AAT)=1tr(AA^{T}) = 1.
disparityM2M^{2} as defined above.
RThe (N, N) matrix solution of the orthogonal Procrustes problem. Minimizes the Frobenius norm of dot(data1, R) - data2, subject to dot(R.T, R) == I.
scaleSum of the singular values of dot(data1.T, data2).

######## procrustes_distance

procrustes_distance(mat1, mat2, *, n_permute = 5000, tail = 2, n_jobs = -1, random_state = None)

Test matrix similarity using Procrustes superposition.

Matrices need to match in size on their first dimension only, as the smaller matrix on the second dimension will be padded with zeros. After aligning two matrices using the Procrustes transformation, use the computed disparity between them (sum of squared error of elements) as a similarity metric. Shuffle the rows of one of the matrices and recompute the disparity to perform inference (Peres-Neto & Jackson, 2001).

Returns:

NameTypeDescription
dictresults with keys similarity (float in [0, 1]) and p (permuted p-value)
srm

Shared Response Model (SRM) for multi-subject fMRI alignment.

SRM finds a shared low-dimensional representation across subjects while allowing subject-specific transformations. This enables cross-subject analyses while preserving individual variability.

Algorithm overview
  1. Initialize subject-specific transforms W_i (random orthogonal matrices)

  2. Iteratively optimize using Expectation-Maximization (EM):

    • E-step: Update shared response S (group average in shared space)

    • M-step: Update subject transforms W_i (solve Procrustes problem)

    • Update noise variance rho_i^2 per subject

    • Compute likelihood (measure of fit)

  3. Converge when likelihood stabilizes or max iterations reached

Performance
  • Time complexity: O(n_iter × (n_subjects × n_voxels × n_features × n_samples + n_features^3))

  • Memory complexity: O(n_subjects × n_voxels × n_features)

  • Parallelization: ~4-8× speedup with CPU-parallel (parallel=“cpu”)

  • GPU acceleration: Falls back to CPU (not yet implemented)

When to use SRM
  • Multi-subject alignment preserving representational structure

  • Cross-subject analysis requiring shared response space

  • Alternative to hyperalignment when spatial structure is less important

  • See nltools.algorithms.hyperalignment.HyperAlignment for spatial-preserving alignment

The implementations are based on the following publications:

Chen, P. H. C., Chen, J., Yeshurun, Y., Hasson, U., Haxby, J., & Ramadge, P. J. (2015). A reduced-dimension fMRI shared response model. In Advances in Neural Information Processing Systems (pp. 460-468).

Anderson, M. J., Capota, M., Turek, J. S., Zhu, X., Willke, T. L., Wang, Y., & Norman, K. A. (2016, December). Enabling factor analysis on thousand-subject neuroimaging datasets. In Big Data (Big Data), 2016 IEEE International Conference on (pp. 1151-1160). IEEE.

References:

Copyright 2016 Intel Corporation

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Classes:

NameDescription
DetSRMDeterministic Shared Response Model (DetSRM).
SRMProbabilistic Shared Response Model (SRM).

####### Attributes

####### Classes##

DetSRM
DetSRM(*, n_iter: int = 10, features: int = 50, rand_seed: int = 0) -> None

Bases: BaseEstimator, TransformerMixin

Deterministic Shared Response Model (DetSRM).

Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform W per subject:

XiWiS,i=1NX_i \approx W_i S, \forall i=1 \dots N

Parameters:

NameTypeDescriptionDefault
n_iterint, default=10Number of iterations to run the algorithm.10
featuresint, default=50Number of features to compute.50
rand_seedint, default=0Seed for initializing the random number generator.0

Attributes:

NameTypeDescription
w_list of array, element i has shape=[voxels_i, features]The orthogonal transforms (mappings) for each subject.
s_array, shape=[features, samples]The shared response.
random_state_RandomStateRandom number generator initialized using rand_seed
Note

The number of voxels may be different between subjects. However, the number of samples must be the same across subjects.

The Deterministic Shared Response Model is approximated using the Block Coordinate Descent (BCD) algorithm proposed in Chen2015.

This is a single node version.

The run-time complexity is O(I (V T K + V K^2)) and the memory complexity is O(V T) with I - the number of iterations, V - the sum of voxels from all subjects, T - the number of samples, K - the number of features (typically, V \gg T \gg K), and N - the number of subjects.

Methods:

NameDescription
fitCompute the Deterministic Shared Response Model.
transformUse the model to transform data to the Shared Response subspace.
transform_subjectTransform a new subject using the existing model.

######### Attributes####

Examples:

Basic multi-subject DetSRM fitting:

>>> from nltools.algorithms import DetSRM
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit DetSRM with CPU parallelization (default)
>>> detsrm = DetSRM(n_iter=10, features=50)
>>> detsrm.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to shared response space
>>> shared_responses = detsrm.transform(data)
>>>
>>> # Access fitted model components
>>> w = detsrm.w_  # Subject-specific transforms
>>> s = detsrm.s_  # Shared response
features
features = features

########## n_iter

n_iter = n_iter

########## rand_seed

rand_seed = rand_seed

######### Functions####

fit
fit(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> DetSRM

Compute the Deterministic Shared Response Model.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples]Each element in the list contains the fMRI data of one subject.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples_i]Each element in the list contains the fMRI data of one subject.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
X2D array, shape=[voxels, timepoints]The fMRI data of the new subject.required

Parameters:

NameTypeDescriptionDefault
n_iterint, default=10Number of iterations to run the algorithm.10
featuresint, default=50Number of features to compute.50
rand_seedint, default=0Seed for initializing the random number generator.0

Attributes:

NameTypeDescription
w_list of array, element i has shape=[voxels_i, features]The orthogonal transforms (mappings) for each subject.
s_array, shape=[features, samples]The shared response.
sigma_s_array, shape=[features, features]The covariance of the shared response Normal distribution.
mu_list of array, element i has shape=[voxels_i]The voxel means over the samples for each subject.
rho2_array, shape=[subjects]The estimated noise variance ρi2\rho_i^2 for each subject
random_state_RandomStateRandom number generator initialized using rand_seed
Note

The number of voxels may be different between subjects. However, the number of samples must be the same across subjects.

The probabilistic Shared Response Model is approximated using the Expectation Maximization (EM) algorithm proposed in Chen2015. The implementation follows the optimizations published in Anderson2016.

This is a single node version.

The run-time complexity is O(I (V T K + V K^2 + K^3)) and the memory complexity is O(V T) with I - the number of iterations, V - the sum of voxels from all subjects, T - the number of samples, and K - the number of features (typically, V \gg T \gg K).

Methods:

NameDescription
fitCompute the probabilistic Shared Response Model.
transformUse the model to transform matrix to Shared Response space.
transform_subjectTransform a new subject using the existing model.

######### Attributes####

Returns:

NameTypeDescription
selfDetSRMFitted model

########## transform

transform(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray]

Use the model to transform data to the Shared Response subspace.

Returns:

NameTypeDescription
slist of 2D arrays, element i has shape=[features_i, samples_i]Shared responses from input data (X)

########## transform_subject

transform_subject(X: np.ndarray) -> np.ndarray

Transform a new subject using the existing model.

The subject is assumed to have received equivalent stimulation.

Returns:

NameTypeDescription
w2D array, shape=[voxels, features]Orthogonal mapping W_{new} for new subject

######## SRM

SRM(*, n_iter: int = 10, features: int = 50, rand_seed: int = 0) -> None

Bases: BaseEstimator, TransformerMixin

Probabilistic Shared Response Model (SRM).

Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform W per subject:

XiWiS,i=1NX_i \approx W_i S, \forall i=1 \dots N

Examples:

Basic multi-subject SRM fitting:

>>> from nltools.algorithms import SRM
>>> import numpy as np
>>>
>>> # Create sample data (3 subjects)
>>> data = [np.random.randn(100, 50) for _ in range(3)]
>>>
>>> # Fit SRM with CPU parallelization (default)
>>> srm = SRM(n_iter=10, features=50)
>>> srm.fit(data, parallel="cpu", n_jobs=-1)
>>>
>>> # Transform to shared response space
>>> shared_responses = srm.transform(data)
>>>
>>> # Access fitted model components
>>> w = srm.w_  # Subject-specific transforms
>>> s = srm.s_  # Shared response
features
features = features

########## n_iter

n_iter = n_iter

########## rand_seed

rand_seed = rand_seed

######### Functions####

fit
fit(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1, pad_samples: bool = True) -> SRM

Compute the probabilistic Shared Response Model.

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples]Each element in the list contains the fMRI data of one subject. Subjects can have different numbers of samples if pad_samples=True.required
yAny | Nonenot usedNone
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1
pad_samplesboolIf True (default), automatically zero-pad subjects with fewer samples to match the longest subject. This allows fitting SRM on data with unequal numbers of time points across subjects.True

Parameters:

NameTypeDescriptionDefault
Xlist of 2D arrays, element i has shape=[voxels_i, samples_i]Each element in the list contains the fMRI data of one subject. Note that number of voxels and samples can vary across subjects.required
yAny | Nonenot used (as it is unsupervised learning)None
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU parallelization via joblib (default, multi-subject processing) - “gpu”: not implemented -- raises NotImplementedError (never a silent CPU fallback)‘cpu’
n_jobsintNumber of CPU cores for parallelization (-1 = auto-detect based on memory). Only used when parallel=“cpu”. Defaults to -1.-1

Parameters:

NameTypeDescriptionDefault
X2D array, shape=[voxels, timepoints]The fMRI data of the new subject.required

Returns:

NameTypeDescription
selfSRMFitted model

########## transform

transform(X: list[np.ndarray], y: Any | None = None, *, parallel: str | None = 'cpu', n_jobs: int = -1) -> list[np.ndarray | None]

Use the model to transform matrix to Shared Response space.

Returns:

NameTypeDescription
slist of 2D arrays, element i has shape=[features_i, samples_i]Shared responses from input data (X)

########## transform_subject

transform_subject(X: np.ndarray) -> np.ndarray

Transform a new subject using the existing model.

The subject is assumed to have received equivalent stimulation.

Returns:

NameTypeDescription
w2D array, shape=[voxels, features]Orthogonal mapping W_{new} for new subject

backends

Backend abstraction for CPU/GPU operations.

Supports NumPy (CPU-only) and PyTorch (CPU/CUDA/MPS) backends for linear algebra operations. Enables transparent acceleration while maintaining NumPy-first development.

Attributes:

NameTypeDescription
BATCH_WORKING_SET_CEILING_GB

Classes:

NameDescription
BackendBackend abstraction for numerical operations.

Methods:

NameDescription
assert_array_almost_equalTest array equality with automatic precision adjustment for MPS backend.
auto_batch_sizeSplit n_items into batches that fit a memory budget.
auto_n_jobs_for_arraysMemory-aware joblib worker count for a per-item map over arrays.
auto_select_backendAutomatically select backend based on problem size.
check_gpu_availableCheck if GPU acceleration is available.
compute_oom_safeRun fn(*arrays) with reactive out-of-memory recovery.
device_memory_budgetUsable memory budget in GB for a backend’s device.
empty_device_cacheRelease cached device memory. No-op without torch or a GPU.
gb_to_bytesConvert a GB budget to bytes — the package’s one GB↔bytes conversion.
is_oom_errorTrue if exc is a device out-of-memory error (CUDA or MPS).
resolve_backendCoerce a backend specifier into a Backend instance.

Classes

Backend
Backend(backend: str = 'numpy')

Backend abstraction for numerical operations.

Provides a unified interface for NumPy and PyTorch operations, enabling transparent GPU acceleration when available.

Parameters:

NameTypeDescriptionDefault
backendstrBackend type: ‘numpy’, ‘torch’, or ‘auto’ - ‘numpy’: CPU-only using NumPy - ‘torch’: PyTorch with automatic device detection (cuda/mps/cpu) - ‘auto’: Automatically select best available backend‘numpy’

Attributes:

NameTypeDescription
namestrBackend identifier (e.g., ‘numpy’, ‘torch-cuda’, ‘torch-mps’)
devicestrDevice type (‘cpu’, ‘cuda’, or ‘mps’)
xpmoduleArray library module (numpy or torch)

Methods:

NameDescription
asarrayConvert input to a backend array.
asarray_likeConvert x to an array matching ref’s dtype (and device for torch).
check_arraysCoerce all inputs to the same dtype (and device) as the first.
concatenateConcatenate arrays along an axis.
copyReturn an independent copy of the array.
dtype_to_strNormalize a dtype (numpy, torch, or string) to its string name.
expand_dimsInsert a new axis.
flatnonzeroReturn indices of non-zero elements in the flattened array.
fullCreate array filled with fill_value.
full_likeCreate array filled with fill_value, optionally with a different shape.
matmulMatrix multiplication.
ones_likeCreate ones array, optionally with a different shape.
sortSort along an axis, returning values only.
svdCompute Singular Value Decomposition.
to_cpuTransfer array to CPU. No-op for numpy.
to_deviceTransfer array to backend device.
to_gpuTransfer array to GPU. No-op for numpy.
to_numpyConvert array back to NumPy.
zeros_likeCreate zeros array, optionally with a different shape.

####### Attributes##

is_gpu
is_gpu

True if backend is using a GPU device (CUDA or MPS).

####### Functions##

asarray
asarray(x, dtype = None, device = None)

Convert input to a backend array.

Handles numpy arrays, lists, and torch tensors. Places result on the backend’s device (or an explicit device).

Parameters:

NameTypeDescriptionDefault
xInput data (array-like, tensor, list).required
dtypeDesired dtype as string, numpy, or torch dtype. If None, inferred from input.None
deviceTarget device string (e.g. “cpu”, “cuda”). Ignored for numpy backend. If None, uses the backend’s default device.None

Parameters:

NameTypeDescriptionDefault
xInput data.required
refReference array whose dtype/device to match.required

Parameters:

NameTypeDescriptionDefault
*inputsArrays, lists of arrays, or None.()

Parameters:

NameTypeDescriptionDefault
arraysSequence of arrays.required
axisAxis to concatenate along (default 0).0

######## copy

copy(array)

Return an independent copy of the array.

Parameters:

NameTypeDescriptionDefault
arrayInput array.required

######## dtype_to_str

dtype_to_str(dtype)

Normalize a dtype (numpy, torch, or string) to its string name.

Parameters:

NameTypeDescriptionDefault
dtypeData type to convert (str, numpy dtype, torch dtype, or None).required

Parameters:

NameTypeDescriptionDefault
arrayInput array.required
axisPosition of the new axis.required

######## flatnonzero

flatnonzero(array)

Return indices of non-zero elements in the flattened array.

Parameters:

NameTypeDescriptionDefault
arrayInput array.required

######## full

full(shape, fill_value, dtype = None)

Create array filled with fill_value.

Parameters:

NameTypeDescriptionDefault
shapeOutput shape (int or tuple).required
fill_valueScalar fill value.required
dtypeOutput dtype. If None, inferred by the backend.None

######## full_like

full_like(array, fill_value, shape = None, dtype = None, device = None)

Create array filled with fill_value, optionally with a different shape.

Parameters:

NameTypeDescriptionDefault
arrayReference array for dtype inference.required
fill_valueScalar fill value.required
shapeOutput shape. If None, uses array.shape.None
dtypeOutput dtype. If None, uses array.dtype.None
deviceTarget device (torch only). If None, uses array’s device.None

######## matmul

matmul(A, B)

Matrix multiplication.

Parameters:

NameTypeDescriptionDefault
AarrayFirst matrixrequired
BarraySecond matrixrequired

Parameters:

NameTypeDescriptionDefault
arrayReference array for dtype inference.required
shapeOutput shape. If None, uses array.shape.None
dtypeOutput dtype. If None, uses array.dtype.None
deviceTarget device (torch only). If None, uses array’s device.None

######## sort

sort(array, axis = -1)

Sort along an axis, returning values only.

Parameters:

NameTypeDescriptionDefault
arrayInput array.required
axisAxis to sort along (default -1).-1

######## svd

svd(X, full_matrices = False)

Compute Singular Value Decomposition.

Parameters:

NameTypeDescriptionDefault
XarrayInput matrix (n_samples, n_features)required
full_matricesbool, default=FalseIf False, returns reduced SVDFalse

Parameters:

NameTypeDescriptionDefault
arrayInput array or tensor.required

Parameters:

NameTypeDescriptionDefault
arrndarrayInput numpy arrayrequired

Parameters:

NameTypeDescriptionDefault
arrayInput array or tensor.required
deviceTarget device (defaults to backend’s device).None

Parameters:

NameTypeDescriptionDefault
arrndarray or TensorArray to convertrequired

Parameters:

NameTypeDescriptionDefault
arrayReference array for dtype inference.required
shapeOutput shape. If None, uses array.shape.None
dtypeOutput dtype. If None, uses array.dtype.None
deviceTarget device (torch only). If None, uses array’s device.None

Returns:

TypeDescription
Backend array (numpy ndarray or torch Tensor).

######## asarray_like

asarray_like(x, ref)

Convert x to an array matching ref’s dtype (and device for torch).

Returns:

TypeDescription
Backend array with same dtype/device as ref.

######## check_arrays

check_arrays(*inputs)

Coerce all inputs to the same dtype (and device) as the first.

None values are passed through. Lists of arrays are converted element-wise.

Returns:

NameTypeDescription
listConverted arrays in the same order as inputs.

######## concatenate

concatenate(arrays, axis = 0)

Concatenate arrays along an axis.

Returns:

TypeDescription
str or None: e.g. “float32”, “float64”, or None if input was None.

######## expand_dims

expand_dims(array, axis)

Insert a new axis.

Returns:

NameTypeDescription
arrayResult of A @ B

######## ones_like

ones_like(array, shape = None, dtype = None, device = None)

Create ones array, optionally with a different shape.

Returns:

NameTypeDescription
tuple(U, s, Vt) where: - U (array): Left singular vectors - s (array): Singular values - Vt (array): Right singular vectors (transposed)

######## to_cpu

to_cpu(array)

Transfer array to CPU. No-op for numpy.

Returns:

TypeDescription
Array on CPU.

######## to_device

to_device(arr: np.ndarray)

Transfer array to backend device.

Returns:

NameTypeDescription
arrayArray on device (numpy array or torch tensor)

######## to_gpu

to_gpu(array, device = None)

Transfer array to GPU. No-op for numpy.

Returns:

TypeDescription
Array on GPU device.

######## to_numpy

to_numpy(arr)

Convert array back to NumPy.

Returns:

TypeDescription
np.ndarray: NumPy array

######## zeros_like

zeros_like(array, shape = None, dtype = None, device = None)

Create zeros array, optionally with a different shape.

Methods

assert_array_almost_equal
assert_array_almost_equal(x, y, decimal = 6, err_msg = '', verbose = True, backend = None)

Test array equality with automatic precision adjustment for MPS backend.

This utility automatically reduces precision expectations for torch-mps backend due to float32 precision limitations, preventing test failures while maintaining realistic precision checks for other backends.

Parameters:

NameTypeDescriptionDefault
xFirst array to comparerequired
ySecond array to comparerequired
decimalDesired decimal precision (default: 6)6
err_msgError message prefix‘’
verboseWhether to print detailed error messagesTrue
backendBackend instance (optional). If None, attempts to detect from x/y.None

Returns:

TypeDescription
None (raises AssertionError if arrays don’t match)
auto_batch_size
auto_batch_size(n_items: int, bytes_per_item: float, *, budget_gb: float, overhead: float = 1.0, min_batch: int = 1) -> tuple[int, int]

Split n_items into batches that fit a memory budget.

The one batch calculator for the package. Callers supply only the per-item working-set estimate (bytes_per_item) and an algorithm’s allocation overhead factor; the clamp/ceil policy lives here.

Parameters:

NameTypeDescriptionDefault
n_itemsintTotal number of items (permutations, targets, ...).required
bytes_per_itemfloatDominant working-set size of one item in bytes.required
budget_gbfloatMemory budget from device_memory_budget.required
overheadfloatMultiplier for intermediate allocations (e.g. 3.0 when the computation holds ~3x the input working set).1.0
min_batchintSmallest batch worth dispatching (amortizes launch and transfer overhead). Never exceeds n_items.1

Returns:

TypeDescription
inttuple[int, int]: (batch_size, n_batches) with
intbatch_size * n_batches >= n_items.
auto_n_jobs_for_arrays
auto_n_jobs_for_arrays(arrays, *, max_memory_gb: float | None = None, min_jobs: int = 1) -> int

Memory-aware joblib worker count for a per-item map over arrays.

Sizes workers by the largest item (each worker pickles its item), using the same measured budget as the device batching layer. None entries are ignored; an empty list returns min_jobs.

Parameters:

NameTypeDescriptionDefault
arraysIterable of numpy arrays (None entries allowed).required
max_memory_gbfloat | NoneExplicit memory budget in GB. None (default) measures available system RAM with headroom via device_memory_budget.None
min_jobsintMinimum number of workers (default: 1).1

Returns:

NameTypeDescription
intintWorker count for joblib.Parallel(n_jobs=...).
auto_select_backend
auto_select_backend(n_samples: int, n_features: int, cv: int = 1) -> Backend

Automatically select backend based on problem size.

Uses heuristics to decide between NumPy (CPU) and PyTorch (GPU) based on the computational workload. Small problems use NumPy to avoid GPU transfer overhead. Large problems prefer GPU when available.

Parameters:

NameTypeDescriptionDefault
n_samplesintNumber of samples in datasetrequired
n_featuresintNumber of features in datasetrequired
cvint, default=1Number of cross-validation folds (multiplies effective size)1

Returns:

NameTypeDescription
BackendBackendSelected backend instance
Notes

Selection criteria:

  • Small problems (< 10M elements): Use NumPy

  • Large problems (> 30M elements): Use GPU if available

  • Cross-validation: Prefer GPU even for medium problems

check_gpu_available
check_gpu_available() -> tuple[bool, dict[str, Any]]

Check if GPU acceleration is available.

Returns:

NameTypeDescription
tupletuple [ bool , dict [ str , Any ]](available, info) where: - available (bool): True if GPU (CUDA or MPS) is available - info (dict): Dictionary with keys: - ‘backend’: ‘torch’ or ‘numpy’ - ‘device’: ‘cpu’, ‘cuda’, or ‘mps’ - ‘device_name’: Human-readable device name
compute_oom_safe
compute_oom_safe(fn, *arrays, min_chunk: int = 1)

Run fn(*arrays) with reactive out-of-memory recovery.

All arrays must share their axis-0 length, and fn must map them to a numpy array whose axis 0 corresponds row-for-row to its inputs. On a device OOM the cache is emptied, the arrays are split in half along axis 0, and the halves are retried recursively; partial results are concatenated along axis 0.

Because splitting reuses the already generated inputs rather than re-drawing them, recovery never changes which permutations a seeded result is computed from — RNG-consuming input generation stays outside this function. For a row-independent fn the recovered output matches the unsplit computation to within floating-point reduction order (backends may block reductions differently per batch shape; observed differences are ~1 float32 ulp).

Parameters:

NameTypeDescriptionDefault
fnCallable mapping the arrays to a numpy result (axis-0 aligned).required
*arraysInput arrays sharing axis-0 length.()
min_chunkintChunk size below which an OOM is considered fatal.1

Returns:

TypeDescription
np.ndarray: fn’s result, possibly assembled from retried chunks.
device_memory_budget
device_memory_budget(backend: Backend | None = None, max_gpu_memory_gb: float | None = None, *, cap_for_batching: bool = False) -> float

Usable memory budget in GB for a backend’s device.

An explicit max_gpu_memory_gb always wins, uncapped. Otherwise the budget is measured at call time: free CUDA memory (with headroom) on CUDA devices; available system RAM (with headroom) for CPU and MPS, which share unified/system memory. When nothing can be measured the conservative 4 GB fallback applies.

Parameters:

NameTypeDescriptionDefault
backendBackend | NoneResolved Backend whose device the work runs on. None is treated as CPU.None
max_gpu_memory_gbfloat | NoneExplicit budget override in GB. Must be positive.None
cap_for_batchingboolPass True when the budget sizes batches — a measured budget is then capped at BATCH_WORKING_SET_CEILING_GB, because working sets beyond the saturation ceiling add allocation cost without throughput gain and starve unified-memory hosts. Never applied to an explicit max_gpu_memory_gb; capacity queries (the default) stay uncapped.False

Returns:

NameTypeDescription
floatfloatBudget in GB.
empty_device_cache
empty_device_cache() -> None

Release cached device memory. No-op without torch or a GPU.

gb_to_bytes
gb_to_bytes(gb: float) -> int

Convert a GB budget to bytes — the package’s one GB↔bytes conversion.

is_oom_error
is_oom_error(exc: BaseException) -> bool

True if exc is a device out-of-memory error (CUDA or MPS).

resolve_backend
resolve_backend(parallel)

Coerce a backend specifier into a Backend instance.

Accepts the values callers typically thread through the algorithms package (None/"cpu" → numpy, "gpu"/"torch" → torch, "numpy"/"auto" → their direct Backend constructors). Existing Backend instances are returned unchanged — this is the main reason to prefer resolve_backend over constructing a new Backend(...) at each call site: it avoids repeated device detection/torch imports when a backend has already been chosen upstream.

Parameters:

NameTypeDescriptionDefault
parallelBackend specifier. One of:
- None or "cpu": numpy backend. - "numpy", "torch", "auto": forwarded to Backend(...). - "gpu": alias for "torch" (auto-detects cuda/mps/cpu). - An existing Backend instance (returned as-is).
required

Returns:

NameTypeDescription
BackendResolved backend instance.

corrections

Multiple comparison corrections and thresholding.

Methods:

NameDescription
fdrDetermine an FDR threshold for an array of p-values.
holm_bonfCompute Holm-Bonferroni-corrected p-values.
multi_thresholdThreshold test image by multiple p-values from p image.
thresholdThreshold test image by p-value from p image.

Methods

fdr
fdr(p, q = 0.05)

Determine an FDR threshold for an array of p-values.

Uses the desired false discovery rate q. Written by Tal Yarkoni.

Parameters:

NameTypeDescriptionDefault
p(np.array) vector of p-valuesrequired
q(float) false discovery rate level0.05

Returns:

NameTypeDescription
fdr_p(float) p-value threshold based on independence or positive dependence
holm_bonf
holm_bonf(p, alpha = 0.05)

Compute Holm-Bonferroni-corrected p-values.

This step-down procedure applies iteratively less correction to the highest p-values. It is a bit more conservative than FDR, but much more powerful than vanilla Bonferroni correction.

Parameters:

NameTypeDescriptionDefault
p(np.array) vector of p-valuesrequired
alpha(float) alpha level0.05

Returns:

NameTypeDescription
bonf_p(float) p-value threshold based on bonferroni step-down procedure
multi_threshold
multi_threshold(t_map, p_map, thresh)

Threshold test image by multiple p-values from p image.

Parameters:

NameTypeDescriptionDefault
t_map(BrainData) BrainData instance of statistic metric (e.g., t-statistic, beta, etc)required
p_map(BrainData) BrainData instance of p-valuesrequired
thresh(list) list of p-values to threshold stat imagerequired

Returns:

NameTypeDescription
outThresholded BrainData instance with cumulative map - Positive values indicate how many thresholds were passed for positive stats - Negative values indicate how many thresholds were passed for negative stats
Note

This function provides unique cumulative threshold map functionality:

  • Creates a single map showing which thresholds were passed

  • Different from calling threshold() multiple times (which would give separate images)

  • Useful for visualizing threshold hierarchies

  • nilearn.threshold_img() does not support cumulative multi-threshold maps

threshold
threshold(stat, p, thr = 0.05, return_mask = False)

Threshold test image by p-value from p image.

Parameters:

NameTypeDescriptionDefault
stat(BrainData) BrainData instance of arbitrary statistic metric (e.g., beta, t, etc)required
p(BrainData) BrainData instance of p-valuesrequired
thr(float) p-value threshold to apply0.05
return_mask(bool) optionally return the thresholding mask; default FalseFalse

Returns:

NameTypeDescription
outThresholded BrainData instance
mask(optional) BrainData instance of thresholding mask if return_mask=True
Note

This function provides unique functionality not available in nilearn:

  • Thresholds stat image based on p-values from separate p-value image

  • Neither nilearn.threshold_img nor BrainData.threshold() support this

  • BrainData.threshold() thresholds based on stat values themselves

  • nilearn.threshold_img() thresholds based on image intensity values

hrf

Hemodynamic response functions — re-exported from nilearn.

nilearn ships canonical SPM and Glover HRFs (and their derivatives) under nilearn.glm.first_level. This module just re-exports them so existing nltools.algorithms.hrf imports keep working.

Methods:

NameDescription
glover_dispersion_derivativeImplement the Glover dispersion derivative :term:HRF model.
glover_hrfImplement the Glover :term:HRF model.
glover_time_derivativeImplement the Glover time derivative :term:HRF (dhrf) model.
spm_dispersion_derivativeImplement the :term:SPM dispersion derivative :term:HRF model.
spm_hrfImplement the :term:SPM :term:HRF model.
spm_time_derivativeImplement the :term:SPM time derivative :term:HRF (dhrf) model.

Methods

glover_dispersion_derivative
glover_dispersion_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the Glover dispersion derivative :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor in seconds.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response in seconds.

Returns

dhrf : array of shape (length / t_r * oversampling), dtype=float dhrf sampling on the oversampled time grid

Examples

import numpy as np from nilearn.glm.first_level import glover_dispersion_derivative ddhrf = glover_dispersion_derivative( ... t_r=2.0, oversampling=1, time_length=20.0 ... ) np.round(ddhrf, 3).tolist() [0.0, -0.0, -0.373, 0.282, 0.295, -0.04, -0.094, -0.048, -0.017, -0.005]

glover_hrf
glover_hrf(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the Glover :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response.

Returns

hrf : array of shape (length / t_r * oversampling, dtype=float) :term:HRF sampling on the oversampled time grid.

Examples

import numpy as np from nilearn.glm.first_level import glover_hrf hrf = glover_hrf(t_r=2.0, oversampling=1, time_length=20.0) np.round(hrf, 3).tolist() [0.0, 0.0, 0.226, 0.741, 0.5, 0.037, -0.181, -0.176, -0.103, -0.045]

glover_time_derivative
glover_time_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the Glover time derivative :term:HRF (dhrf) model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response.

Returns

dhrf : array of shape (length / t_r), dtype=float dhrf sampling on the provided grid

Examples

import numpy as np from nilearn.glm.first_level import glover_time_derivative dhrf = glover_time_derivative( ... t_r=2.0, oversampling=1, time_length=20.0 ... ) np.round(dhrf, 3).tolist() [0.0, 0.0, 0.267, 0.076, -0.215, -0.168, -0.039, 0.027, 0.033, 0.019]

spm_dispersion_derivative
spm_dispersion_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the :term:SPM dispersion derivative :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor in seconds.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response in seconds.

Returns

dhrf : array of shape (length / tr * oversampling), dtype=float dhrf sampling on the oversampled time grid

Examples

import numpy as np from nilearn.glm.first_level import glover_dispersion_derivative ddhrf = glover_dispersion_derivative( ... t_r=2.0, oversampling=1, time_length=20.0 ... ) np.round(ddhrf, 3).tolist() [0.0, -0.0, -0.373, 0.282, 0.295, -0.04, -0.094, -0.048, -0.017, -0.005]

spm_hrf
spm_hrf(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the :term:SPM :term:HRF model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

:term:HRF onset time, in seconds.

Returns

hrf : array of shape (length / t_r * oversampling, dtype=float) :term:HRF sampling on the oversampled time grid

Examples

import numpy as np from nilearn.glm.first_level import spm_hrf hrf = spm_hrf(t_r=2.0, oversampling=1, time_length=20.0) np.round(hrf, 3).tolist() [0.0, 0.0, 0.161, 0.443, 0.335, 0.139, 0.022, -0.028, -0.04, -0.033]

spm_time_derivative
spm_time_derivative(t_r, oversampling = 50, time_length = 32.0, onset = 0.0)

Implement the :term:SPM time derivative :term:HRF (dhrf) model.

Parameters

t_r : float :term:Repetition time<TR>, in seconds (sampling period).

`int`, default=50

Temporal oversampling factor.

`float`, default=32.0

:term:HRF kernel length, in seconds.

`float`, default=0.0

Onset of the response in seconds.

Returns

dhrf : array of shape (length / t_r, dtype=float) dhrf sampling on the provided grid

Examples

import numpy as np from nilearn.glm.first_level import spm_time_derivative dhrf = spm_time_derivative(t_r=2.0, oversampling=1, time_length=20.0) np.round(dhrf, 3).tolist() [0.0, 0.0, 0.167, 0.04, -0.091, -0.072, -0.035, -0.013, -0.0, 0.005]

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

####### 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

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

####### Attributes##

Classes:

NameDescription
OnlineBootstrapStatsMemory-efficient online statistics aggregator for bootstrap samples.
FITTED_METHODS
FITTED_METHODS = ['weights', 'predict']

######## SIMPLE_METHODS

SIMPLE_METHODS = ['mean', 'median', 'std', 'sum', 'min', 'max']

####### 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

####### Functions

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.

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.

####### Attributes

####### Classes

####### Functions##

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.

####### Functions##

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

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

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

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:

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.

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.

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.

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.

####### Attributes

####### Functions##

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’

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_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.

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

####### Attributes##

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.
MAX_INT
MAX_INT = np.iinfo(np.int32).max

####### Functions##

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

Parameters:

NameTypeDescriptionDefault
matndarray2d numpy arrayrequired

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

Parameters:

NameTypeDescriptionDefault
matndarray2d numpy arrayrequired

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.

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:

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.

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

####### Functions##

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

####### Functions##

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

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

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
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

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.

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

####### Functions##

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

####### Attributes##

EPSILON
EPSILON = 1e-10

####### Functions

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.

####### Functions##

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.

outliers

Outlier detection, robust statistics, and data normalization.

Methods:

NameDescription
find_spikesIdentify spikes (motion artifacts, intensity outliers) in 4D fMRI data.
trimTrim a Polars DataFrame/Series by replacing outlier values with NaNs.
winsorizeWinsorize a Polars DataFrame/Series with the largest/lowest value not considered outlier.
zscoreZ-score every column of a Polars or pandas DataFrame/Series.

Methods

find_spikes
find_spikes(data, global_spike_cutoff = 3, diff_spike_cutoff = 3, *, TR: float | None = None, sampling_freq: float | None = None)

Identify spikes (motion artifacts, intensity outliers) in 4D fMRI data.

Parameters:

NameTypeDescriptionDefault
dataBrainData or nibabel instancerequired
global_spike_cutoff(int, None) cutoff in std-deviations for spikes in the per-TR global signal. None to skip.3
diff_spike_cutoff(int, None) cutoff in std-deviations for spikes in the per-TR mean absolute frame-to-frame difference. None to skip.3
TRfloat | NoneRepetition time in seconds. Sets the returned DesignMatrix’s sampling_freq for downstream .append(...) / .convolve(). Pass exactly one of TR or sampling_freq.None
sampling_freqfloat | NoneSampling frequency in Hz (= 1/TR). See TR.None

Returns:

NameTypeDescription
DesignMatrixone indicator column per detected spike TR, named
.nl_global_spike{n} / .nl_diff_spike{n} in the reserved
namespace for generated columns (see RESERVED_PREFIX), with all
spike columns pre-marked as confounds. The two detectors run
independently, so a single bad volume is routinely caught by both;
those detections are bitwise-identical one-hot columns, and only one
is kept (the .nl_global_spike* name, a deterministic tie-break —
the column values are the same either way). Row position is the time
axis (no separate TR index column — that was a pandas-era
artifact). When TR / sampling_freq aren’t provided the DM has
sampling_freq=None; you can still .append() it onto a DM that
does have one.
trim
trim(data, cutoff = None)

Trim a Polars DataFrame/Series by replacing outlier values with NaNs.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to trimrequired
cutoff(dict) a dictionary with keys {‘std’:[low,high]} or {‘quantile’:[low,high]}None

Returns: out: (pl.DataFrame, pl.Series) trimmed data (same type as input)

winsorize
winsorize(data, cutoff = None, replace_with_cutoff = True)

Winsorize a Polars DataFrame/Series with the largest/lowest value not considered outlier.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to winsorizerequired
cutoff(dict) a dictionary with keys {‘std’:[low,high]} or {‘quantile’:[low,high]}None
replace_with_cutoff(bool) If True, replace outliers with cutoff. If False, replaces outliers with closest existing values; (default: True)True

Returns: out: (pl.DataFrame, pl.Series) winsorized data (same type as input)

zscore
zscore(data)

Z-score every column of a Polars or pandas DataFrame/Series.

Accepts pandas inputs at the boundary for convenience and converts to Polars internally. Always returns Polars output (DataFrame or Series, matching the input shape).

Parameters:

NameTypeDescriptionDefault
datapl.DataFrame, pl.Series, pd.DataFrame, or pd.Series.required

Returns:

TypeDescription
pl.DataFrame or pl.Series with each column z-scored using sample
standard deviation (ddof=1), matching the input shape.

procrustes

Data alignment — SRM, Procrustes, and state alignment.

Methods:

NameDescription
alignAlign subject data into a common response model.
align_statesAlign state weight maps by minimizing pairwise distance between group states.
procrustesPerform a Procrustes similarity analysis on two data sets.
procrustes_distanceTest matrix similarity using Procrustes superposition.

Classes

Methods

align
align(data, method = 'deterministic_srm', n_features = None, axis = 0, *args, **kwargs)

Align subject data into a common response model.

This function is a convenience wrapper around HyperAlignment and SRM classes.

Can be used to hyperalign source data to target data using Hyperalignment from Dartmouth (i.e., procrustes transformation; see nltools.algorithms.procrustes) or Shared Response Model from Princeton (see nltools.algorithms.srm). (see nltools.data.BrainData.align for aligning a single Brain object to another). Common Model is shared response model or centered target data. Transformed data can be back projected to original data using Tranformation matrix. Inputs must be a list of BrainData instances or numpy arrays (observations by features).

Parameters:

NameTypeDescriptionDefault
data(list) A list of BrainData objectsrequired
method(str) alignment method to use [‘probabilistic_srm’,‘deterministic_srm’,‘procrustes’]‘deterministic_srm’
n_features(int) number of features to align to common space. If None then will select number of voxelsNone
axis(int) axis to align on0

Returns:

NameTypeDescription
out(dict) a dictionary containing a list of transformed subject matrices, a list of transformation matrices, the shared response matrix, and the intersubject correlation of the shared responses

Examples:

align_states
align_states(reference, target, *, metric = 'correlation', return_index = False, replace_zero_variance = False)

Align state weight maps by minimizing pairwise distance between group states.

This function uses the Hungarian algorithm for state alignment, which is different from aligning multiple subjects’ data.

Parameters:

NameTypeDescriptionDefault
reference(np.array) reference pattern x state matrixrequired
target(np.array) target pattern x state matrix to align to referencerequired
metric(str) distance metric to use‘correlation’
return_index(bool) return index if True, return remapped data if FalseFalse
replace_zero_variance(bool) transform a vector with zero variance to random numbers from a uniform distribution. Useful for when using correlation as a distance metric to avoid NaNs.False

Returns: If return_index=False (default): target[:, remapping], a single ndarray of the target’s columns reordered to match the reference, oriented pattern x state (same shape as target). If return_index=True: the remapping index array (ndarray) that reorders the target’s state columns.

procrustes
procrustes(data1, data2)

Perform a Procrustes similarity analysis on two data sets.

For more comprehensive Procrustes-based alignment tasks, use HyperAlignment and align() instead.

Each input matrix is a set of points or vectors (the rows of the matrix). The dimension of the space is the number of columns of each matrix. Given two identically sized matrices, procrustes standardizes both such that:

Parameters:

NameTypeDescriptionDefault
data1Matrix whose n rows represent points in k (columns) space. data1 is the reference data; after it is standardized, the data from data2 will be transformed to fit the pattern in data1 (must have >1 unique points).required
data2n rows of data in k space to be fit to data1. Must be the same shape (numrows, numcols) as data1 (must have >1 unique points).required

Returns:

NameTypeDescription
mtx1A standardized version of data1.
mtx2The orientation of data2 that best fits data1. Centered, but not necessarily tr(AAT)=1tr(AA^{T}) = 1.
disparityM2M^{2} as defined above.
RThe (N, N) matrix solution of the orthogonal Procrustes problem. Minimizes the Frobenius norm of dot(data1, R) - data2, subject to dot(R.T, R) == I.
scaleSum of the singular values of dot(data1.T, data2).
procrustes_distance
procrustes_distance(mat1, mat2, *, n_permute = 5000, tail = 2, n_jobs = -1, random_state = None)

Test matrix similarity using Procrustes superposition.

Matrices need to match in size on their first dimension only, as the smaller matrix on the second dimension will be padded with zeros. After aligning two matrices using the Procrustes transformation, use the computed disparity between them (sum of squared error of elements) as a similarity metric. Shuffle the rows of one of the matrices and recompute the disparity to perform inference (Peres-Neto & Jackson, 2001).

Parameters:

NameTypeDescriptionDefault
mat1ndarray2d numpy array; must have same number of rows as mat2required
mat2ndarray1d or 2d numpy array; must have same number of rows as mat1required
n_permuteintnumber of permutation iterations to perform5000
tailint | str2‘two’ (two-tailed, default) or 1
n_jobsintThe number of CPUs to use to do permutation; default -1 (all)-1
random_stateint, np.random.RandomState, or Noneseed or generator for the permutation shuffling; default NoneNone

Returns:

NameTypeDescription
dictresults with keys similarity (float in [0, 1]) and p (permuted p-value)

random

Shared random-state utilities for deterministic parallel execution.

Key features
  • Deterministic parallelization: Pre-generates seeds for reproducible parallel execution

  • Consistent RNG patterns: Matches stats.py patterns for backward compatibility

  • Thread-safe design: Each parallel worker gets independent RandomState

Usage

These utilities are used in bootstrap and permutation tests to ensure deterministic behavior when using parallel processing.

Example: >>> from nltools.algorithms.random import generate_seeds >>> seeds = generate_seeds(100, random_state=42) >>> # Use seeds in parallel workers for deterministic results

Methods:

NameDescription
generate_bootstrap_indicesGenerate bootstrap indices deterministically for resampling.
generate_seedsGenerate random seeds for deterministic parallelization.
generate_sign_flipsGenerate random sign-flip matrix for one-sample permutation tests.

Methods

generate_bootstrap_indices
generate_bootstrap_indices(n_samples: int, n_bootstrap: int, random_state: int | None = None) -> np.ndarray

Generate bootstrap indices deterministically for resampling.

Uses the same pattern as permutation tests: pre-generate seeds for reproducible parallelization.

Parameters:

NameTypeDescriptionDefault
n_samplesintNumber of samples in original dataset.required
n_bootstrapintNumber of bootstrap iterations.required
random_stateint | NoneRandom seed for reproducibility. Defaults to None.None

Returns:

TypeDescription
ndarrayBootstrap indices with shape (n_bootstrap, n_samples). Each row contains indices sampled with replacement from [0, n_samples).

Examples:

>>> indices = generate_bootstrap_indices(100, 1000, random_state=42)
>>> indices.shape
(1000, 100)
>>> indices[0]  # First bootstrap sample indices
array([23, 45, 23, 67, ...])  # Some repeated (sampling with replacement)
Notes
  • Uses same seed generation pattern as permutation tests for consistency

  • Each bootstrap iteration gets independent RandomState for reproducibility

  • Sampling is with replacement (some indices may repeat)

generate_seeds
generate_seeds(n_permute: int, random_state: int | None = None) -> np.ndarray

Generate random seeds for deterministic parallelization.

Pre-generates unique seeds for each permutation/bootstrap iteration to ensure deterministic behavior across parallel workers.

Parameters:

NameTypeDescriptionDefault
n_permuteintNumber of permutations/bootstrap iterationsrequired
random_stateint | NoneRandom seed for reproducibilityNone

Returns:

TypeDescription
ndarrayArray of seeds with shape (n_permute,)

Examples:

>>> seeds = generate_seeds(100, random_state=42)
>>> seeds.shape
(100,)
>>> isinstance(seeds[0], (int, np.integer))
True
generate_sign_flips
generate_sign_flips(n_permute: int, n_samples: int, random_state: int | None = None) -> np.ndarray

Generate random sign-flip matrix for one-sample permutation tests.

Creates a matrix of random +1/-1 values for sign-flipping permutation tests. Each row represents one permutation, where each sample is randomly multiplied by +1 or -1 to create the null distribution.

This implementation matches the RNG pattern from the original nltools.algorithms one_sample_permutation for exact backward compatibility: each permutation gets an independent RandomState derived from a unique seed.

Parameters:

NameTypeDescriptionDefault
n_permuteintNumber of permutations to generaterequired
n_samplesintNumber of samples in the datasetrequired
random_stateint | NoneRandom seed for reproducibilityNone

Returns:

TypeDescription
ndarraySign-flip matrix of shape (n_permute, n_samples) containing only +1 and -1 values

Examples:

>>> sign_flips = generate_sign_flips(n_permute=100, n_samples=30, random_state=42)
>>> sign_flips.shape
(100, 30)
>>> np.all(np.isin(sign_flips, [-1, 1]))
True
Notes
  • Each permutation uses independent RandomState for stats.py compatibility

  • Values are uniformly sampled from {+1, -1} (matching stats.py order)

  • Returns NumPy array (device transfer handled by caller)

  • Memory cost: n_permute × n_samples × 1 byte (negligible for typical use)

regression

Standalone OLS regression on numpy arrays.

Pedagogical helper used in tutorials and notebooks where callers want a (b, se, t, p, df, res) tuple from a design matrix X and response Y without constructing a BrainData or Glm. For 4D neuroimaging data use BrainData.fit with model='glm'.

Methods:

NameDescription
regressFit an OLS regression of Y on X.

Methods

regress
regress(X, Y, *, method: str = 'ols', stats: str = 'full', tail: int | str = 2)

Fit an OLS regression of Y on X.

Does not add an intercept — include one in X explicitly. If Y is 2D, a separate regression is fit to each column.

Parameters:

NameTypeDescriptionDefault
XDesign matrix, shape (n_samples, n_regressors).required
YResponse, shape (n_samples,) or (n_samples, n_targets).required
methodstrOnly 'ols' is supported in v0.6.0. The legacy 'robust' and 'arma' methods were dropped; use statsmodels or a dedicated package if you need them.‘ols’
statsstr'full' returns the 6-tuple below; 'betas' returns just b; 'tstats' returns (b, t).‘full’
tailint | str2‘two’ (two-tailed, default) or 1

Returns:

NameTypeDescription
tuple(b, se, t, p, df, res) when stats='full':
- b: coefficients
- se: standard errors
- t: t-statistics
- p: p-values (per tail)
- df: residual degrees of freedom
- res: residuals

ridge

Ridge regression algorithms and utilities.

This package contains ridge regression implementations with GPU acceleration.

Features:

Quick Start

X = np.random.randn(100, 50) Y = np.random.randn(100, 10) result = solve_ridge_cv(X, Y, alphas=[0.1, 1.0, 10.0])

Methods:

NameDescription
cross_val_predict_ridgeHeld-out ridge predictions per CV fold under a (per-target) alpha.
generate_dirichlet_samplesGenerate samples from a Dirichlet distribution.
ridge_cvRidge regression with cross-validation for hyperparameter selection.
ridge_svdSolve ridge regression using Singular Value Decomposition.
solve_banded_ridge_cvSolve banded ridge regression with cross-validation using random search.
solve_ridge_cvSolve ridge regression with cross-validation.

Modules:

NameDescription
coreRidge regression algorithms using SVD decomposition.
solversRidge regression solvers with cross-validation.
utilsUtility functions for ridge regression.

Methods

cross_val_predict_ridge
cross_val_predict_ridge(X: np.ndarray, Y: np.ndarray, *, alphas: float | np.ndarray, cv: int | BaseCrossValidator = 5, fit_intercept: bool = False, n_targets_batch: int | None = None, n_alphas_batch: int | None = None, Y_in_cpu: bool = True, score_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None) -> dict[str, Any]

Held-out ridge predictions per CV fold under a (per-target) alpha.

For each fold, refits ridge with the supplied alpha (per-target or scalar) on the training fold and predicts the held-out fold. Targets sharing the same alpha share an SVD of the training fold via _refit_banded_ridge, so the cost scales with the number of unique alphas, not the number of targets.

Designed to be the BrainData CV layer’s source of held-out predictions when alpha selection has already been done by solve_ridge_cv: pass the selected per-voxel alphas back through here to get the fold-by-fold predictions and per-fold R² needed for cv_results_.

Parameters:

NameTypeDescriptionDefault
XndarrayFeature matrix of shape (n_samples, n_features).required
YndarrayTarget data of shape (n_samples, n_targets). 1D Y is promoted to (n_samples, 1).required
alphasfloat | ndarrayPer-target alpha array of shape (n_targets,) or a scalar (broadcast to every target).required
cvint | BaseCrossValidatorCross-validation strategy. If int, uses KFold with that many splits (no shuffling). Generators (e.g. KFold(5).split(X)) are rejected — pass the splitter object instead.5
fit_interceptboolIf True, center X and Y on the training fold’s mean per fold (sklearn convention) and add the intercept back so predictions live on the original Y scale.False
n_targets_batchint | NoneBatch size for targets during refit (for memory efficiency). If None, processes all targets at once.None
n_alphas_batchint | NoneBatch size for alphas. If None, processes all unique alphas at once.None
Y_in_cpuboolIf True, keep Y on CPU and transfer batches to backend device as needed (recommended for large neuroimaging Y).True
score_funcCallable [[ ndarray , ndarray ], ndarray ] | NonePer-fold scoring function (y_true, y_pred) -> per-target scores. If None, uses R² in NumPy on CPU (cheap at one fold’s size and decoupled from backend ops to avoid stray transfers).None
parallelstr | NoneBackend to use: “cpu”, “gpu”, or None.‘cpu’
max_gpu_memory_gbfloat | NoneGPU memory budget in GB (only used if parallel=“gpu”).None

Returns:

NameTypeDescription
dictdict [ str , Any ]Dictionary with keys: - ‘predictions’: (n_samples, n_targets) held-out per-target predictions on the original Y scale (CPU numpy). - ‘folds’: (n_samples,) int fold index per row (CPU numpy). - ‘scores’: (n_splits, n_targets) per-fold R² (or score_func) at the supplied alpha (CPU numpy). - ‘backend’: Backend used (for transparency).
generate_dirichlet_samples
generate_dirichlet_samples(n_samples: int, n_kernels: int, concentration: float | list[float] = [0.1, 1.0], random_state: int | None = None) -> np.ndarray

Generate samples from a Dirichlet distribution.

This function generates random samples from a Dirichlet distribution, which is used for sampling feature space weights (gamma) in banded ridge regression random search.

Parameters:

NameTypeDescriptionDefault
n_samplesintNumber of samples to generate.required
n_kernelsintNumber of dimensions (feature spaces) of the distribution.required
concentrationfloat | list [ float ]Concentration parameters of the Dirichlet distribution. - A value of 1 corresponds to uniform sampling over the simplex. - A value of infinity corresponds to equal weights. - If a list, samples cycle through the list. Defaults to [0.1, 1.0].[0.1, 1.0]
random_stateint | NoneRandom generator seed. Use an int for deterministic samples. Defaults to None.None

Returns:

TypeDescription
ndarraynp.ndarray: Dirichlet samples of shape (n_samples, n_kernels). Each row sums to 1 (lies on simplex).

Examples:

>>> # Generate 10 samples for 3 feature spaces
>>> gammas = generate_dirichlet_samples(10, 3, concentration=[0.1, 1.0])
>>> gammas.shape
(10, 3)
>>> # Each row sums to 1
>>> np.allclose(gammas.sum(axis=1), 1.0)
True
ridge_cv
ridge_cv(X: np.ndarray, y: np.ndarray, *, alphas: np.ndarray | None = None, cv: int | BaseCrossValidator = 5, fit_intercept: bool = False, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict

Ridge regression with cross-validation for hyperparameter selection.

Performs k-fold cross-validation to select the best alpha parameter, then fits a final model on all data using the selected alpha.

Parameters:

NameTypeDescriptionDefault
XndarrayTraining data features with shape (n_samples, n_features)required
yndarrayTarget values with shape (n_samples,) or (n_samples, n_targets)required
alphasndarrayArray of alpha values to try. If None, uses default range: np.logspace(-2, 4, 20) = [0.01, 0.015, ..., 10000]None
cvint or sklearn CV splitterNumber of folds (int) or an sklearn cross-validator (anything with .split(X) and .get_n_splits(), e.g. KFold(5, shuffle=True) or GroupKFold(8)). Splitters are honored for the actual fold iteration, so leave-one-run-out and shuffled-K-fold give different results from contiguous K-fold. Defaults to 5.5
fit_interceptboolIf True, center X and y on the training mean before fitting and recover the intercept after. The returned coef is on the centered scale; the recovered intercept is returned under the intercept key. Defaults to False.False
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU-only using NumPy (default) - “gpu”: GPU acceleration via PyTorch. Requires torch installed (raises ImportError otherwise); degrades to torch-CPU only when no GPU device is present. Use “auto” for torch-optional CPU fallback. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloatGPU memory budget in GB (only used if parallel=‘gpu’). Defaults to 4.0.None
random_stateintRandom seed (not currently used, kept for consistency). Defaults to None.None

Returns:

NameTypeDescription
dictdictDictionary containing:
- ‘alpha’ (float): Best alpha value selected by CV - ‘coef’ (np.ndarray): Coefficients using best alpha on full dataset - ‘cv_scores’ (np.ndarray): Cross-validation R**2 scores for each fold, alpha, and target with shape (n_folds, n_alphas, n_targets) - ‘backend’ (str): Backend used for computation

Examples:

>>> X = np.random.randn(100, 50)
>>> y = np.random.randn(100)
>>> result = ridge_cv(X, y, cv=3)
>>> result['alpha']  # Best alpha selected
1.0
>>> result['coef'].shape
(50,)
Notes
  • Uses R**2 (coefficient of determination) as the scoring metric

  • For multi-target regression, selects alpha that maximizes mean R**2 across targets

  • parallel=‘gpu’ requires torch installed; with torch present but no GPU device it runs on torch-CPU. It does not fall back to NumPy when torch is absent — use parallel=‘auto’ for that.

ridge_svd
ridge_svd(X: np.ndarray, y: np.ndarray, *, alpha: float = 1.0, parallel: str | None = None, max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> np.ndarray

Solve ridge regression using Singular Value Decomposition.

This function implements ridge regression using SVD, which provides numerical stability and efficiency for high-dimensional problems. The implementation is inspired by the himalaya library.

Algorithm

The ridge regression solution is: beta = (X.T @ X + alpha*I)^(-1) @ X.T @ y

Using SVD of X = U @ diag(s) @ V.T, this becomes: beta = V @ diag(s / (s**2 + alpha)) @ U.T @ y

This formulation avoids explicit matrix inversion and is numerically stable. The shrinkage factor s / (s**2 + alpha) regularizes small singular values.

Performance
  • Time complexity: O(n_samples × n_features × min(n_samples, n_features))

  • Space complexity: O(n_samples × n_features)

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

  • See solve_ridge_cv() for cross-validation with GPU support

Parameters:

NameTypeDescriptionDefault
XndarrayTraining data features with shape (n_samples, n_features)required
yndarrayTarget values with shape (n_samples,) or (n_samples, n_targets). Can be 1D for single-target or 2D for multi-targetrequired
alphafloatRegularization strength. Must be positive. Higher values increase regularization (shrink coefficients toward zero). Defaults to 1.0.1.0
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU-only using NumPy (default) - “gpu”: GPU acceleration via PyTorch. Requires torch installed (raises ImportError otherwise); degrades to torch-CPU only when no GPU device is present. Use “auto” for torch-optional CPU fallback. Defaults to None.None
max_gpu_memory_gbfloatGPU memory budget in GB (only used if parallel=‘gpu’). Defaults to 4.0.None
random_stateintRandom seed (not currently used, kept for consistency). Defaults to None.None

Returns:

TypeDescription
ndarraynp.ndarray: Ridge regression coefficients - shape (n_features,) for single-target regression - shape (n_features, n_targets) for multi-target regression

Examples:

>>> X = np.random.randn(100, 50)
>>> y = np.random.randn(100)
>>> beta = ridge_svd(X, y, alpha=1.0)
>>> beta.shape
(50,)
>>> # Multi-target regression
>>> Y = np.random.randn(100, 5)
>>> beta = ridge_svd(X, Y, alpha=1.0)
>>> beta.shape
(50, 5)
Notes
  • Time complexity: O(n_samples * n_features * min(n_samples, n_features))

  • Space complexity: O(n_samples * n_features)

  • For alpha→0, this reduces to ordinary least squares (OLS). Use alpha=1e-6 for OLS in practice (more numerically stable than alpha=0)

  • Supports both CPU (NumPy) and GPU (PyTorch) backends

  • See nltools.algorithms.ridge.solvers.solve_ridge_cv() for cross-validation

  • See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern

solve_banded_ridge_cv
solve_banded_ridge_cv(Xs: list[np.ndarray], Y: np.ndarray, *, n_iter: int | np.integer | np.ndarray = 100, concentration: float | list[float] = [0.1, 1.0], alphas: float | np.ndarray | list[float] = [0.1, 1.0, 10.0], cv: int | BaseCrossValidator = 5, local_alpha: bool = True, n_targets_batch: int | None = None, n_targets_batch_refit: int | None = None, n_alphas_batch: int | None = None, Y_in_cpu: bool = True, score_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, fit_intercept: bool = False, progress_bar: bool = False, conservative: bool = False, jitter_alphas: bool = False, return_weights: bool = True, diagonalize_method: str = 'svd', warn: bool = True, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict[str, Any]

Solve banded ridge regression with cross-validation using random search.

This function implements true banded/group ridge regression (as in Himalaya). It searches over feature space weights (gamma) sampled from a Dirichlet distribution, combined with alpha grid search.

Banded ridge (also called group ridge) applies different scaling weights per feature space: Z_i = sqrt(gamma_i) * X_i, then solves standard ridge regression on the scaled concatenated features. This allows optimizing the relative importance of different feature spaces.

The feature spaces are scaled by sqrt(gamma) for each gamma sample, then standard ridge regression is applied with alpha grid search.

Parameters:

NameTypeDescriptionDefault
Xslist [ ndarray ]Feature matrices for different feature spaces. Each array has shape (n_samples, n_features_i). All must have the same n_samples.required
YndarrayTarget data of shape (n_samples, n_targets).required
n_iterint | integer | ndarrayNumber of feature-space weights combination to search, or array of shape (n_iter, n_spaces). If an array is given, the solver uses it as the list of weights to try, instead of sampling from a Dirichlet distribution. Defaults to 100.100
concentrationfloat | list [ float ]Concentration parameters of the Dirichlet distribution. - A value of 1 corresponds to uniform sampling over the simplex. - A value of infinity corresponds to equal weights. - If a list, iteratively cycle through the list. Not used if n_iter is an array. Defaults to [0.1, 1.0].[0.1, 1.0]
alphasfloat | ndarray | list [ float ]Range of ridge regularization parameters to try. Can be float or array of shape (n_alphas,). Defaults to [0.1, 1.0, 10.0].[0.1, 1.0, 10.0]
cvint | BaseCrossValidatorCross-validation strategy. If int, uses KFold with that many splits. Defaults to 5.5
local_alphaboolIf True, select best alpha independently for each target. If False, select single best alpha for all targets. Defaults to True.True
n_targets_batchint | NoneBatch size for targets during CV (for memory efficiency). If None, processes all targets at once. Defaults to None.None
n_targets_batch_refitint | NoneBatch size for targets during refit. If None, uses n_targets_batch value. Defaults to None.None
n_alphas_batchint | NoneBatch size for alphas (for memory efficiency). If None, processes all alphas at once. Defaults to None.None
Y_in_cpuboolIf True, keep Y on CPU and transfer batches to GPU as needed. This prevents OOM when Y is large (e.g., 300k voxels). Defaults to True (recommended for neuroimaging).True
score_funcCallable [[ ndarray , ndarray ], ndarray ] | NoneScoring function (y_true, y_pred) -> scores. If None, uses R² score. Defaults to None.None
fit_interceptboolWhether to fit an intercept. If False, X and Y should be centered. Defaults to False.False
progress_barboolWhether to display progress bar (requires tqdm). Defaults to False.False
conservativeboolIf True, select largest alpha within 1 std of best score. Defaults to False.False
jitter_alphasboolIf True, alphas range is slightly jittered for each gamma. Defaults to False.False
return_weightsboolWhether to refit on the entire dataset and return the weights. Defaults to True.True
diagonalize_methodstrMethod used to diagonalize the features. Currently only “svd” is supported. Defaults to “svd”.‘svd’
warnboolIf True, warn if the number of samples is smaller than the number of features. Defaults to True.True
parallelstr | NoneBackend to use: “cpu”, “gpu”, or None. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloat | NoneGPU memory budget in GB (only used if parallel=“gpu”). Defaults to 4.0.None
random_stateint | NoneRandom generator seed. Use an int for deterministic search. Defaults to None.None

Returns:

NameTypeDescription
dictdict [ str , Any ]Dictionary with keys: - ‘deltas’: Best log feature-space weights for each target, shape (n_spaces, n_targets). deltas = log(gamma / alpha), where gamma are the feature space weights. - ‘cv_scores’: Cross-validation scores per iteration, averaged over splits, for the best alpha, shape (n_iter, n_targets). Always returned on CPU (numpy array). - ‘coefs’: Ridge coefficients refit on entire dataset using best hyperparameters, shape (n_features_total, n_targets), or None if return_weights=False. Always returned on CPU (numpy array). - ‘intercept’: Intercept of shape (n_targets,), or None if fit_intercept=False or return_weights=False. - ‘backend’: Backend used (for transparency).

Examples:

>>> # Multiple feature spaces (banded ridge with random search)
>>> X1 = np.random.randn(100, 30)  # First feature space
>>> X2 = np.random.randn(100, 20)  # Second feature space
>>> Y = np.random.randn(100, 10)
>>> result = solve_banded_ridge_cv(
...     [X1, X2], Y, n_iter=50, alphas=[0.1, 1.0, 10.0]
... )
>>> deltas = result['deltas']
>>> coefs = result['coefs']
>>> scores = result['cv_scores']
Notes

This implements true banded/group ridge regression (as in Himalaya’s solve_group_ridge_random_search) with:

  • Dirichlet sampling for feature space weights (gamma)

  • Scaling each feature space by sqrt(gamma) for each gamma sample

  • Cross-validation with alpha grid search

  • Per-target selection of best gamma and alpha combination

This is the correct implementation of banded/group ridge regression, which allows different scaling weights per feature space. For single feature space ridge regression, use solve_ridge_cv instead.

Algorithm details:

  • Random search: Samples gamma weights from Dirichlet distribution

  • Banded ridge: Scales each feature space by sqrt(gamma_i), then solves standard ridge

  • Cross-validation: Evaluates each (gamma, alpha) combination via k-fold CV

  • Best selection: Chooses (gamma, alpha) that maximizes CV score per target

Memory efficiency strategies (Principle 2: automatic memory efficiency):

  • Generator pattern for alpha batching (via _decompose_ridge): Processes alphas in batches to avoid storing all resolution matrices simultaneously

  • Target batching (n_targets_batch): Processes targets in chunks to fit GPU memory

  • Y_in_cpu strategy: Keeps large Y on CPU, transfers only batches needed for computation

  • Immediate cleanup with del statements: Explicitly frees memory after each batch

Performance:

  • Time complexity: O(n_iter × n_splits × (n_alphas_batch × n_features^2 + n_targets_batch × n_samples))

  • Memory complexity: O(n_features × n_targets_batch) per batch

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern details. See docs/development/ridge-internals.md for detailed algorithm explanation.

solve_ridge_cv
solve_ridge_cv(X: np.ndarray, Y: np.ndarray, *, alphas: float | np.ndarray | list[float] = [0.1, 1.0, 10.0], cv: int | BaseCrossValidator = 5, local_alpha: bool = True, n_targets_batch: int | None = None, n_targets_batch_refit: int | None = None, n_alphas_batch: int | None = None, Y_in_cpu: bool = True, score_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, fit_intercept: bool = False, progress_bar: bool = False, conservative: bool = False, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict[str, Any]

Solve ridge regression with cross-validation.

This function solves ridge regression for a single feature space with cross-validation for hyperparameter selection.

Parameters:

NameTypeDescriptionDefault
XndarrayFeature matrix of shape (n_samples, n_features).required
YndarrayTarget data of shape (n_samples, n_targets).required
alphasfloat | ndarray | list [ float ]Ridge regularization parameters to try. Defaults to [0.1, 1.0, 10.0].[0.1, 1.0, 10.0]
cvint | BaseCrossValidatorCross-validation strategy. If int, uses KFold with that many splits. Defaults to 5.5
local_alphaboolIf True, select best alpha independently for each target. If False, select single best alpha for all targets. Defaults to True.True
n_targets_batchint | NoneBatch size for targets during CV (for memory efficiency). If None, processes all targets at once. Defaults to None.None
n_targets_batch_refitint | NoneBatch size for targets during refit. If None, uses n_targets_batch value. Defaults to None.None
n_alphas_batchint | NoneBatch size for alphas (for memory efficiency). If None, processes all alphas at once. Defaults to None.None
Y_in_cpuboolIf True, keep Y on CPU and transfer batches to GPU as needed. This prevents OOM when Y is large (e.g., 300k voxels). Defaults to True (recommended for neuroimaging).True
score_funcCallable [[ ndarray , ndarray ], ndarray ] | NoneScoring function (y_true, y_pred) -> scores. If None, uses R² score. Defaults to None.None
fit_interceptboolWhether to fit an intercept. If False, X and Y should be centered. Defaults to False.False
progress_barboolWhether to display progress bar (requires tqdm). Defaults to False.False
conservativeboolIf True, select largest alpha within 1 std of best score. Defaults to False.False
parallelstr | NoneBackend to use: “cpu”, “gpu”, or None. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloat | NoneGPU memory budget in GB (only used if parallel=“gpu”). Defaults to 4.0.None
random_stateint | NoneRandom generator seed. Use an int for deterministic search. Defaults to None.None

Returns:

NameTypeDescription
dictdict [ str , Any ]Dictionary with keys: - ‘best_alphas’: Selected best alpha for each target (or same alpha repeated if local_alpha=False), shape (n_targets,). - ‘coefs’: Ridge coefficients refit on entire dataset using best alphas, shape (n_features, n_targets). Always returned on CPU (numpy array). - ‘cv_scores’: Cross-validation scores for best alphas, shape (n_splits, n_alphas, n_targets). Always returned on CPU (numpy array). - ‘intercept’: Per-target intercept of shape (n_targets,). Only present when fit_intercept=True. - ‘backend’: Backend used (for transparency).

Examples:

>>> X = np.random.randn(100, 50)
>>> Y = np.random.randn(100, 10)
>>> result = solve_ridge_cv(X, Y, alphas=[0.1, 1.0, 10.0])
>>> alphas = result['best_alphas']
>>> coefs = result['coefs']
>>> scores = result['cv_scores']
Notes

This is the efficient implementation for single feature space ridge regression with cross-validation. For multiple feature spaces (banded/group ridge), use solve_banded_ridge_cv instead.

Algorithm details:

  • Cross-validation: k-fold CV evaluates each alpha value

  • Alpha selection: Chooses best alpha per target (or globally if local_alpha=False)

  • Refit: Fits final model on full dataset using best alpha(s)

Memory efficiency strategies (Principle 2: automatic memory efficiency):

  • Generator pattern for alpha batching (via _decompose_ridge): Processes alphas in batches to avoid storing all resolution matrices simultaneously

  • Target batching (n_targets_batch): Processes targets in chunks to fit GPU memory

  • Y_in_cpu strategy: Keeps large Y on CPU, transfers only batches needed for computation

  • Immediate cleanup with del statements: Explicitly frees memory after each batch

Performance:

  • Time complexity: O(n_splits × (n_alphas_batch × n_features^2 + n_targets_batch × n_samples))

  • Memory complexity: O(n_features × n_targets_batch) per batch

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern details. See docs/development/ridge-internals.md for detailed algorithm explanation.

Modules

core

Ridge regression algorithms using SVD decomposition.

This module implements ridge regression using Singular Value Decomposition (SVD), which provides numerical stability and efficiency for high-dimensional problems.

Algorithm approach

Why SVD vs direct inversion: - Direct inversion: beta = (X.T @ X + alpha*I)^(-1) @ X.T @ y - SVD approach: X = U @ diag(s) @ V.T, then beta = V @ diag(s / (s**2 + alpha)) @ U.T @ y - Benefits: Avoids explicit matrix inversion (numerically stable), efficient for rank-deficient X - Performance: O(n_samples × n_features × min(n_samples, n_features)) for SVD

Backend choice trade-offs
  • NumPy (CPU): Default, reliable, works everywhere

  • PyTorch CPU: Similar performance to NumPy, useful for consistent API

  • PyTorch GPU: ~10-100× speedup for large problems (n_features > 10K), requires GPU

Cross-references
  • See nltools.algorithms.ridge.solvers.solve_ridge_cv() for GPU-accelerated cross-validation

  • See nltools.algorithms.ridge.utils._decompose_ridge() for generator-based batching pattern

  • See docs/development/ridge-internals.md for detailed algorithm explanation

Inspired by the himalaya library’s efficient SVD-based ridge regression approach. himalaya is licensed under BSD-3-Clause: https://github.com/gallantlab/himalaya

References
  • Huth, A. G., et al. (2016). “Natural speech reveals the semantic maps that tile human cerebral cortex.” Nature, 532(7600), 453-458.

  • himalaya documentation: https://gallantlab.github.io/himalaya/

Methods:

NameDescription
ridge_cvRidge regression with cross-validation for hyperparameter selection.
ridge_svdSolve ridge regression using Singular Value Decomposition.

####### Functions##

ridge_cv
ridge_cv(X: np.ndarray, y: np.ndarray, *, alphas: np.ndarray | None = None, cv: int | BaseCrossValidator = 5, fit_intercept: bool = False, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict

Ridge regression with cross-validation for hyperparameter selection.

Performs k-fold cross-validation to select the best alpha parameter, then fits a final model on all data using the selected alpha.

Parameters:

NameTypeDescriptionDefault
XndarrayTraining data features with shape (n_samples, n_features)required
yndarrayTarget values with shape (n_samples,) or (n_samples, n_targets)required
alphasndarrayArray of alpha values to try. If None, uses default range: np.logspace(-2, 4, 20) = [0.01, 0.015, ..., 10000]None
cvint or sklearn CV splitterNumber of folds (int) or an sklearn cross-validator (anything with .split(X) and .get_n_splits(), e.g. KFold(5, shuffle=True) or GroupKFold(8)). Splitters are honored for the actual fold iteration, so leave-one-run-out and shuffled-K-fold give different results from contiguous K-fold. Defaults to 5.5
fit_interceptboolIf True, center X and y on the training mean before fitting and recover the intercept after. The returned coef is on the centered scale; the recovered intercept is returned under the intercept key. Defaults to False.False
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU-only using NumPy (default) - “gpu”: GPU acceleration via PyTorch. Requires torch installed (raises ImportError otherwise); degrades to torch-CPU only when no GPU device is present. Use “auto” for torch-optional CPU fallback. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloatGPU memory budget in GB (only used if parallel=‘gpu’). Defaults to 4.0.None
random_stateintRandom seed (not currently used, kept for consistency). Defaults to None.None

Parameters:

NameTypeDescriptionDefault
XndarrayTraining data features with shape (n_samples, n_features)required
yndarrayTarget values with shape (n_samples,) or (n_samples, n_targets). Can be 1D for single-target or 2D for multi-targetrequired
alphafloatRegularization strength. Must be positive. Higher values increase regularization (shrink coefficients toward zero). Defaults to 1.0.1.0
parallelstrExecution backend. - None: Single-threaded NumPy (debugging/small problems) - “cpu”: CPU-only using NumPy (default) - “gpu”: GPU acceleration via PyTorch. Requires torch installed (raises ImportError otherwise); degrades to torch-CPU only when no GPU device is present. Use “auto” for torch-optional CPU fallback. Defaults to None.None
max_gpu_memory_gbfloatGPU memory budget in GB (only used if parallel=‘gpu’). Defaults to 4.0.None
random_stateintRandom seed (not currently used, kept for consistency). Defaults to None.None

Returns:

NameTypeDescription
dictdictDictionary containing:
- ‘alpha’ (float): Best alpha value selected by CV - ‘coef’ (np.ndarray): Coefficients using best alpha on full dataset - ‘cv_scores’ (np.ndarray): Cross-validation R**2 scores for each fold, alpha, and target with shape (n_folds, n_alphas, n_targets) - ‘backend’ (str): Backend used for computation

Examples:

>>> X = np.random.randn(100, 50)
>>> y = np.random.randn(100)
>>> result = ridge_cv(X, y, cv=3)
>>> result['alpha']  # Best alpha selected
1.0
>>> result['coef'].shape
(50,)
Notes
  • Uses R**2 (coefficient of determination) as the scoring metric

  • For multi-target regression, selects alpha that maximizes mean R**2 across targets

  • parallel=‘gpu’ requires torch installed; with torch present but no GPU device it runs on torch-CPU. It does not fall back to NumPy when torch is absent — use parallel=‘auto’ for that.

######## ridge_svd

ridge_svd(X: np.ndarray, y: np.ndarray, *, alpha: float = 1.0, parallel: str | None = None, max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> np.ndarray

Solve ridge regression using Singular Value Decomposition.

This function implements ridge regression using SVD, which provides numerical stability and efficiency for high-dimensional problems. The implementation is inspired by the himalaya library.

Algorithm

The ridge regression solution is: beta = (X.T @ X + alpha*I)^(-1) @ X.T @ y

Using SVD of X = U @ diag(s) @ V.T, this becomes: beta = V @ diag(s / (s**2 + alpha)) @ U.T @ y

This formulation avoids explicit matrix inversion and is numerically stable. The shrinkage factor s / (s**2 + alpha) regularizes small singular values.

Performance
  • Time complexity: O(n_samples × n_features × min(n_samples, n_features))

  • Space complexity: O(n_samples × n_features)

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

  • See solve_ridge_cv() for cross-validation with GPU support

Returns:

TypeDescription
ndarraynp.ndarray: Ridge regression coefficients - shape (n_features,) for single-target regression - shape (n_features, n_targets) for multi-target regression

Examples:

>>> X = np.random.randn(100, 50)
>>> y = np.random.randn(100)
>>> beta = ridge_svd(X, y, alpha=1.0)
>>> beta.shape
(50,)
>>> # Multi-target regression
>>> Y = np.random.randn(100, 5)
>>> beta = ridge_svd(X, Y, alpha=1.0)
>>> beta.shape
(50, 5)
Notes
  • Time complexity: O(n_samples * n_features * min(n_samples, n_features))

  • Space complexity: O(n_samples * n_features)

  • For alpha→0, this reduces to ordinary least squares (OLS). Use alpha=1e-6 for OLS in practice (more numerically stable than alpha=0)

  • Supports both CPU (NumPy) and GPU (PyTorch) backends

  • See nltools.algorithms.ridge.solvers.solve_ridge_cv() for cross-validation

  • See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern

solvers

Ridge regression solvers with cross-validation.

Implements banded ridge regression (multiple feature spaces) and regular ridge regression (single feature space) with cross-validation for hyperparameter selection.

Follows himalaya’s implementation patterns:

Methods:

NameDescription
cross_val_predict_ridgeHeld-out ridge predictions per CV fold under a (per-target) alpha.
solve_banded_ridge_cvSolve banded ridge regression with cross-validation using random search.
solve_ridge_cvSolve ridge regression with cross-validation.

####### Functions##

cross_val_predict_ridge
cross_val_predict_ridge(X: np.ndarray, Y: np.ndarray, *, alphas: float | np.ndarray, cv: int | BaseCrossValidator = 5, fit_intercept: bool = False, n_targets_batch: int | None = None, n_alphas_batch: int | None = None, Y_in_cpu: bool = True, score_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None) -> dict[str, Any]

Held-out ridge predictions per CV fold under a (per-target) alpha.

For each fold, refits ridge with the supplied alpha (per-target or scalar) on the training fold and predicts the held-out fold. Targets sharing the same alpha share an SVD of the training fold via _refit_banded_ridge, so the cost scales with the number of unique alphas, not the number of targets.

Designed to be the BrainData CV layer’s source of held-out predictions when alpha selection has already been done by solve_ridge_cv: pass the selected per-voxel alphas back through here to get the fold-by-fold predictions and per-fold R² needed for cv_results_.

Parameters:

NameTypeDescriptionDefault
XndarrayFeature matrix of shape (n_samples, n_features).required
YndarrayTarget data of shape (n_samples, n_targets). 1D Y is promoted to (n_samples, 1).required
alphasfloat | ndarrayPer-target alpha array of shape (n_targets,) or a scalar (broadcast to every target).required
cvint | BaseCrossValidatorCross-validation strategy. If int, uses KFold with that many splits (no shuffling). Generators (e.g. KFold(5).split(X)) are rejected — pass the splitter object instead.5
fit_interceptboolIf True, center X and Y on the training fold’s mean per fold (sklearn convention) and add the intercept back so predictions live on the original Y scale.False
n_targets_batchint | NoneBatch size for targets during refit (for memory efficiency). If None, processes all targets at once.None
n_alphas_batchint | NoneBatch size for alphas. If None, processes all unique alphas at once.None
Y_in_cpuboolIf True, keep Y on CPU and transfer batches to backend device as needed (recommended for large neuroimaging Y).True
score_funcCallable [[ ndarray , ndarray ], ndarray ] | NonePer-fold scoring function (y_true, y_pred) -> per-target scores. If None, uses R² in NumPy on CPU (cheap at one fold’s size and decoupled from backend ops to avoid stray transfers).None
parallelstr | NoneBackend to use: “cpu”, “gpu”, or None.‘cpu’
max_gpu_memory_gbfloat | NoneGPU memory budget in GB (only used if parallel=“gpu”).None

Parameters:

NameTypeDescriptionDefault
Xslist [ ndarray ]Feature matrices for different feature spaces. Each array has shape (n_samples, n_features_i). All must have the same n_samples.required
YndarrayTarget data of shape (n_samples, n_targets).required
n_iterint | integer | ndarrayNumber of feature-space weights combination to search, or array of shape (n_iter, n_spaces). If an array is given, the solver uses it as the list of weights to try, instead of sampling from a Dirichlet distribution. Defaults to 100.100
concentrationfloat | list [ float ]Concentration parameters of the Dirichlet distribution. - A value of 1 corresponds to uniform sampling over the simplex. - A value of infinity corresponds to equal weights. - If a list, iteratively cycle through the list. Not used if n_iter is an array. Defaults to [0.1, 1.0].[0.1, 1.0]
alphasfloat | ndarray | list [ float ]Range of ridge regularization parameters to try. Can be float or array of shape (n_alphas,). Defaults to [0.1, 1.0, 10.0].[0.1, 1.0, 10.0]
cvint | BaseCrossValidatorCross-validation strategy. If int, uses KFold with that many splits. Defaults to 5.5
local_alphaboolIf True, select best alpha independently for each target. If False, select single best alpha for all targets. Defaults to True.True
n_targets_batchint | NoneBatch size for targets during CV (for memory efficiency). If None, processes all targets at once. Defaults to None.None
n_targets_batch_refitint | NoneBatch size for targets during refit. If None, uses n_targets_batch value. Defaults to None.None
n_alphas_batchint | NoneBatch size for alphas (for memory efficiency). If None, processes all alphas at once. Defaults to None.None
Y_in_cpuboolIf True, keep Y on CPU and transfer batches to GPU as needed. This prevents OOM when Y is large (e.g., 300k voxels). Defaults to True (recommended for neuroimaging).True
score_funcCallable [[ ndarray , ndarray ], ndarray ] | NoneScoring function (y_true, y_pred) -> scores. If None, uses R² score. Defaults to None.None
fit_interceptboolWhether to fit an intercept. If False, X and Y should be centered. Defaults to False.False
progress_barboolWhether to display progress bar (requires tqdm). Defaults to False.False
conservativeboolIf True, select largest alpha within 1 std of best score. Defaults to False.False
jitter_alphasboolIf True, alphas range is slightly jittered for each gamma. Defaults to False.False
return_weightsboolWhether to refit on the entire dataset and return the weights. Defaults to True.True
diagonalize_methodstrMethod used to diagonalize the features. Currently only “svd” is supported. Defaults to “svd”.‘svd’
warnboolIf True, warn if the number of samples is smaller than the number of features. Defaults to True.True
parallelstr | NoneBackend to use: “cpu”, “gpu”, or None. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloat | NoneGPU memory budget in GB (only used if parallel=“gpu”). Defaults to 4.0.None
random_stateint | NoneRandom generator seed. Use an int for deterministic search. Defaults to None.None

Parameters:

NameTypeDescriptionDefault
XndarrayFeature matrix of shape (n_samples, n_features).required
YndarrayTarget data of shape (n_samples, n_targets).required
alphasfloat | ndarray | list [ float ]Ridge regularization parameters to try. Defaults to [0.1, 1.0, 10.0].[0.1, 1.0, 10.0]
cvint | BaseCrossValidatorCross-validation strategy. If int, uses KFold with that many splits. Defaults to 5.5
local_alphaboolIf True, select best alpha independently for each target. If False, select single best alpha for all targets. Defaults to True.True
n_targets_batchint | NoneBatch size for targets during CV (for memory efficiency). If None, processes all targets at once. Defaults to None.None
n_targets_batch_refitint | NoneBatch size for targets during refit. If None, uses n_targets_batch value. Defaults to None.None
n_alphas_batchint | NoneBatch size for alphas (for memory efficiency). If None, processes all alphas at once. Defaults to None.None
Y_in_cpuboolIf True, keep Y on CPU and transfer batches to GPU as needed. This prevents OOM when Y is large (e.g., 300k voxels). Defaults to True (recommended for neuroimaging).True
score_funcCallable [[ ndarray , ndarray ], ndarray ] | NoneScoring function (y_true, y_pred) -> scores. If None, uses R² score. Defaults to None.None
fit_interceptboolWhether to fit an intercept. If False, X and Y should be centered. Defaults to False.False
progress_barboolWhether to display progress bar (requires tqdm). Defaults to False.False
conservativeboolIf True, select largest alpha within 1 std of best score. Defaults to False.False
parallelstr | NoneBackend to use: “cpu”, “gpu”, or None. Defaults to “cpu”.‘cpu’
max_gpu_memory_gbfloat | NoneGPU memory budget in GB (only used if parallel=“gpu”). Defaults to 4.0.None
random_stateint | NoneRandom generator seed. Use an int for deterministic search. Defaults to None.None

Returns:

NameTypeDescription
dictdict [ str , Any ]Dictionary with keys: - ‘predictions’: (n_samples, n_targets) held-out per-target predictions on the original Y scale (CPU numpy). - ‘folds’: (n_samples,) int fold index per row (CPU numpy). - ‘scores’: (n_splits, n_targets) per-fold R² (or score_func) at the supplied alpha (CPU numpy). - ‘backend’: Backend used (for transparency).

######## solve_banded_ridge_cv

solve_banded_ridge_cv(Xs: list[np.ndarray], Y: np.ndarray, *, n_iter: int | np.integer | np.ndarray = 100, concentration: float | list[float] = [0.1, 1.0], alphas: float | np.ndarray | list[float] = [0.1, 1.0, 10.0], cv: int | BaseCrossValidator = 5, local_alpha: bool = True, n_targets_batch: int | None = None, n_targets_batch_refit: int | None = None, n_alphas_batch: int | None = None, Y_in_cpu: bool = True, score_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, fit_intercept: bool = False, progress_bar: bool = False, conservative: bool = False, jitter_alphas: bool = False, return_weights: bool = True, diagonalize_method: str = 'svd', warn: bool = True, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict[str, Any]

Solve banded ridge regression with cross-validation using random search.

This function implements true banded/group ridge regression (as in Himalaya). It searches over feature space weights (gamma) sampled from a Dirichlet distribution, combined with alpha grid search.

Banded ridge (also called group ridge) applies different scaling weights per feature space: Z_i = sqrt(gamma_i) * X_i, then solves standard ridge regression on the scaled concatenated features. This allows optimizing the relative importance of different feature spaces.

The feature spaces are scaled by sqrt(gamma) for each gamma sample, then standard ridge regression is applied with alpha grid search.

Returns:

NameTypeDescription
dictdict [ str , Any ]Dictionary with keys: - ‘deltas’: Best log feature-space weights for each target, shape (n_spaces, n_targets). deltas = log(gamma / alpha), where gamma are the feature space weights. - ‘cv_scores’: Cross-validation scores per iteration, averaged over splits, for the best alpha, shape (n_iter, n_targets). Always returned on CPU (numpy array). - ‘coefs’: Ridge coefficients refit on entire dataset using best hyperparameters, shape (n_features_total, n_targets), or None if return_weights=False. Always returned on CPU (numpy array). - ‘intercept’: Intercept of shape (n_targets,), or None if fit_intercept=False or return_weights=False. - ‘backend’: Backend used (for transparency).

Examples:

>>> # Multiple feature spaces (banded ridge with random search)
>>> X1 = np.random.randn(100, 30)  # First feature space
>>> X2 = np.random.randn(100, 20)  # Second feature space
>>> Y = np.random.randn(100, 10)
>>> result = solve_banded_ridge_cv(
...     [X1, X2], Y, n_iter=50, alphas=[0.1, 1.0, 10.0]
... )
>>> deltas = result['deltas']
>>> coefs = result['coefs']
>>> scores = result['cv_scores']
Notes

This implements true banded/group ridge regression (as in Himalaya’s solve_group_ridge_random_search) with:

  • Dirichlet sampling for feature space weights (gamma)

  • Scaling each feature space by sqrt(gamma) for each gamma sample

  • Cross-validation with alpha grid search

  • Per-target selection of best gamma and alpha combination

This is the correct implementation of banded/group ridge regression, which allows different scaling weights per feature space. For single feature space ridge regression, use solve_ridge_cv instead.

Algorithm details:

  • Random search: Samples gamma weights from Dirichlet distribution

  • Banded ridge: Scales each feature space by sqrt(gamma_i), then solves standard ridge

  • Cross-validation: Evaluates each (gamma, alpha) combination via k-fold CV

  • Best selection: Chooses (gamma, alpha) that maximizes CV score per target

Memory efficiency strategies (Principle 2: automatic memory efficiency):

  • Generator pattern for alpha batching (via _decompose_ridge): Processes alphas in batches to avoid storing all resolution matrices simultaneously

  • Target batching (n_targets_batch): Processes targets in chunks to fit GPU memory

  • Y_in_cpu strategy: Keeps large Y on CPU, transfers only batches needed for computation

  • Immediate cleanup with del statements: Explicitly frees memory after each batch

Performance:

  • Time complexity: O(n_iter × n_splits × (n_alphas_batch × n_features^2 + n_targets_batch × n_samples))

  • Memory complexity: O(n_features × n_targets_batch) per batch

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern details. See docs/development/ridge-internals.md for detailed algorithm explanation.

######## solve_ridge_cv

solve_ridge_cv(X: np.ndarray, Y: np.ndarray, *, alphas: float | np.ndarray | list[float] = [0.1, 1.0, 10.0], cv: int | BaseCrossValidator = 5, local_alpha: bool = True, n_targets_batch: int | None = None, n_targets_batch_refit: int | None = None, n_alphas_batch: int | None = None, Y_in_cpu: bool = True, score_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, fit_intercept: bool = False, progress_bar: bool = False, conservative: bool = False, parallel: str | None = 'cpu', max_gpu_memory_gb: float | None = None, random_state: int | None = None) -> dict[str, Any]

Solve ridge regression with cross-validation.

This function solves ridge regression for a single feature space with cross-validation for hyperparameter selection.

Returns:

NameTypeDescription
dictdict [ str , Any ]Dictionary with keys: - ‘best_alphas’: Selected best alpha for each target (or same alpha repeated if local_alpha=False), shape (n_targets,). - ‘coefs’: Ridge coefficients refit on entire dataset using best alphas, shape (n_features, n_targets). Always returned on CPU (numpy array). - ‘cv_scores’: Cross-validation scores for best alphas, shape (n_splits, n_alphas, n_targets). Always returned on CPU (numpy array). - ‘intercept’: Per-target intercept of shape (n_targets,). Only present when fit_intercept=True. - ‘backend’: Backend used (for transparency).

Examples:

>>> X = np.random.randn(100, 50)
>>> Y = np.random.randn(100, 10)
>>> result = solve_ridge_cv(X, Y, alphas=[0.1, 1.0, 10.0])
>>> alphas = result['best_alphas']
>>> coefs = result['coefs']
>>> scores = result['cv_scores']
Notes

This is the efficient implementation for single feature space ridge regression with cross-validation. For multiple feature spaces (banded/group ridge), use solve_banded_ridge_cv instead.

Algorithm details:

  • Cross-validation: k-fold CV evaluates each alpha value

  • Alpha selection: Chooses best alpha per target (or globally if local_alpha=False)

  • Refit: Fits final model on full dataset using best alpha(s)

Memory efficiency strategies (Principle 2: automatic memory efficiency):

  • Generator pattern for alpha batching (via _decompose_ridge): Processes alphas in batches to avoid storing all resolution matrices simultaneously

  • Target batching (n_targets_batch): Processes targets in chunks to fit GPU memory

  • Y_in_cpu strategy: Keeps large Y on CPU, transfers only batches needed for computation

  • Immediate cleanup with del statements: Explicitly frees memory after each batch

Performance:

  • Time complexity: O(n_splits × (n_alphas_batch × n_features^2 + n_targets_batch × n_samples))

  • Memory complexity: O(n_features × n_targets_batch) per batch

  • GPU acceleration: ~10-100× speedup for large problems (n_features > 10K)

See nltools.algorithms.ridge.utils._decompose_ridge() for generator pattern details. See docs/development/ridge-internals.md for detailed algorithm explanation.

utils

Utility functions for ridge regression.

Contains helper functions for batching, decomposition, and other utilities following himalaya’s implementation patterns.

Methods:

NameDescription
generate_dirichlet_samplesGenerate samples from a Dirichlet distribution.

####### Classes

####### Functions##

generate_dirichlet_samples
generate_dirichlet_samples(n_samples: int, n_kernels: int, concentration: float | list[float] = [0.1, 1.0], random_state: int | None = None) -> np.ndarray

Generate samples from a Dirichlet distribution.

This function generates random samples from a Dirichlet distribution, which is used for sampling feature space weights (gamma) in banded ridge regression random search.

Parameters:

NameTypeDescriptionDefault
n_samplesintNumber of samples to generate.required
n_kernelsintNumber of dimensions (feature spaces) of the distribution.required
concentrationfloat | list [ float ]Concentration parameters of the Dirichlet distribution. - A value of 1 corresponds to uniform sampling over the simplex. - A value of infinity corresponds to equal weights. - If a list, samples cycle through the list. Defaults to [0.1, 1.0].[0.1, 1.0]
random_stateint | NoneRandom generator seed. Use an int for deterministic samples. Defaults to None.None

Returns:

TypeDescription
ndarraynp.ndarray: Dirichlet samples of shape (n_samples, n_kernels). Each row sums to 1 (lies on simplex).

Examples:

>>> # Generate 10 samples for 3 feature spaces
>>> gammas = generate_dirichlet_samples(10, 3, concentration=[0.1, 1.0])
>>> gammas.shape
(10, 3)
>>> # Each row sums to 1
>>> np.allclose(gammas.sum(axis=1), 1.0)
True

shape_utils

Shared shape-manipulation helpers for triangle extraction and symmetric permutation.

Key functions
  • extract_triangle_elements: Extract upper/lower triangle from matrices

  • permute_matrix_symmetric: Apply symmetric permutation (key for matrix tests)

Usage

These utilities are used throughout the algorithms module for consistent shape handling and matrix operations.

Example: >>> from nltools.algorithms.shape_utils import extract_triangle_elements >>> matrix = np.arange(16).reshape(4, 4) >>> upper = extract_triangle_elements(matrix, triangle=‘upper’)

Methods:

NameDescription
extract_triangle_elementsExtract triangle elements from square matrix.
permute_matrix_symmetricApply symmetric row+column permutation to square matrix.

Methods

extract_triangle_elements
extract_triangle_elements(matrix: np.ndarray, triangle: str = 'upper', include_diag: bool = False) -> np.ndarray

Extract triangle elements from square matrix.

Parameters:

NameTypeDescriptionDefault
matrixndarraySquare matrix (n×n)required
trianglestrWhich triangle [‘upper’‘lower’
include_diagboolInclude diagonal (only for ‘full’)False

Returns:

TypeDescription
ndarrayExtracted elements as 1D array

Examples:

>>> matrix = np.arange(16).reshape(4, 4)
>>> extract_triangle_elements(matrix, triangle='upper')
array([ 1,  2,  3,  6,  7, 11])
permute_matrix_symmetric
permute_matrix_symmetric(matrix: np.ndarray, permutation: np.ndarray) -> np.ndarray

Apply symmetric row+column permutation to square matrix.

This is the KEY operation for matrix permutation tests. It reorders both rows AND columns together, preserving matrix structure while destroying correlation between matrices.

Parameters:

NameTypeDescriptionDefault
matrixndarraySquare matrix (n×n)required
permutationndarrayPermutation indices (length n)required

Returns:

TypeDescription
ndarraySymmetrically permuted matrix (n×n)

Examples:

>>> matrix = np.arange(9).reshape(3, 3)
>>> perm = np.array([2, 0, 1])  # Rotate indices
>>> permute_matrix_symmetric(matrix, perm)
array([[8, 6, 7],
       [2, 0, 1],
       [5, 3, 4]])

signal

Temporal signal processing — resampling, filtering, and basis functions.

Methods:

NameDescription
calc_bpmCalculate instantaneous BPM from beat to beat interval.
downsampleDownsample a Polars DataFrame/Series to a new target frequency or number of samples using averaging.
make_cosine_basisCreate basis functions for a discrete cosine transform.
upsampleUpsample a Polars DataFrame/Series to a new target frequency or number of samples using interpolation.

Methods

calc_bpm
calc_bpm(beat_interval, sampling_freq)

Calculate instantaneous BPM from beat to beat interval.

Parameters:

NameTypeDescriptionDefault
beat_interval(int) number of samples in between each beat (typically R-R Interval)required
sampling_freq(float) sampling frequency in Hzrequired

Returns:

NameTypeDescription
bpm(float) beats per minute for time interval
downsample
downsample(data, *, sampling_freq = None, target = None, target_type = 'samples', method = 'mean')

Downsample a Polars DataFrame/Series to a new target frequency or number of samples using averaging.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to downsamplerequired
sampling_freq(float) Sampling frequency of data in hertzNone
target(float) downsampling targetNone
target_typetype of target can be [samples,seconds,hz]‘samples’
method(str) type of downsample method [‘mean’,‘median’], default: mean‘mean’

Returns:

NameTypeDescription
out(pl.DataFrame, pl.Series) downsampled data (same type as input)
make_cosine_basis
make_cosine_basis(nsamples, sampling_freq, filter_length, unit_scale = True, drop = 0)

Create basis functions for a discrete cosine transform.

Based on the implementation in spm_filter and spm_dctmtx because scipy DCT can only apply transforms but not return the basis functions. Like SPM, this does not add a constant (i.e. intercept), but does retain the first basis (i.e. sigmoidal/linear drift).

Parameters:

NameTypeDescriptionDefault
nsamplesintnumber of observations (e.g. TRs)required
sampling_freqfloatsampling frequency in hertz (i.e. 1 / TR)required
filter_lengthintlength of filter in secondsrequired
unit_scaleboolassure that the basis functions are on the normalized range [-1, 1]; default TrueTrue
dropintindex of which early/slow bases to drop if any; default is to drop constant (i.e. intercept) like SPM. Unlike SPM, retains first basis (i.e. linear/sigmoidal). Will cumulatively drop bases up to and inclusive of index provided (e.g. 2, drops bases 1 and 2)0

Returns:

NameTypeDescription
outndarraynsamples x number of basis sets numpy array
upsample
upsample(data, *, sampling_freq = None, target = None, target_type = 'samples', method = 'linear')

Upsample a Polars DataFrame/Series to a new target frequency or number of samples using interpolation.

Parameters:

NameTypeDescriptionDefault
data(pl.DataFrame, pl.Series) data to upsample (Note: will drop non-numeric columns from DataFrame)required
sampling_freqSampling frequency of data in hertzNone
target(float) upsampling targetNone
target_type(str) type of target can be [samples,seconds,hz]‘samples’
method(str) [‘linear’, ‘nearest’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’] where ‘zero’, ‘slinear’, ‘quadratic’ and ‘cubic’ refer to a spline interpolation of zeroth, first, second or third order (default: linear)‘linear’

Returns: upsampled Polars DataFrame or Series (same type as input)

similarity

Similarity metrics and correlation.

Methods:

NameDescription
compute_multivariate_similarityCompute multivariate similarity via OLS regression.
compute_similarityCompute similarity between two data arrays.
fisher_r_to_zUse Fisher transformation to convert correlation to z score.
fisher_z_to_rConvert Fisher z back to a correlation coefficient.
transform_pairwiseTransform data into pairs with balanced labels for ranking.

Methods

compute_multivariate_similarity
compute_multivariate_similarity(y, X, method = 'ols', tail = 2)

Compute multivariate similarity via OLS regression.

This is the functional core implementation for multivariate similarity computation. Used by BrainData.multivariate_similarity() to delegate computation to the functional core.

Predicts spatial distribution of y from linear combination of X columns. Computes OLS regression statistics including beta coefficients, t-statistics, p-values, and residuals.

Parameters:

NameTypeDescriptionDefault
yndarrayTarget data, shape (n_features,) - single imagerequired
XndarrayPredictor data, shape (n_features, n_predictors) where first column should be intercept (ones) if intercept is desired. If X does not include intercept, an intercept will be added automatically.required
methodstrRegression method (currently only ‘ols’ supported)‘ols’

Returns:

NameTypeDescription
dictDictionary with keys: - ‘beta’: Regression coefficients including intercept, shape (n_predictors+1,) - ‘t’: t-statistics, shape (n_predictors+1,) - ‘p’: p-values, shape (n_predictors+1,) - ‘df’: Degrees of freedom (int) - ‘sigma’: Residual standard deviation (float) - ‘residual’: Residuals, shape (n_features,)

Examples:

>>> y = np.random.randn(100)
>>> X = np.random.randn(100, 5)
>>> result = compute_multivariate_similarity(y, X, method='ols')
>>> 'beta' in result
True
>>> result['beta'].shape
(6,)  # 5 predictors + intercept
compute_similarity
compute_similarity(data1, data2, metric = 'correlation')

Compute similarity between two data arrays.

This is the functional core implementation for similarity computation. Used by BrainData.similarity() to delegate computation to the functional core.

Parameters:

NameTypeDescriptionDefault
data1ndarrayFirst data array, shape (n_samples1, n_features)required
data2ndarraySecond data array, shape (n_samples2, n_features)required
metricstrType of similarity metric - ‘correlation’ or ‘pearson’: Pearson correlation - ‘spearman’ or ‘rank_correlation’: Spearman rank correlation - ‘dot_product’: Dot product - ‘cosine’: Cosine similarity‘correlation’

Returns:

TypeDescription
np.ndarray: Similarity matrix or vector - If data1.shape[0] == 1 and data2.shape[0] == 1: scalar - If data1.shape[0] == 1 or data2.shape[0] == 1: 1D array - Otherwise: 2D array shape (n_samples1, n_samples2)

Examples:

>>> data1 = np.random.randn(10, 100)
>>> data2 = np.random.randn(5, 100)
>>> sim = compute_similarity(data1, data2, metric='correlation')
>>> sim.shape
(10, 5)
fisher_r_to_z
fisher_r_to_z(r)

Use Fisher transformation to convert correlation to z score.

Parameters:

NameTypeDescriptionDefault
rcorrelation coefficient(s)required

Returns:

NameTypeDescription
zFisher z-transformed correlation(s)
fisher_z_to_r
fisher_z_to_r(z)

Convert Fisher z back to a correlation coefficient.

Parameters:

NameTypeDescriptionDefault
zFisher z-transformed value(s)required

Returns:

NameTypeDescription
rcorrelation coefficient(s)
transform_pairwise
transform_pairwise(X, y)

Transform data into pairs with balanced labels for ranking.

Transforms a n-class ranking problem into a two-class classification problem. Subclasses implementing particular strategies for choosing pairs should override this method. In this method, all pairs are choosen, except for those that have the same target value. The output is an array of balanced classes, i.e. there are the same number of -1 as +1

Reference: “Large Margin Rank Boundaries for Ordinal Regression”, R. Herbrich, T. Graepel, K. Obermayer. Authors: Fabian Pedregosa fabian@fseoane.net Alexandre Gramfort alexandre.gramfort@inria.fr

Parameters:

NameTypeDescriptionDefault
X(np.array), shape (n_samples, n_features) The datarequired
y(np.array), shape (n_samples,) or (n_samples, 2) Target labels. If it’s a 2D array, the second column represents the grouping of samples, i.e., samples with different groups will not be considered.required

Returns:

NameTypeDescription
X_trans(np.array), shape (k, n_features) Data as pairs, where k = n_samples * (n_samples-1)) / 2 if grouping values were not passed. If grouping variables exist, then returns values computed for each group.
y_trans(np.array), shape (k,) Output class labels, where classes have values {-1, +1} If y was shape (n_samples, 2), then returns (k, 2) with groups on the second dimension.