From a278d5e6735282f75367453569925d3017ad8e4b Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 24 Sep 2018 16:33:42 -0400 Subject: [PATCH 01/43] Reworked core ISC function for more flexible input, no internal stats --- brainiak/isfc.py | 548 ++++++++++++++++++++++++++++------------------- 1 file changed, 322 insertions(+), 226 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 0c730f74b..c9d7011af 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -1,226 +1,322 @@ -# Copyright 2017 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. -"""Intersubject analyses (ISC/ISFC) - -Functions for computing intersubject correlation (ISC) and intersubject -functional correlation (ISFC) - -Paper references: -ISC: Hasson, U., Nir, Y., Levy, I., Fuhrmann, G. & Malach, R. Intersubject -synchronization of cortical activity during natural vision. Science 303, -1634–1640 (2004). - -ISFC: Simony E, Honey CJ, Chen J, Lositsky O, Yeshurun Y, Wiesel A, Hasson U -(2016) Dynamic reconfiguration of the default mode network during narrative -comprehension. Nat Commun 7. -""" - -# Authors: Christopher Baldassano and Mor Regev -# Princeton University, 2017 - -from brainiak.fcma.util import compute_correlation -import numpy as np -from scipy import stats -from .utils.utils import phase_randomize, p_from_null - - -def isc(D, collapse_subj=True, return_p=False, num_perm=1000, - two_sided=False, random_state=0, float_type=np.float64): - """Intersubject correlation - - For each voxel, computes the correlation of each subject's timecourse with - the mean of all other subjects' timecourses. By default the result is - averaged across subjects, unless collapse_subj is set to False. A null - distribution can optionally be computed using phase randomization, to - compute a p value for each voxel. - - Parameters - ---------- - D : voxel by time by subject ndarray - fMRI data for which to compute ISC - - collapse_subj : bool, default:True - Whether to average across subjects before returning result - - return_p : bool, default:False - Whether to use phase randomization to compute a p value for each voxel - - num_perm : int, default:1000 - Number of null samples to use for computing p values - - two_sided : bool, default:False - Whether the p value should be one-sided (testing only for being - above the null) or two-sided (testing for both significantly positive - and significantly negative values) - - random_state : RandomState or an int seed (0 by default) - A random number generator instance to define the state of the - random permutations generator. - - float_type : either float16, float32, or float64, - Depends on the required precision - and available memory in the system. - All the arrays generated during the execution will be cast - to specified float type in order to save memory. - - Returns - ------- - ISC : voxel ndarray (or voxel by subject ndarray, if collapse_subj=False) - pearson correlation for each voxel, across subjects - - p : ndarray the same shape as ISC (if return_p = True) - p values for each ISC value under the null distribution - """ - - n_vox = D.shape[0] - n_subj = D.shape[2] - - n_perm = num_perm*int(return_p) - max_null = np.zeros(n_perm, dtype=float_type) - min_null = np.zeros(n_perm, dtype=float_type) - ISC = np.zeros((n_vox, n_subj), dtype=float_type) - - for loo_subj in range(n_subj): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - for v in range(n_vox): - ISC[v, loo_subj] = stats.pearsonr(group[v, :], - subj[v, :])[0] - if collapse_subj: - ISC = np.mean(ISC, axis=1) - - for p in range(n_perm): - # Randomize phases of D to create next null dataset - D = phase_randomize(D, random_state) - # Loop across choice of leave-one-out subject - tmp_ISC = np.zeros((n_vox, n_subj), dtype=float_type) - for loo_subj in range(n_subj): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - for v in range(n_vox): - tmp_ISC[v, loo_subj] = stats.pearsonr(group[v, :], - subj[v, :])[0] - if collapse_subj: - tmp_ISC = np.mean(tmp_ISC, axis=1) - max_null[p] = np.max(tmp_ISC) - min_null[p] = np.min(tmp_ISC) - - if return_p: - p = p_from_null(ISC, two_sided, - max_null_input=max_null, - min_null_input=min_null) - return ISC, p - else: - return ISC - - -def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, - two_sided=False, random_state=0, float_type=np.float64): - """Intersubject functional correlation - - Computes the correlation between the timecoure of each voxel in each - subject with the average of all other subjects' timecourses in *all* - voxels. By default the result is averaged across subjects, unless - collapse_subj is set to False. A null distribution can optionally be - computed using phase randomization, to compute a p value for each voxel-to- - voxel correlation. - - Uses the high performance compute_correlation routine from fcma.util - - Parameters - ---------- - D : voxel by time by subject ndarray - fMRI data for which to compute ISFC - - collapse_subj : bool, default:True - Whether to average across subjects before returning result - - return_p : bool, default:False - Whether to use phase randomization to compute a p value for each voxel - - num_perm : int, default:1000 - Number of null samples to use for computing p values - - two_sided : bool, default:False - Whether the p value should be one-sided (testing only for being - above the null) or two-sided (testing for both significantly positive - and significantly negative values) - - random_state : RandomState or an int seed (0 by default) - A random number generator instance to define the state of the - random permutations generator. - - float_type : either float16, float32, or float64 - Depends on the required precision - and available memory in the system. - All the arrays generated during the execution will be cast - to specified float type in order to save memory. - - Returns - ------- - ISFC : voxel by voxel ndarray - (or voxel by voxel by subject ndarray, if collapse_subj=False) - pearson correlation between all pairs of voxels, across subjects - - p : ndarray the same shape as ISC (if return_p = True) - p values for each ISC value under the null distribution - """ - - n_vox = D.shape[0] - n_subj = D.shape[2] - - n_perm = num_perm*int(return_p) - max_null = -np.ones(n_perm, dtype=float_type) - min_null = np.ones(n_perm, dtype=float_type) - - ISFC = np.zeros((n_vox, n_vox, n_subj), dtype=float_type) - - for loo_subj in range(D.shape[2]): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - tmp_ISFC = compute_correlation(group, subj).astype(float_type) - # Symmetrize matrix - tmp_ISFC = (tmp_ISFC+tmp_ISFC.T)/2 - ISFC[:, :, loo_subj] = tmp_ISFC - if collapse_subj: - ISFC = np.mean(ISFC, axis=2) - - for p in range(n_perm): - # Randomize phases of D to create next null dataset - D = phase_randomize(D, random_state) - # Loop across choice of leave-one-out subject - ISFC_null = np.zeros((n_vox, n_vox), dtype=float_type) - for loo_subj in range(D.shape[2]): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - tmp_ISFC = compute_correlation(group, subj).astype(float_type) - # Symmetrize matrix - tmp_ISFC = (tmp_ISFC+tmp_ISFC.T)/2 - - if not collapse_subj: - max_null[p] = max(np.max(tmp_ISFC), max_null[p]) - min_null[p] = min(np.min(tmp_ISFC), min_null[p]) - ISFC_null = ISFC_null + tmp_ISFC/n_subj - - if collapse_subj: - max_null[p] = np.max(ISFC_null) - min_null[p] = np.min(ISFC_null) - - if return_p: - p = p_from_null(ISFC, two_sided, - max_null_input=max_null, - min_null_input=min_null) - return ISFC, p - else: - return ISFC +# Copyright 2017 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. +"""Intersubject correlation (ISC) analysis + +Functions for computing intersubject correlation (ISC) and variations +including intersubject fucntional correlations (ISFC) + +Paper references: +ISC: Hasson, U., Nir, Y., Levy, I., Fuhrmann, G. & Malach, R. Intersubject +synchronization of cortical activity during natural vision. Science 303, +1634–1640 (2004). + +ISFC: Simony E, Honey CJ, Chen J, Lositsky O, Yeshurun Y, Wiesel A, Hasson U +(2016) Dynamic reconfiguration of the default mode network during narrative +comprehension. Nat Commun 7. +""" + +# Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev +# Princeton University, 2018 + +from brainiak.fcma.util import compute_correlation +import numpy as np +from scipy.spatial.distance import squareform +from scipy.stats import pearsonr, zscore + +def isc(data, pairwise=False, summary_statistic=None): + """Intersubject correlation + + For each voxel or ROI, compute the Pearson correlation between each + subject's response time series and other subjects' response time series. + If pairwise is False (default), use the leave-one-out approach, where + correlation is computed between each subject and the average of the other + subjects. If pairwise is True, compute correlations between all pairs of + subjects. If summary_statistic is None, return N ISC values for N subjects + (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, + corresponding to the upper triangle of the pairwise correlation matrix + (see scipy.spatial.distance.squareform). Alternatively, supply either + np.mean or np.median to compute summary statistic of ISCs (Fisher Z will + be applied and inverted if using mean). Input data should be a list + where each item is a time-points by voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a single ndarray is + supplied, the last dimension is assumed to correspond to subjects. + Output is an ndarray where the first dimension is the number of subjects + or pairs and the second dimension is the number of voxels (or ROIs). + + Parameters + ---------- + data : list or ndarray + fMRI data for which to compute ISC + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach + + summary_statistic : None + Return all ISCs or collapse using np.mean or np.median + + Returns + ------- + iscs : subjects or pairs by voxels ndarray + ISC for each subject or pair + + """ + + # Convert list input to 3d and check shapes + if type(data) == list: + data_shape = data[0].shape + for i, d in enumerate(data): + if d.shape != data_shape: + raise ValueError("All ndarrays in input list " + "must be the same shape!") + if d.ndim == 1: + data[i] = d[:, np.newaxis] + data = np.dstack(data) + + # Convert input ndarray to 3d and check shape + elif type(data) == np.ndarray: + if data.ndim == 2: + data = data[:, np.newaxis, :] + elif data.ndim == 3: + pass + else: + raise ValueError("Input ndarray should have 2 " + f"or 3 dimensions (got {data.ndim})!") + + # Infer subjects, TRs, voxels and print for user to check + n_subjects = data.shape[2] + n_TRs = data.shape[0] + n_voxels = data.shape[1] + print(f"Assuming {n_subjects} subjects with {n_TRs} time points " + f"and {n_voxels} voxel(s) or ROI(s).") + + # Loop over each voxel or ROI + voxel_iscs = [] + for v in np.arange(n_voxels): + voxel_data = data[:, v, :].T + if pairwise: + iscs = squareform(np.corrcoef(voxel_data), checks=False) + elif not pairwise: + iscs = np.array([pearsonr(subject, + np.mean([s for s in voxel_data + if s is not subject], + axis=0))[0] + for subject in voxel_data]) + voxel_iscs.append(iscs) + + iscs = np.column_stack(voxel_iscs) + + if summary_statistic == np.mean: + iscs = np.tanh(summary_statistic(np.arctanh(iscs), axis=0))[np.newaxis, :] + elif summary_statistic == np.median: + iscs = summary_statistic(iscs, axis=0)[np.newaxis, :] + elif not summary_statistic: + pass + else: + raise TypeError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") + return iscs + +###IF PAIRWISE=FALSE COMES IN AS JUST TWO VECTORS, ASSUME THE AVERAGING HAS +###ALREADY BEEN DONE!!! + + +def isc(D, collapse_subj=True, return_p=False, num_perm=1000, + two_sided=False, random_state=0, float_type=np.float64): + """Intersubject correlation + + For each voxel, computes the correlation of each subject's timecourse with + the mean of all other subjects' timecourses. By default the result is + averaged across subjects, unless collapse_subj is set to False. A null + distribution can optionally be computed using phase randomization, to + compute a p value for each voxel. + + Parameters + ---------- + D : voxel by time by subject ndarray + fMRI data for which to compute ISC + + collapse_subj : bool, default:True + Whether to average across subjects before returning result + + return_p : bool, default:False + Whether to use phase randomization to compute a p value for each voxel + + num_perm : int, default:1000 + Number of null samples to use for computing p values + + two_sided : bool, default:False + Whether the p value should be one-sided (testing only for being + above the null) or two-sided (testing for both significantly positive + and significantly negative values) + + random_state : RandomState or an int seed (0 by default) + A random number generator instance to define the state of the + random permutations generator. + + float_type : either float16, float32, or float64, + Depends on the required precision + and available memory in the system. + All the arrays generated during the execution will be cast + to specified float type in order to save memory. + + Returns + ------- + ISC : voxel ndarray (or voxel by subject ndarray, if collapse_subj=False) + pearson correlation for each voxel, across subjects + + p : ndarray the same shape as ISC (if return_p = True) + p values for each ISC value under the null distribution + """ + + n_vox = D.shape[0] + n_subj = D.shape[2] + + n_perm = num_perm*int(return_p) + max_null = np.zeros(n_perm, dtype=float_type) + min_null = np.zeros(n_perm, dtype=float_type) + ISC = np.zeros((n_vox, n_subj), dtype=float_type) + + for loo_subj in range(n_subj): + group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) + subj = D[:, :, loo_subj] + for v in range(n_vox): + ISC[v, loo_subj] = stats.pearsonr(group[v, :], + subj[v, :])[0] + if collapse_subj: + ISC = np.mean(ISC, axis=1) + + for p in range(n_perm): + # Randomize phases of D to create next null dataset + D = phase_randomize(D, random_state) + # Loop across choice of leave-one-out subject + tmp_ISC = np.zeros((n_vox, n_subj), dtype=float_type) + for loo_subj in range(n_subj): + group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) + subj = D[:, :, loo_subj] + for v in range(n_vox): + tmp_ISC[v, loo_subj] = stats.pearsonr(group[v, :], + subj[v, :])[0] + if collapse_subj: + tmp_ISC = np.mean(tmp_ISC, axis=1) + max_null[p] = np.max(tmp_ISC) + min_null[p] = np.min(tmp_ISC) + + if return_p: + p = p_from_null(ISC, two_sided, + max_null_input=max_null, + min_null_input=min_null) + return ISC, p + else: + return ISC + + +def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, + two_sided=False, random_state=0, float_type=np.float64): + """Intersubject functional correlation + + Computes the correlation between the timecoure of each voxel in each + subject with the average of all other subjects' timecourses in *all* + voxels. By default the result is averaged across subjects, unless + collapse_subj is set to False. A null distribution can optionally be + computed using phase randomization, to compute a p value for each voxel-to- + voxel correlation. + + Uses the high performance compute_correlation routine from fcma.util + + Parameters + ---------- + D : voxel by time by subject ndarray + fMRI data for which to compute ISFC + + collapse_subj : bool, default:True + Whether to average across subjects before returning result + + return_p : bool, default:False + Whether to use phase randomization to compute a p value for each voxel + + num_perm : int, default:1000 + Number of null samples to use for computing p values + + two_sided : bool, default:False + Whether the p value should be one-sided (testing only for being + above the null) or two-sided (testing for both significantly positive + and significantly negative values) + + random_state : RandomState or an int seed (0 by default) + A random number generator instance to define the state of the + random permutations generator. + + float_type : either float16, float32, or float64 + Depends on the required precision + and available memory in the system. + All the arrays generated during the execution will be cast + to specified float type in order to save memory. + + Returns + ------- + ISFC : voxel by voxel ndarray + (or voxel by voxel by subject ndarray, if collapse_subj=False) + pearson correlation between all pairs of voxels, across subjects + + p : ndarray the same shape as ISC (if return_p = True) + p values for each ISC value under the null distribution + """ + + n_vox = D.shape[0] + n_subj = D.shape[2] + + n_perm = num_perm*int(return_p) + max_null = -np.ones(n_perm, dtype=float_type) + min_null = np.ones(n_perm, dtype=float_type) + + ISFC = np.zeros((n_vox, n_vox, n_subj), dtype=float_type) + + for loo_subj in range(D.shape[2]): + group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) + subj = D[:, :, loo_subj] + tmp_ISFC = compute_correlation(group, subj).astype(float_type) + # Symmetrize matrix + tmp_ISFC = (tmp_ISFC+tmp_ISFC.T)/2 + ISFC[:, :, loo_subj] = tmp_ISFC + if collapse_subj: + ISFC = np.mean(ISFC, axis=2) + + for p in range(n_perm): + # Randomize phases of D to create next null dataset + D = phase_randomize(D, random_state) + # Loop across choice of leave-one-out subject + ISFC_null = np.zeros((n_vox, n_vox), dtype=float_type) + for loo_subj in range(D.shape[2]): + group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) + subj = D[:, :, loo_subj] + tmp_ISFC = compute_correlation(group, subj).astype(float_type) + # Symmetrize matrix + tmp_ISFC = (tmp_ISFC+tmp_ISFC.T)/2 + + if not collapse_subj: + max_null[p] = max(np.max(tmp_ISFC), max_null[p]) + min_null[p] = min(np.min(tmp_ISFC), min_null[p]) + ISFC_null = ISFC_null + tmp_ISFC/n_subj + + if collapse_subj: + max_null[p] = np.max(ISFC_null) + min_null[p] = np.min(ISFC_null) + + if return_p: + p = p_from_null(ISFC, two_sided, + max_null_input=max_null, + min_null_input=min_null) + return ISFC, p + else: + return ISFC + From be185d29127c0fe8ee52c362b2e342ed5ac50638 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 24 Sep 2018 16:55:58 -0400 Subject: [PATCH 02/43] ISC function intelligently handles only two subjects --- brainiak/isfc.py | 126 ++++++++++------------------------------------- 1 file changed, 27 insertions(+), 99 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index c9d7011af..5b06125c0 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -50,9 +50,18 @@ def isc(data, pairwise=False, summary_statistic=None): be applied and inverted if using mean). Input data should be a list where each item is a time-points by voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a single ndarray is - supplied, the last dimension is assumed to correspond to subjects. - Output is an ndarray where the first dimension is the number of subjects - or pairs and the second dimension is the number of voxels (or ROIs). + supplied, the last dimension is assumed to correspond to subjects. If + only two subjects are supplied, simply compute Pearson correlation + (precludes averaging in leave-one-out approach, and does not apply + summary statistic.) Output is an ndarray where the first dimension is + the number of subjects or pairs and the second dimension is the number + of voxels (or ROIs). + + The implementation is based on the following publication: + + .. [Hasson2004] "Intersubject synchronization of cortical activity + during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, + R. Malach, 2004, Science, 303, 1634-1640. Parameters ---------- @@ -68,7 +77,7 @@ def isc(data, pairwise=False, summary_statistic=None): Returns ------- iscs : subjects or pairs by voxels ndarray - ISC for each subject or pair + ISC for each subject or pair (or summary statistic) per voxel """ @@ -104,7 +113,11 @@ def isc(data, pairwise=False, summary_statistic=None): voxel_iscs = [] for v in np.arange(n_voxels): voxel_data = data[:, v, :].T - if pairwise: + if n_subjects == 2: + iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] + summary_statistic = None + print("Only two subjects! Simply computing Pearson correlation.") + elif pairwise: iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: iscs = np.array([pearsonr(subject, @@ -113,9 +126,9 @@ def isc(data, pairwise=False, summary_statistic=None): axis=0))[0] for subject in voxel_data]) voxel_iscs.append(iscs) - iscs = np.column_stack(voxel_iscs) + # Summarize results (if requested) if summary_statistic == np.mean: iscs = np.tanh(summary_statistic(np.arctanh(iscs), axis=0))[np.newaxis, :] elif summary_statistic == np.median: @@ -125,99 +138,6 @@ def isc(data, pairwise=False, summary_statistic=None): else: raise TypeError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") return iscs - -###IF PAIRWISE=FALSE COMES IN AS JUST TWO VECTORS, ASSUME THE AVERAGING HAS -###ALREADY BEEN DONE!!! - - -def isc(D, collapse_subj=True, return_p=False, num_perm=1000, - two_sided=False, random_state=0, float_type=np.float64): - """Intersubject correlation - - For each voxel, computes the correlation of each subject's timecourse with - the mean of all other subjects' timecourses. By default the result is - averaged across subjects, unless collapse_subj is set to False. A null - distribution can optionally be computed using phase randomization, to - compute a p value for each voxel. - - Parameters - ---------- - D : voxel by time by subject ndarray - fMRI data for which to compute ISC - - collapse_subj : bool, default:True - Whether to average across subjects before returning result - - return_p : bool, default:False - Whether to use phase randomization to compute a p value for each voxel - - num_perm : int, default:1000 - Number of null samples to use for computing p values - - two_sided : bool, default:False - Whether the p value should be one-sided (testing only for being - above the null) or two-sided (testing for both significantly positive - and significantly negative values) - - random_state : RandomState or an int seed (0 by default) - A random number generator instance to define the state of the - random permutations generator. - - float_type : either float16, float32, or float64, - Depends on the required precision - and available memory in the system. - All the arrays generated during the execution will be cast - to specified float type in order to save memory. - - Returns - ------- - ISC : voxel ndarray (or voxel by subject ndarray, if collapse_subj=False) - pearson correlation for each voxel, across subjects - - p : ndarray the same shape as ISC (if return_p = True) - p values for each ISC value under the null distribution - """ - - n_vox = D.shape[0] - n_subj = D.shape[2] - - n_perm = num_perm*int(return_p) - max_null = np.zeros(n_perm, dtype=float_type) - min_null = np.zeros(n_perm, dtype=float_type) - ISC = np.zeros((n_vox, n_subj), dtype=float_type) - - for loo_subj in range(n_subj): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - for v in range(n_vox): - ISC[v, loo_subj] = stats.pearsonr(group[v, :], - subj[v, :])[0] - if collapse_subj: - ISC = np.mean(ISC, axis=1) - - for p in range(n_perm): - # Randomize phases of D to create next null dataset - D = phase_randomize(D, random_state) - # Loop across choice of leave-one-out subject - tmp_ISC = np.zeros((n_vox, n_subj), dtype=float_type) - for loo_subj in range(n_subj): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - for v in range(n_vox): - tmp_ISC[v, loo_subj] = stats.pearsonr(group[v, :], - subj[v, :])[0] - if collapse_subj: - tmp_ISC = np.mean(tmp_ISC, axis=1) - max_null[p] = np.max(tmp_ISC) - min_null[p] = np.min(tmp_ISC) - - if return_p: - p = p_from_null(ISC, two_sided, - max_null_input=max_null, - min_null_input=min_null) - return ISC, p - else: - return ISC def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, @@ -320,3 +240,11 @@ def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, else: return ISFC +''' two_sided : bool, default:False + Whether the p value should be one-sided (testing only for being + above the null) or two-sided (testing for both significantly positive + and significantly negative values) + + random_state : RandomState or an int seed (0 by default) + A random number generator instance to define the state of the + random permutations generator.''' From 04726909d293372b04a468458598bb84fd1f1664 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 24 Sep 2018 19:11:09 -0400 Subject: [PATCH 03/43] Added bootstrap resampling subjects for one-sample test --- brainiak/isfc.py | 167 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 5b06125c0..ffc610697 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -248,3 +248,170 @@ def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, random_state : RandomState or an int seed (0 by default) A random number generator instance to define the state of the random permutations generator.''' + + +def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, + n_bootstraps=1000, ci_percentile=95, + return_distribution=False): + + """One-sample group-level bootstrap hypothesis test for iscs + + For ISCs from one more voxels or ROIs, resample subjects with replacement + to construct a bootstrap distribution. Input is either a list or ndarray + of ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. ISC values + should be either N ISC values for N subjects in the leave-one-out appraoch + (pairwise=False), N(N-1)/2 ISC values for N subjects in the pairwise + approach (pairwise=True). In the pairwise approach, ISC values should + correspond to the vectorized upper triangle of a square corrlation matrix + (see scipy.stats.distance.squareform). Shifts bootstrap by actual median + (effectively to zero) for two-tailed null hypothesis test (Hall & Wilson, + 1991). Uses subject-wise (not pair-wise) resampling in the pairwise approach. + Returns the observed ISC, the confidence interval, and a p-value for the + bootstrap hypothesis test. According to Chen et al., 2016, this is the + preferred nonparametric approach for controlling false positive rates (FPR) + for one-sample tests in the pairwise approach. Optionally, you can return + the bootstrap distribution of summary statistics (memory intensive). + + The implementation is based on the following publications: + + .. [Chen2016] "Untangling the relatedness among correlations, part I: + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + + .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", + P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. + + Parameters + ---------- + iscs : list or ndarray, ISCs by voxels array + ISC values for one or more voxels + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs structure + + summary_statistic : numpy function, default:np.median + Summary statistic, either np.median (default) or np.mean + + n_bootstraps : int, default:1000 + Number of bootstrap samples (subject-level with replacement) + + ci_percentile : int, default:95 + Percentile for computing confidence intervals + + Returns + ------- + observed : float, median (or mean) ISC value + Summary statistic for actual ISCs + + ci : tuple, bootstrap confidence intervals + Confidence intervals generated from bootstrap distribution + + p : float, p-value + p-value based on bootstrap hypothesis test + + distribution : ndarray, bootstraps by voxels (optional) + Bootstrap distribution if return_bootstrap=True + + """ + + # Standardize structure of input data + if type(iscs) == list: + iscs = np.array(iscs)[:, np.newaxis] + + elif type(iscs) == np.ndarray: + if iscs.ndim == 1: + iscs = iscs[:, np.newaxis] + + # Check if incoming pairwise matrix is vectorized triangle + if pairwise: + try: + test_square = squareform(iscs[:, 0]) + n_subjects = test_square.shape[0] + except ValueError: + raise ValueError("For pairwise input, ISCs must be the " + "vectorized triangle of a square matrix.") + elif not pairwise: + n_subjects = iscs.shape[0] + + # Infer subjects, TRs, voxels and print for user to check + n_voxels = iscs.shape[1] + print(f"Assuming {n_subjects} subjects with and {n_voxels} " + "voxel(s) or ROI(s).") + + # Compute summary statistic for observed ISCs + if summary_statistic == np.mean: + observed = np.tanh(np.mean(np.arctanh(iscs), axis=0))[np.newaxis, :] + elif summary_statistic == np.median: + observed = summary_statistic(iscs, axis=0)[np.newaxis, :] + else: + raise TypeError("Unrecognized summary_statistic! Use np.median or np.mean.") + + # Set up an empty list to build our bootstrap distribution + distribution = [] + + # Loop through n bootstrap iterations and populate distribution + for i in np.arange(n_bootstraps): + + # Randomly sample subject IDs with replacement + subject_sample = sorted(np.random.choice(np.arange(n_subjects), + size=n_subjects)) + + # Loop through voxels + voxel_statistics = [] + for voxel_iscs in iscs.T: + + # Squareform and shuffle rows/columns of pairwise ISC matrix to + # to retain correlation structure among ISCs, then get triangle + if pairwise: + + # Square the triangle and fill diagonal + voxel_iscs = squareform(voxel_iscs) + np.fill_diagonal(voxel_iscs, 1) + + # Check that pairwise ISC matrix is square and symmetric + assert voxel_iscs.shape[0] == voxel_iscs.shape[1] + assert np.allclose(voxel_iscs, voxel_iscs.T) + + # Shuffle square correlation matrix and get triangle + voxel_sample = voxel_iscs[subject_sample, :][:, subject_sample] + voxel_sample = squareform(voxel_sample, checks=False) + + # Censor off-diagonal 1s for same-subject pairs + voxel_sample[voxel_sample == 1.] = np.NaN + + # Get simple bootstrap sample of not pairwise + elif not pairwise: + voxel_sample = voxel_iscs[subject_sample] + + # Compute summary statistic for bootstrap ISCs per voxel + # (alternatively could construct distrubtion for all voxels + # then compute statistics, but larger memory footprint) + if summary_statistic == np.mean: + voxel_statistics.append(np.tanh(np.nanmean(np.arctanh(voxel_sample), axis=0))) + elif summary_statistic == np.median: + voxel_statistics.append(np.nanmedian(voxel_sample, axis=0)) + + distribution.append(voxel_statistics) + + # Convert distribution to numpy array + distribution = np.array(distribution) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Compute CIs of median from bootstrap distribution (default: 95%) + ci = (np.percentile(distribution, (100 - ci_percentile)/2, axis=0), + np.percentile(distribution, ci_percentile + (100 - ci_percentile)/2, axis=0)) + + # Shift bootstrap distribution to 0 for hypothesis test + shifted = distribution - observed + + # Get p-value for actual median from shifted distribution + p = ((np.sum(np.abs(shifted) >= np.abs(observed), axis=0) + 1) / + float((len(shifted) + 1)))[np.newaxis, :] + + if return_distribution: + return observed, ci, p, distribution + elif not return_distribution: + return observed, ci, p + + From d949b0b1ddf3ea0274f9e4abe8f0c8a89021a916 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Fri, 28 Sep 2018 12:31:45 -0400 Subject: [PATCH 04/43] Moved non-pairwise operations out of voxel loop for speed --- brainiak/isfc.py | 93 +++++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index ffc610697..c278173a1 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -29,10 +29,11 @@ # Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev # Princeton University, 2018 -from brainiak.fcma.util import compute_correlation +#from brainiak.fcma.util import compute_correlation import numpy as np from scipy.spatial.distance import squareform from scipy.stats import pearsonr, zscore +import itertools as it def isc(data, pairwise=False, summary_statistic=None): """Intersubject correlation @@ -136,7 +137,7 @@ def isc(data, pairwise=False, summary_statistic=None): elif not summary_statistic: pass else: - raise TypeError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") + raise ValueError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") return iscs @@ -252,25 +253,25 @@ def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, n_bootstraps=1000, ci_percentile=95, - return_distribution=False): + return_distribution=False, random_state=None): - """One-sample group-level bootstrap hypothesis test for iscs + """One-sample group-level bootstrap hypothesis test for ISCs For ISCs from one more voxels or ROIs, resample subjects with replacement - to construct a bootstrap distribution. Input is either a list or ndarray - of ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. ISC values + to construct a bootstrap distribution. Input is a list or ndarray of + ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. ISC values should be either N ISC values for N subjects in the leave-one-out appraoch (pairwise=False), N(N-1)/2 ISC values for N subjects in the pairwise approach (pairwise=True). In the pairwise approach, ISC values should correspond to the vectorized upper triangle of a square corrlation matrix - (see scipy.stats.distance.squareform). Shifts bootstrap by actual median - (effectively to zero) for two-tailed null hypothesis test (Hall & Wilson, - 1991). Uses subject-wise (not pair-wise) resampling in the pairwise approach. - Returns the observed ISC, the confidence interval, and a p-value for the - bootstrap hypothesis test. According to Chen et al., 2016, this is the - preferred nonparametric approach for controlling false positive rates (FPR) - for one-sample tests in the pairwise approach. Optionally, you can return - the bootstrap distribution of summary statistics (memory intensive). + (see scipy.stats.distance.squareform). Shifts bootstrap distribution by + actual summary statistic (effectively to zero) for two-tailed null + hypothesis test (Hall & Wilson, 1991). Uses subject-wise (not pair-wise) + resampling in the pairwise approach. Returns the observed ISC, the confidence + interval, and a p-value for the bootstrap hypothesis test. Optionally returns + the bootstrap distribution of summary statistics.According to Chen et al., + 2016, this is the preferred nonparametric approach for controlling false + positive rates (FPR) for one-sample tests in the pairwise approach. The implementation is based on the following publications: @@ -298,6 +299,12 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, ci_percentile : int, default:95 Percentile for computing confidence intervals + + return_distribution : bool, default:False + Optionally return the bootstrap distribution of summary statistics + + random_state = int or None, default:None + Initial random seed Returns ------- @@ -333,8 +340,13 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, "vectorized triangle of a square matrix.") elif not pairwise: n_subjects = iscs.shape[0] + + if n_subjects < 2: + raise ValueError("Input data seems to contain only one subject! " + "Needs two or more subjects. Check that input is " + "not summary statistic.") - # Infer subjects, TRs, voxels and print for user to check + # Infer subjects, voxels and print for user to check n_voxels = iscs.shape[1] print(f"Assuming {n_subjects} subjects with and {n_voxels} " "voxel(s) or ROI(s).") @@ -352,18 +364,24 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, # Loop through n bootstrap iterations and populate distribution for i in np.arange(n_bootstraps): + + # Random seed to be deterministically re-randomized at each iteration + if isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) # Randomly sample subject IDs with replacement - subject_sample = sorted(np.random.choice(np.arange(n_subjects), + subject_sample = sorted(prng.choice(np.arange(n_subjects), size=n_subjects)) - # Loop through voxels - voxel_statistics = [] - for voxel_iscs in iscs.T: - - # Squareform and shuffle rows/columns of pairwise ISC matrix to - # to retain correlation structure among ISCs, then get triangle - if pairwise: + # Squareform and shuffle rows/columns of pairwise ISC matrix to + # to retain correlation structure among ISCs, then get triangle + if pairwise: + + # Loop through voxels + isc_sample = [] + for voxel_iscs in iscs.T: # Square the triangle and fill diagonal voxel_iscs = squareform(voxel_iscs) @@ -380,19 +398,24 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, # Censor off-diagonal 1s for same-subject pairs voxel_sample[voxel_sample == 1.] = np.NaN - # Get simple bootstrap sample of not pairwise - elif not pairwise: - voxel_sample = voxel_iscs[subject_sample] + isc_sample.append(voxel_sample) - # Compute summary statistic for bootstrap ISCs per voxel - # (alternatively could construct distrubtion for all voxels - # then compute statistics, but larger memory footprint) - if summary_statistic == np.mean: - voxel_statistics.append(np.tanh(np.nanmean(np.arctanh(voxel_sample), axis=0))) - elif summary_statistic == np.median: - voxel_statistics.append(np.nanmedian(voxel_sample, axis=0)) + isc_sample = np.column_stack(isc_sample) + + # Get simple bootstrap sample of not pairwise + elif not pairwise: + isc_sample = iscs[subject_sample, :] - distribution.append(voxel_statistics) + # Compute summary statistic for bootstrap ISCs per voxel + # (alternatively could construct distrubtion for all voxels + # then compute statistics, but larger memory footprint) + if summary_statistic == np.mean: + distribution.append(np.tanh(np.nanmean(np.arctanh(isc_sample), axis=0))) + elif summary_statistic == np.median: + distribution.append(np.nanmedian(isc_sample, axis=0)) + + # Update random state + random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) # Convert distribution to numpy array distribution = np.array(distribution) @@ -413,5 +436,3 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, return observed, ci, p, distribution elif not return_distribution: return observed, ci, p - - From be765617956c57e830125c572d1cc6f30cc785a9 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 1 Oct 2018 23:29:31 -0400 Subject: [PATCH 05/43] Added permutation test for one- and two-sample tests --- brainiak/isfc.py | 317 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 315 insertions(+), 2 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index c278173a1..d75c3a644 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -402,7 +402,7 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, isc_sample = np.column_stack(isc_sample) - # Get simple bootstrap sample of not pairwise + # Get simple bootstrap sample if not pairwise elif not pairwise: isc_sample = iscs[subject_sample, :] @@ -414,7 +414,7 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, elif summary_statistic == np.median: distribution.append(np.nanmedian(isc_sample, axis=0)) - # Update random state + # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) # Convert distribution to numpy array @@ -436,3 +436,316 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, return observed, ci, p, distribution elif not return_distribution: return observed, ci, p + + +def permutation_isc(iscs, group_assignment=None, pairwise=False, + summary_statistic=np.median, n_permutations=1000, + return_distribution=False, random_state=None): + + """Group-level permutation test for ISCs + + For ISCs from one or more voxels or ROIs, permute group assignments to + construct a permutation distribution. Input is a list or ndarray of + ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. If two groups, + ISC values should stacked along first dimension (vertically), and a + group_assignment list (or 1d array) of same length as the number of + subjects should be provided to indicate group labels. If no group_assignment + is provided, a one-sample test is performed using a sign-flipping procedure. + Performs exact test if number of possible permutations (2**N for one-sample + sign-flipping, N! for two-sample shuffling) is less than or equal to number + of requested permutation; otherwise, performs approximate permutation test + using Monte Carlo resampling. ISC values should either be N ISC values for + N subjects in the leave-one-out approach (pairwise=False) or N(N-1)/2 ISC + values for N subjects in the pairwise approach (pairwise=True). In the + pairwise approach, ISC values should correspond to the vectorized upper + triangle of a square corrlation matrix (see scipy.stats.distance.squareform). + Note that in the pairwise approach, group_assignment order should match the + row/column order of the subject-by-subject square ISC matrix even though the + input ISCs should be supplied as the vectorized upper triangle of the square + ISC matrix. Returns the observed ISC and permutation-based p-value (two-tailed + test). Optionall returns the permutation distribution of summary statistics. + According to Chen et al., 2016, this is the preferred nonparametric approach + for controlling false positive rates (FPR) for two-sample tests. This approach + may yield inflated FPRs for one-sample tests. + + The implementation is based on the following publications: + + .. [Chen2016] "Untangling the relatedness among correlations, part I: + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + + .. [PhipsonSmyth2010] "Permutation p-values should never be zero: + calculating exact p-values when permutations are randomly drawn.", + B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics + and Molecular Biology, 9, 1544-6115. + + Parameters + ---------- + iscs : list or ndarray, correlation matrix of iscs + ISC values for one or more voxels + + group_assignment : list or ndarray, group labels + Group labels matching order of ISC input + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs variable + + summary_statistic : numpy function, default:np.median + Summary statistic, either np.median (default) or np.mean + + n_permutations : int, default:1000 + Number of permutation iteration (randomizing group assignment) + + return_distribution : bool, default:False + Optionally return the bootstrap distribution of summary statistics + + random_state = int, None, or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + observed : float, ISC summary statistic or difference + Actual ISC or group difference (excluding between-group ISCs) + + p : float, p-value + p-value based on permutation test + + distribution : ndarray, permutations by voxels (optional) + Permutation distribution if return_bootstrap=True + """ + + # Standardize structure of input data + if type(iscs) == list: + iscs = np.array(iscs)[:, np.newaxis] + + elif type(iscs) == np.ndarray: + if iscs.ndim == 1: + iscs = iscs[:, np.newaxis] + + # Check if incoming pairwise matrix is vectorized triangle + if pairwise: + try: + test_square = squareform(iscs[:, 0]) + n_subjects = test_square.shape[0] + except ValueError: + raise ValueError("For pairwise input, ISCs must be the " + "vectorized triangle of a square matrix.") + elif not pairwise: + n_subjects = iscs.shape[0] + + # Check match between group labels and ISCs + if type(group_assignment) == list: + pass + elif type(group_assignment) == np.ndarray: + group_assignment = group_assignment.tolist() + else: + print("No group assignment provided, performing one-sample test.") + + if group_assignment and len(group_assignment) != n_subjects: + raise ValueError(f"Group assignments ({len(group_assignment)}) " + f"do not match number of subjects ({n_subjects})!") + + # Set up group selectors for two-group scenario + if group_assignment and len(np.unique(group_assignment)) == 2: + n_groups = 2 + + # Get group labels and counts + group_labels = np.unique(group_assignment) + groups = {group_labels[0]: group_assignment.count(group_labels[0]), + group_labels[1]: group_assignment.count(group_labels[1])} + + # For two-sample pairwise approach set up selector from matrix + if pairwise == True: + # Sort the group_assignment variable if it came in shuffled + # so it's easier to build group assignment matrix + sorter = np.array(group_assignment).argsort() + unsorter = np.array(group_assignment).argsort().argsort() + + # Populate a matrix with group assignments + group_matrix = np.vstack((np.hstack((np.full((groups[group_labels[0]], + groups[group_labels[0]]), + group_labels[0]), + np.full((groups[group_labels[0]], + groups[group_labels[1]]), + np.nan))), + np.hstack((np.full((groups[group_labels[1]], + groups[group_labels[0]]), + np.nan), + np.full((groups[group_labels[1]], + groups[group_labels[1]]), + group_labels[1]))))) + np.fill_diagonal(group_matrix, np.nan) + + # Unsort matrix and squareform to create selector + group_selector = squareform(group_matrix[unsorter, :][:, unsorter], + checks=False) + + # If leave-one-out approach, just user group assignment as selector + elif pairwise == False: + group_selector = group_assignment + + # Manage one-sample and incorrect group assignments + elif not group_assignment or len(np.unique(group_assignment)) == 1: + n_groups = 1 + + # If pairwise initialize matrix of ones for sign-flipping + ones_matrix = np.ones((n_subjects, n_subjects)) + + elif len(np.unique(group_assignment)) > 2: + raise ValueError("This test is not valid for more than " + f"2 groups! (got {n_groups})") + else: + raise ValueError("Invalid group assignments!") + + # Infer subjects, groups, voxels and print for user to check + n_voxels = iscs.shape[1] + print(f"Assuming {n_subjects} subjects, {n_groups} group(s), " + f"and {n_voxels} voxel(s) or ROI(s).") + + # Set up permutation type (exact or Monte Carlo) + if n_groups == 1: + if n_permutations < 2**n_subjects: + print("One-sample approximate permutation test using sign-flipping " + "procedure with Monte Carlo resampling.") + exact_permutations = None + elif n_permutations >= 2**n_subjects: + print("One-sample exact permutation test using sign-flipping " + f"procedure with 2**{n_subjects} ({2**n_subjects}) iterations.") + exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) + n_permutations = 2**n_subjects + elif n_groups == 2: + if n_permutations < np.math.factorial(n_subjects): + print("Two-sample approximate permutation test using " + "group randomization with Monte Carlo resampling.") + exact_permutations = None + elif n_permutations >= np.math.factorial(n_subjects): + print("Two-sample exact permutation test using group " + f"randomization with {n_subjects}! " + f"({np.math.factorial(n_subjects)}) " + "iterations.") + exact_permutations = list(it.permutations( + np.arange(len(group_assignment)))) + n_permutations = np.math.factorial(n_subjects) + + # If one group, just get observed summary statistic + if n_groups == 1: + if summary_statistic == np.mean: + observed = np.tanh(np.mean(np.arctanh(iscs), axis=0)) + if summary_statistic == np.median: + observed = np.median(iscs, axis=0) + + # If two groups, get the observed difference + elif n_groups == 2: + + if summary_statistic == np.mean: + observed = (np.tanh(np.mean(np.arctanh( + iscs[group_selector == group_labels[0], :]), axis=0)) - + np.tanh(np.mean(np.arctanh( + iscs[group_selector == group_labels[1], :]), axis=0))) + if summary_statistic == np.median: + observed = (np.median( + iscs[group_selector == group_labels[0], :], axis=0) - + np.median( + iscs[group_selector == group_labels[1], :], axis=0)) + observed = np.array(observed)[np.newaxis, :] + + # Set up an empty list to build our permutation distribution + distribution = [] + + # Loop through n permutation iterations and populate distribution + for i in np.arange(n_permutations): + + # Random seed to be deterministically re-randomized at each iteration + if exact_permutations: + pass + elif isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # If one group, apply sign-flipping procedure + if n_groups == 1: + + # Randomized sign-flips + if exact_permutations: + sign_flipper = np.array(exact_permutations[i]) + elif not exact_permutations: + sign_flipper = prng.choice([-1, 1], size=n_subjects, replace=True) + + # If pairwise, apply sign-flips by rows and columns + if pairwise: + matrix_flipped = (ones_matrix * sign_flipper + * sign_flipper[:, np.newaxis]) + sign_flipper = squareform(matrix_flipped, checks=False) + + # Apply flips along ISC axis (same across voxels) + isc_flipped = iscs * sign_flipper[:, np.newaxis] + + # Get summary statistics on sign-flipped ISCs + if summary_statistic == np.mean: + isc_sample = np.tanh(np.mean(np.arctanh(isc_flipped), axis=0)) + if summary_statistic == np.median: + isc_sample = np.median(isc_flipped, axis=0) + + # If two groups, set up group matrix get the observed difference + elif n_groups == 2: + + # Shuffle the group assignments + if exact_permutations: + group_shuffler = np.array(exact_permutations[i]) + elif not exact_permutations and pairwise: + group_shuffler = prng.permutation(np.arange( + len(np.array(group_assignment)[sorter]))) + elif not exact_permutations and not pairwise: + group_shuffler = prng.permutation(np.arange( + len(group_assignment))) + + # If pairwise approach, convert group assignments to matrix + if pairwise: + + # Apply shuffler to group matrix rows/columns + group_shuffled = group_matrix[group_shuffler, :][:, group_shuffler] + + # Unsort shuffled matrix and squareform to create selector + group_selector = squareform(group_shuffled[unsorter, :][:, unsorter], + checks=False) + + # Shuffle group assignments in leave-one-out two sample test + elif not pairwise: + + # Apply shuffler to group matrix rows/columns + group_selector = np.array(group_assignment)[group_shuffler] + + # Get difference of within-group summary statistics + # with group permutation + if summary_statistic == np.mean: + isc_sample = (np.tanh(np.mean(np.arctanh( + iscs[group_selector == group_labels[0], :]), axis=0)) - + np.tanh(np.mean(np.arctanh( + iscs[group_selector == group_labels[1], :]), axis=0))) + if summary_statistic == np.median: + isc_sample = (np.median( + iscs[group_selector == group_labels[0], :], axis=0) - + np.median( + iscs[group_selector == group_labels[1], :], axis=0)) + + # Tack our permuted ISCs onto the permutation distribution + distribution.append(isc_sample) + + # Update random state for next iteration + if not exact_permutations: + random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + + # Convert distribution to numpy array + distribution = np.array(distribution) + assert distribution.shape == (n_permutations, n_voxels) + + # Get p-value for actual median from shifted distribution + p = ((np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + 1) / + float((len(distribution) + 1)))[np.newaxis, :] + + if return_distribution: + return observed, p, distribution + elif not return_distribution: + return observed, p From 25b2e738627f45a68127cf83a6146fadd000ab7b Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Tue, 2 Oct 2018 16:16:15 -0400 Subject: [PATCH 06/43] Added function for circular time-shift randomization test --- brainiak/isfc.py | 139 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 4 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index d75c3a644..525fa58c2 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -35,7 +35,7 @@ from scipy.stats import pearsonr, zscore import itertools as it -def isc(data, pairwise=False, summary_statistic=None): +def isc(data, pairwise=False, summary_statistic=None, verbose=True): """Intersubject correlation For each voxel or ROI, compute the Pearson correlation between each @@ -107,8 +107,9 @@ def isc(data, pairwise=False, summary_statistic=None): n_subjects = data.shape[2] n_TRs = data.shape[0] n_voxels = data.shape[1] - print(f"Assuming {n_subjects} subjects with {n_TRs} time points " - f"and {n_voxels} voxel(s) or ROI(s).") + if verbose: + print(f"Assuming {n_subjects} subjects with {n_TRs} time points " + f"and {n_voxels} voxel(s) or ROI(s).") # Loop over each voxel or ROI voxel_iscs = [] @@ -117,7 +118,8 @@ def isc(data, pairwise=False, summary_statistic=None): if n_subjects == 2: iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] summary_statistic = None - print("Only two subjects! Simply computing Pearson correlation.") + if verbose: + print("Only two subjects! Simply computing Pearson correlation.") elif pairwise: iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: @@ -749,3 +751,132 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, return observed, p, distribution elif not return_distribution: return observed, p + + +def timeshift_isc(data, pairwise=False, summary_statistic=np.median, + n_shifts=1000, return_distribution=False, random_state=None): + + """Circular time-shift randomization for one-sample ISC test + + For a single voxel/ROI, take in response time series for multiple + subjects and apply a random temporal shift interval to each subject + prior to computing ISCs. Input should be list or dictionary of + n_samples x n_voxels time series data where each item in the list + or value in the dictionary corresponds to one subject's data. + + This implementation is based on the following publications: + + .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic + responses during watching a movie: localization in space and + frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, + 2010, Frontiers in Neuroinformatics, 4, 5. + + .. [Kauppi2014] "A versatile software package for inter-subject + correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, + J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. + + Parameters + ---------- + data : list or dict, time series data for multiple subjects + List or dictionary of response time series for multiple subjects + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match iscs variable + + summary_statistic : numpy function, default:np.median + Summary statistic, either np.median (default) or np.mean + + n_shifts : int, default:1000 + Number of randomly shifted samples + + return_distribution : bool, default:False + Optionally return the bootstrap distribution of summary statistics + + random_state = int, None, or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + observed : float, observed ISC (without time-shifting) + Actual ISCs + + p : float, p-value + p-value based on time-shifting randomization test + + distribution : ndarray, time-shifts by voxels (optional) + Time-shifted null distribution if return_bootstrap=True + """ + + # Convert list input to 3d and check shapes + if type(data) == list: + data_shape = data[0].shape + for i, d in enumerate(data): + if d.shape != data_shape: + raise ValueError("All ndarrays in input list " + "must be the same shape!") + if d.ndim == 1: + data[i] = d[:, np.newaxis] + data = np.dstack(data) + + # Convert input ndarray to 3d and check shape + elif type(data) == np.ndarray: + if data.ndim == 2: + data = data[:, np.newaxis, :] + elif data.ndim == 3: + pass + else: + raise ValueError("Input ndarray should have 2 " + f"or 3 dimensions (got {data.ndim})!") + + # Infer subjects, TRs, voxels and print for user to check + n_subjects = data.shape[2] + n_TRs = data.shape[0] + n_voxels = data.shape[1] + + # Get actual observed ISC + observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) + + # Roll axis to get subjects in first dimension for loop + data = np.rollaxis(data, 2, 0) + + # Iterate through randomized shifts to create null distribution + distribution = [] + for i in np.arange(n_shifts): + + # Random seed to be deterministically re-randomized at each iteration + if isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # Get a random set of shifts based on number of TRs, + shifts = prng.choice(np.arange(n_TRs), size=n_subjects, + replace=True) + + # Apply circular shift to each subject's time series + shifted_data = [] + for subject, shift in zip(data, shifts): + shifted_data.append(np.concatenate( + (subject[-shift:, :], subject[:-shift, :]))) + shifted_data = np.dstack(shifted_data) + + # Compute null ISC on shifted data + shifted_isc = isc(shifted_data, pairwise=pairwise, + summary_statistic=summary_statistic, verbose=False) + distribution.append(shifted_isc) + + # Update random state for next iteration + random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + + # Convert distribution to numpy array + distribution = np.vstack(distribution) + assert distribution.shape == (n_shifts, n_voxels) + + # Get p-value for actual median from shifted distribution + p = ((np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + 1) / + float((len(distribution) + 1)))[np.newaxis, :] + + if return_distribution: + return observed, p, distribution + elif not return_distribution: + return observed, p From decfbf9675d02f5a23fd1fb418a651edf0ad3274 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Tue, 2 Oct 2018 16:58:43 -0400 Subject: [PATCH 07/43] Fixed time-shift to only shift test subject in leave-one-out --- brainiak/isfc.py | 50 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 525fa58c2..3e68b7849 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -634,7 +634,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, if n_groups == 1: if summary_statistic == np.mean: observed = np.tanh(np.mean(np.arctanh(iscs), axis=0)) - if summary_statistic == np.median: + elif summary_statistic == np.median: observed = np.median(iscs, axis=0) # If two groups, get the observed difference @@ -645,7 +645,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, iscs[group_selector == group_labels[0], :]), axis=0)) - np.tanh(np.mean(np.arctanh( iscs[group_selector == group_labels[1], :]), axis=0))) - if summary_statistic == np.median: + elif summary_statistic == np.median: observed = (np.median( iscs[group_selector == group_labels[0], :], axis=0) - np.median( @@ -687,7 +687,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Get summary statistics on sign-flipped ISCs if summary_statistic == np.mean: isc_sample = np.tanh(np.mean(np.arctanh(isc_flipped), axis=0)) - if summary_statistic == np.median: + elif summary_statistic == np.median: isc_sample = np.median(isc_flipped, axis=0) # If two groups, set up group matrix get the observed difference @@ -726,7 +726,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, iscs[group_selector == group_labels[0], :]), axis=0)) - np.tanh(np.mean(np.arctanh( iscs[group_selector == group_labels[1], :]), axis=0))) - if summary_statistic == np.median: + elif summary_statistic == np.median: isc_sample = (np.median( iscs[group_selector == group_labels[0], :], axis=0) - np.median( @@ -837,7 +837,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) # Roll axis to get subjects in first dimension for loop - data = np.rollaxis(data, 2, 0) + if pairwise: + data = np.rollaxis(data, 2, 0) # Iterate through randomized shifts to create null distribution distribution = [] @@ -853,16 +854,35 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, shifts = prng.choice(np.arange(n_TRs), size=n_subjects, replace=True) - # Apply circular shift to each subject's time series - shifted_data = [] - for subject, shift in zip(data, shifts): - shifted_data.append(np.concatenate( - (subject[-shift:, :], subject[:-shift, :]))) - shifted_data = np.dstack(shifted_data) + # In pairwise approach, apply all shifts then compute pairwise ISCs + if pairwise: + + # Apply circular shift to each subject's time series + shifted_data = [] + for subject, shift in zip(data, shifts): + shifted_data.append(np.concatenate( + (subject[-shift:, :], subject[:-shift, :]))) + shifted_data = np.dstack(shifted_data) + + # Compute null ISC on shifted data for pairwise approach + shifted_isc = isc(shifted_data, pairwise=pairwise, + summary_statistic=summary_statistic, verbose=False) + + # In leave-one-out, apply shift only to each left-out participant + elif not pairwise: - # Compute null ISC on shifted data - shifted_isc = isc(shifted_data, pairwise=pairwise, - summary_statistic=summary_statistic, verbose=False) + shifted_isc = [] + for s, shift in enumerate(shifts): + shifted_subject = np.concatenate((data[-shift:, :, s], data[:-shift, :, s])) + nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) + loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, + summary_statistic=None, verbose=False) + shifted_isc.append(loo_isc) + if summary_statistic == np.mean: + shifted_isc = np.tanh(np.mean(np.arctanh(np.dstack(shifted_isc)), axis=2)) + elif summary_statistic == np.median: + shifted_isc = np.median(np.dstack(shifted_isc), axis=2) + distribution.append(shifted_isc) # Update random state for next iteration @@ -879,4 +899,4 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, if return_distribution: return observed, p, distribution elif not return_distribution: - return observed, p + return observed, p \ No newline at end of file From 511afd2794d1049ae81737cacc8ee47d84f81137 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Tue, 2 Oct 2018 18:56:34 -0400 Subject: [PATCH 08/43] New phase randomization test, both pairwise and leave-one-out --- brainiak/isfc.py | 204 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 197 insertions(+), 7 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 3e68b7849..9083089d0 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -33,6 +33,7 @@ import numpy as np from scipy.spatial.distance import squareform from scipy.stats import pearsonr, zscore +from scipy.fftpack import fft, ifft import itertools as it def isc(data, pairwise=False, summary_statistic=None, verbose=True): @@ -465,7 +466,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, row/column order of the subject-by-subject square ISC matrix even though the input ISCs should be supplied as the vectorized upper triangle of the square ISC matrix. Returns the observed ISC and permutation-based p-value (two-tailed - test). Optionall returns the permutation distribution of summary statistics. + test). Optionally returns the permutation distribution of summary statistics. According to Chen et al., 2016, this is the preferred nonparametric approach for controlling false positive rates (FPR) for two-sample tests. This approach may yield inflated FPRs for one-sample tests. @@ -758,11 +759,19 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, """Circular time-shift randomization for one-sample ISC test - For a single voxel/ROI, take in response time series for multiple - subjects and apply a random temporal shift interval to each subject - prior to computing ISCs. Input should be list or dictionary of - n_samples x n_voxels time series data where each item in the list - or value in the dictionary corresponds to one subject's data. + For each voxel or ROI, compute the actual ISC and p-values + from a null distribution of ISCs where response time series + are first circularly shifted by random intervals. If pairwise, + apply time-shift randomization to each subjects and compute pairwise + ISCs. If leave-one-out approach is used (pairwise=False), apply + the random time-shift to only the left-out subject in each iteration + of the leave-one-out procedure. Input data should be a list where + each item is a time-points by voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a single ndarray is + supplied, the last dimension is assumed to correspond to subjects. + Returns the observed ISC and p-values (two-tailed test). Optionally + returns the null distribution of ISCs computed on randomly time- + shifted data. This implementation is based on the following publications: @@ -878,6 +887,187 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, summary_statistic=None, verbose=False) shifted_isc.append(loo_isc) + + # Get summary statistics across left-out subjects + if summary_statistic == np.mean: + shifted_isc = np.tanh(np.mean(np.arctanh(np.dstack(shifted_isc)), axis=2)) + elif summary_statistic == np.median: + shifted_isc = np.median(np.dstack(shifted_isc), axis=2) + + distribution.append(shifted_isc) + + # Update random state for next iteration + random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + + # Convert distribution to numpy array + distribution = np.vstack(distribution) + assert distribution.shape == (n_shifts, n_voxels) + + # Get p-value for actual median from shifted distribution + p = ((np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + 1) / + float((len(distribution) + 1)))[np.newaxis, :] + + if return_distribution: + return observed, p, distribution + elif not return_distribution: + return observed, p + + +def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, + n_shifts=1000, return_distribution=False, random_state=None): + + """Phase randomization for one-sample ISC test + + For each voxel or ROI, compute the actual ISC and p-values + from a null distribution of ISCs where response time series + are phase randomized prior to computing ISC. If pairwise, + apply phase randomization to each subject and compute pairwise + ISCs. If leave-one-out approach is used (pairwise=False), only + apply phase randomization to the left-out subject in each iteration + of the leave-one-out procedure. Input data should be a list where + each item is a time-points by voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a single ndarray is + supplied, the last dimension is assumed to correspond to subjects. + Returns the observed ISC and p-values (two-tailed test). Optionally + returns the null distribution of ISCs computed on phase-randomized + data. + + This implementation is based on the following publications: + + .. [Lerner2011] "Topographic mapping of a hierarchy of temporal + receptive windows using a narrated story.", Y. Lerner, C. J. Honey, + L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. + + .. [Simony2016] "Dynamic reconfiguration of the default mode network + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. + + Parameters + ---------- + data : list or dict, time series data for multiple subjects + List or dictionary of response time series for multiple subjects + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match iscs variable + + summary_statistic : numpy function, default:np.median + Summary statistic, either np.median (default) or np.mean + + n_shifts : int, default:1000 + Number of randomly shifted samples + + return_distribution : bool, default:False + Optionally return the bootstrap distribution of summary statistics + + random_state = int, None, or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + observed : float, observed ISC (without time-shifting) + Actual ISCs + + p : float, p-value + p-value based on time-shifting randomization test + + distribution : ndarray, time-shifts by voxels (optional) + Time-shifted null distribution if return_bootstrap=True + """ + + # Convert list input to 3d and check shapes + if type(data) == list: + data_shape = data[0].shape + for i, d in enumerate(data): + if d.shape != data_shape: + raise ValueError("All ndarrays in input list " + "must be the same shape!") + if d.ndim == 1: + data[i] = d[:, np.newaxis] + data = np.dstack(data) + + # Convert input ndarray to 3d and check shape + elif type(data) == np.ndarray: + if data.ndim == 2: + data = data[:, np.newaxis, :] + elif data.ndim == 3: + pass + else: + raise ValueError("Input ndarray should have 2 " + f"or 3 dimensions (got {data.ndim})!") + + # Infer subjects, TRs, voxels and print for user to check + n_subjects = data.shape[2] + n_TRs = data.shape[0] + n_voxels = data.shape[1] + + # Get actual observed ISC + observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) + + # Iterate through randomized shifts to create null distribution + distribution = [] + for i in np.arange(n_shifts): + + # Random seed to be deterministically re-randomized at each iteration + if isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # Get randomized phase shifts + if data.shape[0] % 2 == 0: + # Why are we indexing from 1 not zero here? Vector is n_TRs / -1 long? + pos_freq = np.arange(1, data.shape[0] // 2) + neg_freq = np.arange(data.shape[0] - 1, data.shape[0] // 2, -1) + else: + pos_freq = np.arange(1, (data.shape[0] - 1) // 2 + 1) + neg_freq = np.arange(data.shape[0] - 1, (data.shape[0] - 1) // 2, -1) + + phase_shifts = prng.rand(len(pos_freq), 1, n_subjects) * 2 * np.math.pi + + # In pairwise approach, apply all shifts then compute pairwise ISCs + if pairwise: + + # Fast Fourier transform along time dimension of data + fft_data = fft(data, axis=0) + + # Shift pos and neg frequencies symmetrically, to keep signal real + fft_data[pos_freq, :, :] *= np.exp(1j * phase_shifts) + fft_data[neg_freq, :, :] *= np.exp(-1j * phase_shifts) + + # Inverse FFT to put data back in time domain for ISC + shifted_data = np.real(ifft(fft_data, axis=0)) + + # Compute null ISC on shifted data for pairwise approach + shifted_isc = isc(shifted_data, pairwise=True, + summary_statistic=summary_statistic, verbose=False) + + # In leave-one-out, apply shift only to each left-out participant + elif not pairwise: + + # Roll subject axis in phaseshifts for loop + phase_shifts = np.rollaxis(phase_shifts, 2, 0) + + shifted_isc = [] + for s, shift in enumerate(phase_shifts): + + # Apply FFT to left-out subject + fft_subject = fft(data[:, :, s], axis=0) + + # Shift pos and neg frequencies symmetrically, to keep signal real + fft_subject[pos_freq, :] *= np.exp(1j * shift) + fft_subject[neg_freq, :] *= np.exp(-1j * shift) + + # Inverse FFT to put data back in time domain for ISC + shifted_subject = np.real(ifft(fft_subject, axis=0)) + + # Compute ISC of shifted left-out subject against mean of N-1 subjects + nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) + loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, + summary_statistic=None, verbose=False) + shifted_isc.append(loo_isc) + + # Get summary statistics across left-out subjects if summary_statistic == np.mean: shifted_isc = np.tanh(np.mean(np.arctanh(np.dstack(shifted_isc)), axis=2)) elif summary_statistic == np.median: @@ -899,4 +1089,4 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, if return_distribution: return observed, p, distribution elif not return_distribution: - return observed, p \ No newline at end of file + return observed, p From 169c9db885105c7dca1b890d73e047ffd70355af Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Tue, 2 Oct 2018 19:44:10 -0400 Subject: [PATCH 09/43] Revamped ISFC with no internal stats (no pairwise yet) --- brainiak/isfc.py | 195 ++++++++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 97 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 9083089d0..e4afd41ad 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -14,7 +14,7 @@ """Intersubject correlation (ISC) analysis Functions for computing intersubject correlation (ISC) and variations -including intersubject fucntional correlations (ISFC) +including intersubject functional correlations (ISFC) Paper references: ISC: Hasson, U., Nir, Y., Levy, I., Fuhrmann, G. & Malach, R. Intersubject @@ -143,115 +143,116 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): raise ValueError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") return iscs + +def isfc(data, pairwise=False, summary_statistic=None, verbose=True): + + """Intersubject correlation -def isfc(D, collapse_subj=True, return_p=False, num_perm=1000, - two_sided=False, random_state=0, float_type=np.float64): - """Intersubject functional correlation - - Computes the correlation between the timecoure of each voxel in each - subject with the average of all other subjects' timecourses in *all* - voxels. By default the result is averaged across subjects, unless - collapse_subj is set to False. A null distribution can optionally be - computed using phase randomization, to compute a p value for each voxel-to- - voxel correlation. - - Uses the high performance compute_correlation routine from fcma.util + For each voxel or ROI, compute the Pearson correlation between each + subject's response time series and other subjects' response time series. + If pairwise is False (default), use the leave-one-out approach, where + correlation is computed between each subject and the average of the other + subjects. If pairwise is True, compute correlations between all pairs of + subjects. If summary_statistic is None, return N ISC values for N subjects + (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, + corresponding to the upper triangle of the pairwise correlation matrix + (see scipy.spatial.distance.squareform). Alternatively, supply either + np.mean or np.median to compute summary statistic of ISCs (Fisher Z will + be applied and inverted if using mean). Input data should be a list + where each item is a time-points by voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a single ndarray is + supplied, the last dimension is assumed to correspond to subjects. If + only two subjects are supplied, simply compute Pearson correlation + (precludes averaging in leave-one-out approach, and does not apply + summary statistic.) Output is an ndarray where the first dimension is + the number of subjects or pairs and the second dimension is the number + of voxels (or ROIs). + + The implementation is based on the following publication: + + .. [Simony2016] "Dynamic reconfiguration of the default mode network + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. Parameters ---------- - D : voxel by time by subject ndarray + data : list or ndarray fMRI data for which to compute ISFC - - collapse_subj : bool, default:True - Whether to average across subjects before returning result - - return_p : bool, default:False - Whether to use phase randomization to compute a p value for each voxel - - num_perm : int, default:1000 - Number of null samples to use for computing p values - - two_sided : bool, default:False - Whether the p value should be one-sided (testing only for being - above the null) or two-sided (testing for both significantly positive - and significantly negative values) - - random_state : RandomState or an int seed (0 by default) - A random number generator instance to define the state of the - random permutations generator. - - float_type : either float16, float32, or float64 - Depends on the required precision - and available memory in the system. - All the arrays generated during the execution will be cast - to specified float type in order to save memory. + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach + + summary_statistic : None + Return all ISFCs or collapse using np.mean or np.median Returns ------- - ISFC : voxel by voxel ndarray - (or voxel by voxel by subject ndarray, if collapse_subj=False) - pearson correlation between all pairs of voxels, across subjects + isfcs : subjects or pairs by voxels ndarray + ISFC for each subject or pair (or summary statistic) per voxel - p : ndarray the same shape as ISC (if return_p = True) - p values for each ISC value under the null distribution """ + + # Convert list input to 3d and check shapes + if type(data) == list: + data_shape = data[0].shape + for i, d in enumerate(data): + if d.shape != data_shape: + raise ValueError("All ndarrays in input list " + "must be the same shape!") + if d.ndim == 1: + data[i] = d[:, np.newaxis] + data = np.dstack(data) - n_vox = D.shape[0] - n_subj = D.shape[2] - - n_perm = num_perm*int(return_p) - max_null = -np.ones(n_perm, dtype=float_type) - min_null = np.ones(n_perm, dtype=float_type) - - ISFC = np.zeros((n_vox, n_vox, n_subj), dtype=float_type) - - for loo_subj in range(D.shape[2]): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - tmp_ISFC = compute_correlation(group, subj).astype(float_type) - # Symmetrize matrix - tmp_ISFC = (tmp_ISFC+tmp_ISFC.T)/2 - ISFC[:, :, loo_subj] = tmp_ISFC - if collapse_subj: - ISFC = np.mean(ISFC, axis=2) - - for p in range(n_perm): - # Randomize phases of D to create next null dataset - D = phase_randomize(D, random_state) - # Loop across choice of leave-one-out subject - ISFC_null = np.zeros((n_vox, n_vox), dtype=float_type) - for loo_subj in range(D.shape[2]): - group = np.mean(D[:, :, np.arange(n_subj) != loo_subj], axis=2) - subj = D[:, :, loo_subj] - tmp_ISFC = compute_correlation(group, subj).astype(float_type) - # Symmetrize matrix - tmp_ISFC = (tmp_ISFC+tmp_ISFC.T)/2 - - if not collapse_subj: - max_null[p] = max(np.max(tmp_ISFC), max_null[p]) - min_null[p] = min(np.min(tmp_ISFC), min_null[p]) - ISFC_null = ISFC_null + tmp_ISFC/n_subj - - if collapse_subj: - max_null[p] = np.max(ISFC_null) - min_null[p] = np.min(ISFC_null) - - if return_p: - p = p_from_null(ISFC, two_sided, - max_null_input=max_null, - min_null_input=min_null) - return ISFC, p - else: - return ISFC + # Convert input ndarray to 3d and check shape + elif type(data) == np.ndarray: + if data.ndim == 2: + data = data[:, np.newaxis, :] + elif data.ndim == 3: + pass + else: + raise ValueError("Input ndarray should have 2 " + f"or 3 dimensions (got {data.ndim})!") -''' two_sided : bool, default:False - Whether the p value should be one-sided (testing only for being - above the null) or two-sided (testing for both significantly positive - and significantly negative values) + # Infer subjects, TRs, voxels and print for user to check + n_subjects = data.shape[2] + n_TRs = data.shape[0] + n_voxels = data.shape[1] + if verbose: + print(f"Assuming {n_subjects} subjects with {n_TRs} time points " + f"and {n_voxels} voxel(s) or ROI(s).") - random_state : RandomState or an int seed (0 by default) - A random number generator instance to define the state of the - random permutations generator.''' + # Compute all pairwise ISFCs + if pairwise: + pass + + # Compute ISFCs using leave-one-out approach + if not pairwise: + + # Roll subject axis for loop + data = np.rollaxis(data, 2, 0) + + # Compute leave-one-out ISFCs + isfcs = [compute_correlation(subject, + np.mean([s for s in data + if s is not subject], + axis=0)) + for subject in data] + + # Transpose and average ISFC matrices for both directions + isfcs = np.dstack([(isfc_matrix + isfc_matrix.T) / 2 + for isfc_matrix in isfcs]) + + # Summarize results (if requested) + if summary_statistic == np.mean: + isfcs = np.tanh(np.mean(np.arctanh(isfcs), axis=2)) + elif summary_statistic == np.median: + isfcs = np.median(isfcs, axis=2) + elif not summary_statistic: + pass + else: + raise ValueError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") + return isfcs def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, From be46bf5aef6029128f46f16be952483ec3cb5291 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 3 Oct 2018 13:40:48 -0400 Subject: [PATCH 10/43] Added pairwise option to ISFC, fixed inputs to compute_correlation --- brainiak/isfc.py | 51 ++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index e4afd41ad..e1508fbe1 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -13,17 +13,10 @@ # limitations under the License. """Intersubject correlation (ISC) analysis -Functions for computing intersubject correlation (ISC) and variations -including intersubject functional correlations (ISFC) +Functions for computing intersubject correlation (ISC) and related +analyses (e.g., intersubject funtional correlations; ISFC), as well +as statistical tests designed specifically for ISC analyses. -Paper references: -ISC: Hasson, U., Nir, Y., Levy, I., Fuhrmann, G. & Malach, R. Intersubject -synchronization of cortical activity during natural vision. Science 303, -1634–1640 (2004). - -ISFC: Simony E, Honey CJ, Chen J, Lositsky O, Yeshurun Y, Wiesel A, Hasson U -(2016) Dynamic reconfiguration of the default mode network during narrative -comprehension. Nat Commun 7. """ # Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev @@ -221,27 +214,47 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): if verbose: print(f"Assuming {n_subjects} subjects with {n_TRs} time points " f"and {n_voxels} voxel(s) or ROI(s).") - - # Compute all pairwise ISFCs - if pairwise: - pass + + # Handle just two subjects properly + if n_subjects == 2: + isfcs = compute_correlation(np.ascontiguousarray(data[..., 0].T), + np.ascontiguousarray(data[..., 1].T)) + isfcs = (isfcs + isfcs.T) / 2 + assert isfcs.shape == (n_voxels, n_voxels) + summary_statistic = None + if verbose: + print("Only two subjects! Computing ISFC between them.") + + # Compute all pairwise ISFCs + elif pairwise: + isfcs = [] + for pair in it.combinations(np.arange(n_subjects), 2): + isfc_pair = compute_correlation(np.ascontiguousarray(data[..., pair[0]].T), + np.ascontiguousarray(data[..., pair[1]].T)) + isfc_pair = (isfc_pair + isfc_pair.T) / 2 + isfcs.append(isfc_pair) + isfcs = np.dstack(isfcs) + assert isfcs.shape == (n_voxels, n_voxels, + n_subjects * (n_subjects - 1) / 2) # Compute ISFCs using leave-one-out approach - if not pairwise: + elif not pairwise: # Roll subject axis for loop data = np.rollaxis(data, 2, 0) # Compute leave-one-out ISFCs - isfcs = [compute_correlation(subject, - np.mean([s for s in data - if s is not subject], - axis=0)) + isfcs = [compute_correlation(np.ascontiguousarray(subject.T), + np.ascontiguousarray(np.mean( + [s for s in data + if s is not subject], + axis=0).T)) for subject in data] # Transpose and average ISFC matrices for both directions isfcs = np.dstack([(isfc_matrix + isfc_matrix.T) / 2 for isfc_matrix in isfcs]) + assert isfcs.shape == (n_voxels, n_voxels, n_subjects) # Summarize results (if requested) if summary_statistic == np.mean: From 0a634a5d111061b145f00863cc1c100260d2a032 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 3 Oct 2018 20:29:59 -0400 Subject: [PATCH 11/43] Fixed bug in leave-one-out procedure for ISC and ISFC --- brainiak/isfc.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index e1508fbe1..ad211d30c 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -22,12 +22,12 @@ # Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev # Princeton University, 2018 -#from brainiak.fcma.util import compute_correlation import numpy as np from scipy.spatial.distance import squareform from scipy.stats import pearsonr, zscore from scipy.fftpack import fft, ifft import itertools as it +from brainiak.fcma.util import compute_correlation def isc(data, pairwise=False, summary_statistic=None, verbose=True): """Intersubject correlation @@ -118,10 +118,10 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: iscs = np.array([pearsonr(subject, - np.mean([s for s in voxel_data - if s is not subject], + np.mean(np.delete(voxel_data, + s, axis=0), axis=0))[0] - for subject in voxel_data]) + for s, subject in enumerate(voxel_data)]) voxel_iscs.append(iscs) iscs = np.column_stack(voxel_iscs) @@ -155,11 +155,10 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): where each item is a time-points by voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a single ndarray is supplied, the last dimension is assumed to correspond to subjects. If - only two subjects are supplied, simply compute Pearson correlation + only two subjects are supplied, simply ISFC between these two subjects (precludes averaging in leave-one-out approach, and does not apply - summary statistic.) Output is an ndarray where the first dimension is - the number of subjects or pairs and the second dimension is the number - of voxels (or ROIs). + summary statistic.) Output is an voxels by voxels by subjects (or pairs) + ndarray. The implementation is based on the following publication: @@ -246,10 +245,9 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): # Compute leave-one-out ISFCs isfcs = [compute_correlation(np.ascontiguousarray(subject.T), np.ascontiguousarray(np.mean( - [s for s in data - if s is not subject], + np.delete(data, s, axis=0), axis=0).T)) - for subject in data] + for s, subject in enumerate(data)] # Transpose and average ISFC matrices for both directions isfcs = np.dstack([(isfc_matrix + isfc_matrix.T) / 2 @@ -648,9 +646,9 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # If one group, just get observed summary statistic if n_groups == 1: if summary_statistic == np.mean: - observed = np.tanh(np.mean(np.arctanh(iscs), axis=0)) + observed = np.tanh(np.mean(np.arctanh(iscs), axis=0))[np.newaxis, :] elif summary_statistic == np.median: - observed = np.median(iscs, axis=0) + observed = np.median(iscs, axis=0)[np.newaxis, :] # If two groups, get the observed difference elif n_groups == 2: From 4f9f2c3501df726c2485892d850be93fe4d78280 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 3 Oct 2018 20:35:18 -0400 Subject: [PATCH 12/43] Added tests for new ISC/ISFC functionality and statistical tests --- tests/test_isfc.py | 499 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 tests/test_isfc.py diff --git a/tests/test_isfc.py b/tests/test_isfc.py new file mode 100644 index 000000000..0b3d64651 --- /dev/null +++ b/tests/test_isfc.py @@ -0,0 +1,499 @@ +import numpy as np +from brainiak.isfc import isc, bootstrap_isc + + +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data + + +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + other = np.random.randn(n_TRs, n_subjects)[:, np.newaxis, :] + data = np.repeat(np.column_stack((signal, signal, + ))[..., np.newaxis], 20, axis=2) + data = np.concatenate((data, other), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data + + +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) + + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) + + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic=np.mean) + assert isc_mean.shape == (1, n_voxels) + + isc_median = isc(data, pairwise=False, summary_statistic=np.median) + assert isc_median.shape == (1, n_voxels) + + try: + isc_min = isc(data, pairwise=False, summary_statistic=np.min) + except ValueError: + print("Correctly caught unexpected summary statistic") + + +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + n_bootstraps = 10 + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Test one-sample bootstrap test with pairwise approach + n_bootstraps = 10 + + iscs = isc(data, pairwise=True, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Check random seeds + iscs = isc(data, pairwise=False, summary_statistic=None) + distributions = [] + for random_state in [42, 42, None]: + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True, + random_state=random_state) + distributions.append(distribution) + assert np.array_equal(distributions[0], distributions[1]) + assert not np.array_equal(distributions[1], distributions[2]) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, ci, p = bootstrap_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, ci, p = bootstrap_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=False) + observed, ci, p = bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=True) + observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +# Test permutation test with group assignments +def test_permutation_isc(): + group_assignment = [1] * 10 + [2] * 10 + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + # Create dataset with two groups in pairwise approach + data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=5, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, + pairwise=True, + summary_statistic=np.mean, + n_permutations=200, + return_distribution=True) + + # Create data with two groups in leave-one-out approach + data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=10, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + + observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, + pairwise=False, + summary_statistic=np.median, + n_permutations=200, + return_distribution=True) + + # One-sample leave-one-out permutation test + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic=np.median, + n_permutations=200, + return_distribution=True) + + # One-sample pairwise permutation test + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=True, + summary_statistic=np.median, + n_permutations=200, + return_distribution=True) + + # Small one-sample pairwise exact test + data = simulated_timeseries(12, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic=np.median, + n_permutations=10000, + return_distribution=True) + + # Small two-sample pairwise exact test (and unequal groups) + data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic=np.mean, + n_permutations=10000, + return_distribution=True) + + # Small two-sample leave-one-out exact test (and unequal groups) + data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic=np.mean, + n_permutations=10000, + return_distribution=True) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p = permutation_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, p = permutation_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p = permutation_isc(iscs, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p = permutation_isc(iscs, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +def test_timeshift_isc(): + # Circular time-shift on one sample, leave-one-out + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic=np.median, + n_shifts=200, + return_distribution=True) + + # Circular time-shift on one sample, pairwise + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic=np.median, + n_shifts=200, + return_distribution=True) + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic=np.mean, + n_shifts=200, + return_distribution=True) + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p = timeshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, p = timeshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p = timeshift_isc(data, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p = timeshift_isc(data, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +# Phase randomization test +def test_phaseshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic=np.median, + n_shifts=200, + return_distribution=True) + + # Phase randomization one-sample test, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic=np.mean, + n_shifts=200, + return_distribution=True) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p = phaseshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, p = phaseshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p = phaseshift_isc(data, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p = phaseshift_isc(data, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +# Test ISFC +def test_isfc_options(): + from brainiak.fcma.util import compute_correlation + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + isfcs = isfc(data, pairwise=False, summary_statistic=None) + + # Just two subjects + isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None) + + # ISFC with pairwise approach + isfcs = isfc(data, pairwise=True, summary_statistic=None) + + # ISFC with summary statistics + isfcs = isfc(data, pairwise=True, summary_statistic=np.mean) + isfcs = isfc(data, pairwise=True, summary_statistic=np.median) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + isfcs = isfc(data, pairwise=False) + assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) + assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) + + isfcs = isfc(data, pairwise=True) + assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) + assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=False) + isfcs = isfc(data, pairwise=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=True) + isfcs = isfc(data, pairwise=True) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + + +if __name__ == '__main__': + test_isc_input() + test_isc_options() + test_isc_output() + test_bootstrap_isc() + test_permutation_isc() + test_timeshift_isc() + test_phaseshift_isc() + test_isfc_options() From 94d10cc003e235335ee2a8bf635b78025c7d5ae8 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 3 Oct 2018 20:55:18 -0400 Subject: [PATCH 13/43] Fixed location of test_isfc.py and added module imports --- tests/isfc/test_isfc.py | 543 +++++++++++++++++++++++++++++++++++----- 1 file changed, 476 insertions(+), 67 deletions(-) diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index ccd9fb6a5..0b3d64651 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -1,90 +1,499 @@ -import brainiak.isfc -from brainiak import image, io import numpy as np -import os +from brainiak.isfc import isc, bootstrap_isc -def test_ISC(): - # Create dataset in which one voxel is highly correlated across subjects - # and the other is not - D = np.zeros((2, 5, 3)) - D[:, :, 0] = \ - [[-0.36225433, -0.43482456, 0.26723158, 0.16461712, -0.37991465], - [-0.62305959, -0.46660116, -0.50037994, 1.81083754, 0.23499509]] - D[:, :, 1] = \ - [[-0.30484153, -0.49486988, 0.10966625, -0.19568572, -0.20535156], - [1.68267639, -0.78433298, -0.35875085, -0.6121344, 0.28603493]] - D[:, :, 2] = \ - [[-0.36593192, -0.50914734, 0.21397317, 0.30276589, -0.42637472], - [0.04127293, -0.67598379, -0.51549055, -0.64196342, 1.60686666]] +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data - (ISC, p) = brainiak.isfc.isc(D, return_p=True, num_perm=100, - two_sided=True, random_state=0) - assert np.isclose(ISC, [0.8909243, 0.0267954]).all(), \ - "Calculated ISC does not match ground truth" +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + other = np.random.randn(n_TRs, n_subjects)[:, np.newaxis, :] + data = np.repeat(np.column_stack((signal, signal, + ))[..., np.newaxis], 20, axis=2) + data = np.concatenate((data, other), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data - assert np.isclose(p, [0.02, 1]).all(), \ - "Calculated p values do not match ground truth" - (ISC, p) = brainiak.isfc.isc(D, return_p=True, num_perm=100, - two_sided=True, collapse_subj=False, - random_state=0) - true_ISC = [[0.98221543, 0.76747914, 0.92307833], - [-0.26377767, 0.01490501, 0.32925896]] - true_p = [[0, 0.6, 0.08], [1, 1, 1]] +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) - assert np.isclose(ISC, true_ISC).all(), \ - "Calculated ISC (non collapse) does not match ground truth" + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) - assert np.isclose(p, true_p).all(), \ - "Calculated p values (non collapse) do not match ground truth" + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) -def test_ISFC(): - curr_dir = os.path.dirname(__file__) + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) - mask_fname = os.path.join(curr_dir, 'mask.nii.gz') - mask = io.load_boolean_mask(mask_fname) - fnames = [os.path.join(curr_dir, 'subj1.nii.gz'), - os.path.join(curr_dir, 'subj2.nii.gz')] - masked_images = image.mask_images(io.load_images(fnames), mask) + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) - D = image.MaskedMultiSubjectData.from_masked_images(masked_images, - len(fnames)) - assert D.shape == (4, 5, 2), "Loaded data has incorrect shape" +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): - (ISFC, p) = brainiak.isfc.isfc(D, return_p=True, num_perm=100, - two_sided=True, random_state=0) + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) - ground_truth = \ - [[1, 1, 0, -1], - [1, 1, 0, -1], - [0, 0, 1, 0], - [-1, -1, 0, 1]] + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - ground_truth_p = 1 - np.abs(ground_truth) + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic=np.mean) + assert isc_mean.shape == (1, n_voxels) - assert np.isclose(ISFC, ground_truth).all(), \ - "Calculated ISFC does not match ground truth" + isc_median = isc(data, pairwise=False, summary_statistic=np.median) + assert isc_median.shape == (1, n_voxels) - assert np.isclose(p, ground_truth_p).all(), \ - "Calculated p values do not match ground truth" + try: + isc_min = isc(data, pairwise=False, summary_statistic=np.min) + except ValueError: + print("Correctly caught unexpected summary statistic") - (ISFC, p) = brainiak.isfc.isfc(D, return_p=True, num_perm=100, - two_sided=True, collapse_subj=False, - random_state=0) - array1 = np.array([[1, 1], [1, 1], [0, 0], [-1, -1]]) - array2 = -array1 - array3 = np.absolute(array1) - array4 = 1 - array3 - true_ISFC = np.array([array1, array1, array4, array2]) - true_p = np.array([array4, array4, array3, array4]) +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + n_bootstraps = 10 + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) - assert np.isclose(ISFC, true_ISFC).all(), \ - "Calculated ISFC (non collapse) does not match ground truth" + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True) + assert distribution.shape == (n_bootstraps, n_voxels) - assert np.isclose(p, true_p).all(), \ - "Calculated p values (non collapse) do not match ground truth" + # Test one-sample bootstrap test with pairwise approach + n_bootstraps = 10 + + iscs = isc(data, pairwise=True, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Check random seeds + iscs = isc(data, pairwise=False, summary_statistic=None) + distributions = [] + for random_state in [42, 42, None]: + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True, + random_state=random_state) + distributions.append(distribution) + assert np.array_equal(distributions[0], distributions[1]) + assert not np.array_equal(distributions[1], distributions[2]) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, ci, p = bootstrap_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, ci, p = bootstrap_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=False) + observed, ci, p = bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=True) + observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +# Test permutation test with group assignments +def test_permutation_isc(): + group_assignment = [1] * 10 + [2] * 10 + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + # Create dataset with two groups in pairwise approach + data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=5, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, + pairwise=True, + summary_statistic=np.mean, + n_permutations=200, + return_distribution=True) + + # Create data with two groups in leave-one-out approach + data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=10, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + + observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, + pairwise=False, + summary_statistic=np.median, + n_permutations=200, + return_distribution=True) + + # One-sample leave-one-out permutation test + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic=np.median, + n_permutations=200, + return_distribution=True) + + # One-sample pairwise permutation test + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=True, + summary_statistic=np.median, + n_permutations=200, + return_distribution=True) + + # Small one-sample pairwise exact test + data = simulated_timeseries(12, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic=np.median, + n_permutations=10000, + return_distribution=True) + + # Small two-sample pairwise exact test (and unequal groups) + data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic=np.mean, + n_permutations=10000, + return_distribution=True) + + # Small two-sample leave-one-out exact test (and unequal groups) + data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic=np.mean, + n_permutations=10000, + return_distribution=True) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p = permutation_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, p = permutation_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p = permutation_isc(iscs, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p = permutation_isc(iscs, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +def test_timeshift_isc(): + # Circular time-shift on one sample, leave-one-out + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic=np.median, + n_shifts=200, + return_distribution=True) + + # Circular time-shift on one sample, pairwise + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic=np.median, + n_shifts=200, + return_distribution=True) + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic=np.mean, + n_shifts=200, + return_distribution=True) + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p = timeshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, p = timeshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p = timeshift_isc(data, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p = timeshift_isc(data, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +# Phase randomization test +def test_phaseshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic=np.median, + n_shifts=200, + return_distribution=True) + + # Phase randomization one-sample test, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic=np.mean, + n_shifts=200, + return_distribution=True) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p = phaseshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + iscs = isc(data, pairwise=True) + observed, p = phaseshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .1 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p = phaseshift_isc(data, pairwise=False, summary_statistic=np.median) + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p = phaseshift_isc(data, pairwise=True, summary_statistic=np.mean) + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + +# Test ISFC +def test_isfc_options(): + from brainiak.fcma.util import compute_correlation + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + isfcs = isfc(data, pairwise=False, summary_statistic=None) + + # Just two subjects + isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None) + + # ISFC with pairwise approach + isfcs = isfc(data, pairwise=True, summary_statistic=None) + + # ISFC with summary statistics + isfcs = isfc(data, pairwise=True, summary_statistic=np.mean) + isfcs = isfc(data, pairwise=True, summary_statistic=np.median) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + isfcs = isfc(data, pairwise=False) + assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) + assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) + + isfcs = isfc(data, pairwise=True) + assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) + assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=False) + isfcs = isfc(data, pairwise=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=True) + isfcs = isfc(data, pairwise=True) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + + +if __name__ == '__main__': + test_isc_input() + test_isc_options() + test_isc_output() + test_bootstrap_isc() + test_permutation_isc() + test_timeshift_isc() + test_phaseshift_isc() + test_isfc_options() From d0fc472a77f1a4ade603b6ade6f97a3852676ad0 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sat, 6 Oct 2018 15:20:38 -0400 Subject: [PATCH 14/43] Small fixes in ISC/ISFC tests --- tests/isfc/test_isfc.py | 14 +- tests/test_isfc.py | 499 ---------------------------------------- 2 files changed, 11 insertions(+), 502 deletions(-) delete mode 100644 tests/test_isfc.py diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index 0b3d64651..08d187303 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -1,5 +1,6 @@ import numpy as np -from brainiak.isfc import isc, bootstrap_isc +from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_ics, + timeshift_isc, phaseshift_isc) # Create simple simulated data with high intersubject correlation @@ -179,14 +180,14 @@ def test_bootstrap_isc(): assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .05 iscs = isc(data, pairwise=True) observed, ci, p = bootstrap_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .05 # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=False) @@ -449,6 +450,13 @@ def test_phaseshift_isc(): # Test ISFC def test_isfc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + from brainiak.fcma.util import compute_correlation data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') diff --git a/tests/test_isfc.py b/tests/test_isfc.py deleted file mode 100644 index 0b3d64651..000000000 --- a/tests/test_isfc.py +++ /dev/null @@ -1,499 +0,0 @@ -import numpy as np -from brainiak.isfc import isc, bootstrap_isc - - -# Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, - noise=1, data_type='array', - random_state=None): - prng = np.random.RandomState(random_state) - if n_voxels: - signal = prng.randn(n_TRs, n_voxels) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs, n_voxels) * noise - for subject in np.arange(n_subjects)] - elif not n_voxels: - signal = prng.randn(n_TRs) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs) * noise - for subject in np.arange(n_subjects)] - if data_type == 'array': - if n_voxels: - data = np.dstack(data) - elif not n_voxels: - data = np.column_stack(data) - return data - - -# Create 3 voxel simulated data with correlated time series -def correlated_timeseries(n_subjects, n_TRs, noise=0, - random_state=None): - prng = np.random.RandomState(random_state) - signal = prng.randn(n_TRs) - other = np.random.randn(n_TRs, n_subjects)[:, np.newaxis, :] - data = np.repeat(np.column_stack((signal, signal, - ))[..., np.newaxis], 20, axis=2) - data = np.concatenate((data, other), axis=1) - data = data + np.random.randn(n_TRs, 3, n_subjects) * noise - return data - - -# Compute ISCs using different input types -# List of subjects with one voxel/ROI -def test_isc_input(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with one voxel/ROI - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - # List of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - -# Check pairwise and leave-one-out, and summary statistics for ISC -def test_isc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs_loo = isc(data, pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_subjects, n_voxels) - - iscs_pw = isc(data, pairwise=True, summary_statistic=None) - assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - - # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic=np.mean) - assert isc_mean.shape == (1, n_voxels) - - isc_median = isc(data, pairwise=False, summary_statistic=np.median) - assert isc_median.shape == (1, n_voxels) - - try: - isc_min = isc(data, pairwise=False, summary_statistic=np.min) - except ValueError: - print("Correctly caught unexpected summary statistic") - - -# Make sure ISC recovers correlations of 1 and less than 1 -def test_isc_output(): - - data = correlated_timeseries(20, 60, noise=0, - random_state=42) - iscs = isc(data, pairwise=False) - assert np.all(iscs[:, :2] == 1.) - assert np.all(iscs[:, -1] < 1.) - - iscs = isc(data, pairwise=True) - assert np.all(iscs[:, :2] == 1.) - assert np.all(iscs[:, -1] < 1.) - - -# Test one-sample bootstrap test -def test_bootstrap_isc(): - n_bootstraps = 10 - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs = isc(data, pairwise=False, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic=np.median, - n_bootstraps=n_bootstraps, - ci_percentile=95, - return_distribution=True) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Test one-sample bootstrap test with pairwise approach - n_bootstraps = 10 - - iscs = isc(data, pairwise=True, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic=np.median, - n_bootstraps=n_bootstraps, - ci_percentile=95, - return_distribution=True) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Check random seeds - iscs = isc(data, pairwise=False, summary_statistic=None) - distributions = [] - for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic=np.median, - n_bootstraps=n_bootstraps, - ci_percentile=95, - return_distribution=True, - random_state=random_state) - distributions.append(distribution) - assert np.array_equal(distributions[0], distributions[1]) - assert not np.array_equal(distributions[1], distributions[2]) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, ci, p = bootstrap_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - iscs = isc(data, pairwise=True) - observed, ci, p = bootstrap_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=False) - observed, ci, p = bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=True) - observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) - - -# Test permutation test with group assignments -def test_permutation_isc(): - group_assignment = [1] * 10 + [2] * 10 - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - # Create dataset with two groups in pairwise approach - data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=5, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, - pairwise=True, - summary_statistic=np.mean, - n_permutations=200, - return_distribution=True) - - # Create data with two groups in leave-one-out approach - data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=10, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - - observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, - pairwise=False, - summary_statistic=np.median, - n_permutations=200, - return_distribution=True) - - # One-sample leave-one-out permutation test - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=False, - summary_statistic=np.median, - n_permutations=200, - return_distribution=True) - - # One-sample pairwise permutation test - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=True, - summary_statistic=np.median, - n_permutations=200, - return_distribution=True) - - # Small one-sample pairwise exact test - data = simulated_timeseries(12, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=False, - summary_statistic=np.median, - n_permutations=10000, - return_distribution=True) - - # Small two-sample pairwise exact test (and unequal groups) - data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic=np.mean, - n_permutations=10000, - return_distribution=True) - - # Small two-sample leave-one-out exact test (and unequal groups) - data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic=np.mean, - n_permutations=10000, - return_distribution=True) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p = permutation_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - iscs = isc(data, pairwise=True) - observed, p = permutation_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p = permutation_isc(iscs, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p = permutation_isc(iscs, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) - - -def test_timeshift_isc(): - # Circular time-shift on one sample, leave-one-out - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic=np.median, - n_shifts=200, - return_distribution=True) - - # Circular time-shift on one sample, pairwise - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic=np.median, - n_shifts=200, - return_distribution=True) - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic=np.mean, - n_shifts=200, - return_distribution=True) - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p = timeshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - iscs = isc(data, pairwise=True) - observed, p = timeshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p = timeshift_isc(data, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p = timeshift_isc(data, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) - - -# Phase randomization test -def test_phaseshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic=np.median, - n_shifts=200, - return_distribution=True) - - # Phase randomization one-sample test, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic=np.mean, - n_shifts=200, - return_distribution=True) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p = phaseshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - iscs = isc(data, pairwise=True) - observed, p = phaseshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p = phaseshift_isc(data, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p = phaseshift_isc(data, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) - - -# Test ISFC -def test_isfc_options(): - from brainiak.fcma.util import compute_correlation - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - isfcs = isfc(data, pairwise=False, summary_statistic=None) - - # Just two subjects - isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None) - - # ISFC with pairwise approach - isfcs = isfc(data, pairwise=True, summary_statistic=None) - - # ISFC with summary statistics - isfcs = isfc(data, pairwise=True, summary_statistic=np.mean) - isfcs = isfc(data, pairwise=True, summary_statistic=np.median) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - isfcs = isfc(data, pairwise=False) - assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) - assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - - isfcs = isfc(data, pairwise=True) - assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) - assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=False) - isfcs = isfc(data, pairwise=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=True) - isfcs = isfc(data, pairwise=True) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - - -if __name__ == '__main__': - test_isc_input() - test_isc_options() - test_isc_output() - test_bootstrap_isc() - test_permutation_isc() - test_timeshift_isc() - test_phaseshift_isc() - test_isfc_options() From e9e37fa2ccd53585ced3f01bafe4b7979b4c5178 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sat, 6 Oct 2018 16:27:45 -0400 Subject: [PATCH 15/43] Check simulated data isn't inadvertently correlated in test, more verbose --- tests/isfc/test_isfc.py | 205 +++++++++++++++------------------------- 1 file changed, 77 insertions(+), 128 deletions(-) diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index 08d187303..8a7798595 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -1,152 +1,97 @@ +import brainiak.isfc +from brainiak import image, io import numpy as np +<<<<<<< HEAD from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_ics, timeshift_isc, phaseshift_isc) +======= +import os +>>>>>>> parent of 94d10cc... Fixed location of test_isfc.py and added module imports -# Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, - noise=1, data_type='array', - random_state=None): - prng = np.random.RandomState(random_state) - if n_voxels: - signal = prng.randn(n_TRs, n_voxels) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs, n_voxels) * noise - for subject in np.arange(n_subjects)] - elif not n_voxels: - signal = prng.randn(n_TRs) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs) * noise - for subject in np.arange(n_subjects)] - if data_type == 'array': - if n_voxels: - data = np.dstack(data) - elif not n_voxels: - data = np.column_stack(data) - return data - - -# Create 3 voxel simulated data with correlated time series -def correlated_timeseries(n_subjects, n_TRs, noise=0, - random_state=None): - prng = np.random.RandomState(random_state) - signal = prng.randn(n_TRs) - other = np.random.randn(n_TRs, n_subjects)[:, np.newaxis, :] - data = np.repeat(np.column_stack((signal, signal, - ))[..., np.newaxis], 20, axis=2) - data = np.concatenate((data, other), axis=1) - data = data + np.random.randn(n_TRs, 3, n_subjects) * noise - return data - - -# Compute ISCs using different input types -# List of subjects with one voxel/ROI -def test_isc_input(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) +def test_ISC(): + # Create dataset in which one voxel is highly correlated across subjects + # and the other is not + D = np.zeros((2, 5, 3)) + D[:, :, 0] = \ + [[-0.36225433, -0.43482456, 0.26723158, 0.16461712, -0.37991465], + [-0.62305959, -0.46660116, -0.50037994, 1.81083754, 0.23499509]] + D[:, :, 1] = \ + [[-0.30484153, -0.49486988, 0.10966625, -0.19568572, -0.20535156], + [1.68267639, -0.78433298, -0.35875085, -0.6121344, 0.28603493]] + D[:, :, 2] = \ + [[-0.36593192, -0.50914734, 0.21397317, 0.30276589, -0.42637472], + [0.04127293, -0.67598379, -0.51549055, -0.64196342, 1.60686666]] - # Array of subjects with one voxel/ROI - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) + (ISC, p) = brainiak.isfc.isc(D, return_p=True, num_perm=100, + two_sided=True, random_state=0) - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) + assert np.isclose(ISC, [0.8909243, 0.0267954]).all(), \ + "Calculated ISC does not match ground truth" - # List of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) + assert np.isclose(p, [0.02, 1]).all(), \ + "Calculated p values do not match ground truth" - # Array of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) + (ISC, p) = brainiak.isfc.isc(D, return_p=True, num_perm=100, + two_sided=True, collapse_subj=False, + random_state=0) + true_ISC = [[0.98221543, 0.76747914, 0.92307833], + [-0.26377767, 0.01490501, 0.32925896]] + true_p = [[0, 0.6, 0.08], [1, 1, 1]] - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) + assert np.isclose(ISC, true_ISC).all(), \ + "Calculated ISC (non collapse) does not match ground truth" + assert np.isclose(p, true_p).all(), \ + "Calculated p values (non collapse) do not match ground truth" -# Check pairwise and leave-one-out, and summary statistics for ISC -def test_isc_options(): - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs_loo = isc(data, pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_subjects, n_voxels) +def test_ISFC(): + curr_dir = os.path.dirname(__file__) - iscs_pw = isc(data, pairwise=True, summary_statistic=None) - assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) + mask_fname = os.path.join(curr_dir, 'mask.nii.gz') + mask = io.load_boolean_mask(mask_fname) + fnames = [os.path.join(curr_dir, 'subj1.nii.gz'), + os.path.join(curr_dir, 'subj2.nii.gz')] + masked_images = image.mask_images(io.load_images(fnames), mask) - # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic=np.mean) - assert isc_mean.shape == (1, n_voxels) + D = image.MaskedMultiSubjectData.from_masked_images(masked_images, + len(fnames)) - isc_median = isc(data, pairwise=False, summary_statistic=np.median) - assert isc_median.shape == (1, n_voxels) + assert D.shape == (4, 5, 2), "Loaded data has incorrect shape" - try: - isc_min = isc(data, pairwise=False, summary_statistic=np.min) - except ValueError: - print("Correctly caught unexpected summary statistic") + (ISFC, p) = brainiak.isfc.isfc(D, return_p=True, num_perm=100, + two_sided=True, random_state=0) + ground_truth = \ + [[1, 1, 0, -1], + [1, 1, 0, -1], + [0, 0, 1, 0], + [-1, -1, 0, 1]] -# Make sure ISC recovers correlations of 1 and less than 1 -def test_isc_output(): - - data = correlated_timeseries(20, 60, noise=0, - random_state=42) - iscs = isc(data, pairwise=False) - assert np.all(iscs[:, :2] == 1.) - assert np.all(iscs[:, -1] < 1.) - - iscs = isc(data, pairwise=True) - assert np.all(iscs[:, :2] == 1.) - assert np.all(iscs[:, -1] < 1.) - - -# Test one-sample bootstrap test -def test_bootstrap_isc(): - n_bootstraps = 10 - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) + ground_truth_p = 1 - np.abs(ground_truth) - iscs = isc(data, pairwise=False, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic=np.median, - n_bootstraps=n_bootstraps, - ci_percentile=95, - return_distribution=True) - assert distribution.shape == (n_bootstraps, n_voxels) + assert np.isclose(ISFC, ground_truth).all(), \ + "Calculated ISFC does not match ground truth" + + assert np.isclose(p, ground_truth_p).all(), \ + "Calculated p values do not match ground truth" + + (ISFC, p) = brainiak.isfc.isfc(D, return_p=True, num_perm=100, + two_sided=True, collapse_subj=False, + random_state=0) + array1 = np.array([[1, 1], [1, 1], [0, 0], [-1, -1]]) + array2 = -array1 + array3 = np.absolute(array1) + array4 = 1 - array3 + + true_ISFC = np.array([array1, array1, array4, array2]) + true_p = np.array([array4, array4, array3, array4]) + + assert np.isclose(ISFC, true_ISFC).all(), \ + "Calculated ISFC (non collapse) does not match ground truth" +<<<<<<< HEAD # Test one-sample bootstrap test with pairwise approach n_bootstraps = 10 @@ -505,3 +450,7 @@ def test_isfc_options(): test_timeshift_isc() test_phaseshift_isc() test_isfc_options() +======= + assert np.isclose(p, true_p).all(), \ + "Calculated p values (non collapse) do not match ground truth" +>>>>>>> parent of 94d10cc... Fixed location of test_isfc.py and added module imports From 55673980bb5d87c1bc2feb09340156712c1212c0 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sat, 6 Oct 2018 16:28:58 -0400 Subject: [PATCH 16/43] Revert "Check simulated data isn't inadvertently correlated in test, more verbose" This reverts commit e9e37fa2ccd53585ced3f01bafe4b7979b4c5178. --- tests/isfc/test_isfc.py | 205 +++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 77 deletions(-) diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index 8a7798595..08d187303 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -1,97 +1,152 @@ -import brainiak.isfc -from brainiak import image, io import numpy as np -<<<<<<< HEAD from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_ics, timeshift_isc, phaseshift_isc) -======= -import os ->>>>>>> parent of 94d10cc... Fixed location of test_isfc.py and added module imports -def test_ISC(): - # Create dataset in which one voxel is highly correlated across subjects - # and the other is not - D = np.zeros((2, 5, 3)) - D[:, :, 0] = \ - [[-0.36225433, -0.43482456, 0.26723158, 0.16461712, -0.37991465], - [-0.62305959, -0.46660116, -0.50037994, 1.81083754, 0.23499509]] - D[:, :, 1] = \ - [[-0.30484153, -0.49486988, 0.10966625, -0.19568572, -0.20535156], - [1.68267639, -0.78433298, -0.35875085, -0.6121344, 0.28603493]] - D[:, :, 2] = \ - [[-0.36593192, -0.50914734, 0.21397317, 0.30276589, -0.42637472], - [0.04127293, -0.67598379, -0.51549055, -0.64196342, 1.60686666]] - - (ISC, p) = brainiak.isfc.isc(D, return_p=True, num_perm=100, - two_sided=True, random_state=0) - - assert np.isclose(ISC, [0.8909243, 0.0267954]).all(), \ - "Calculated ISC does not match ground truth" - - assert np.isclose(p, [0.02, 1]).all(), \ - "Calculated p values do not match ground truth" - - (ISC, p) = brainiak.isfc.isc(D, return_p=True, num_perm=100, - two_sided=True, collapse_subj=False, - random_state=0) - true_ISC = [[0.98221543, 0.76747914, 0.92307833], - [-0.26377767, 0.01490501, 0.32925896]] - true_p = [[0, 0.6, 0.08], [1, 1, 1]] +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data + + +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + other = np.random.randn(n_TRs, n_subjects)[:, np.newaxis, :] + data = np.repeat(np.column_stack((signal, signal, + ))[..., np.newaxis], 20, axis=2) + data = np.concatenate((data, other), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data + + +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) - assert np.isclose(ISC, true_ISC).all(), \ - "Calculated ISC (non collapse) does not match ground truth" + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) - assert np.isclose(p, true_p).all(), \ - "Calculated p values (non collapse) do not match ground truth" + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) -def test_ISFC(): - curr_dir = os.path.dirname(__file__) + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) - mask_fname = os.path.join(curr_dir, 'mask.nii.gz') - mask = io.load_boolean_mask(mask_fname) - fnames = [os.path.join(curr_dir, 'subj1.nii.gz'), - os.path.join(curr_dir, 'subj2.nii.gz')] - masked_images = image.mask_images(io.load_images(fnames), mask) + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) - D = image.MaskedMultiSubjectData.from_masked_images(masked_images, - len(fnames)) - assert D.shape == (4, 5, 2), "Loaded data has incorrect shape" +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): - (ISFC, p) = brainiak.isfc.isfc(D, return_p=True, num_perm=100, - two_sided=True, random_state=0) + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) - ground_truth = \ - [[1, 1, 0, -1], - [1, 1, 0, -1], - [0, 0, 1, 0], - [-1, -1, 0, 1]] + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - ground_truth_p = 1 - np.abs(ground_truth) + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic=np.mean) + assert isc_mean.shape == (1, n_voxels) - assert np.isclose(ISFC, ground_truth).all(), \ - "Calculated ISFC does not match ground truth" + isc_median = isc(data, pairwise=False, summary_statistic=np.median) + assert isc_median.shape == (1, n_voxels) - assert np.isclose(p, ground_truth_p).all(), \ - "Calculated p values do not match ground truth" + try: + isc_min = isc(data, pairwise=False, summary_statistic=np.min) + except ValueError: + print("Correctly caught unexpected summary statistic") - (ISFC, p) = brainiak.isfc.isfc(D, return_p=True, num_perm=100, - two_sided=True, collapse_subj=False, - random_state=0) - array1 = np.array([[1, 1], [1, 1], [0, 0], [-1, -1]]) - array2 = -array1 - array3 = np.absolute(array1) - array4 = 1 - array3 - true_ISFC = np.array([array1, array1, array4, array2]) - true_p = np.array([array4, array4, array3, array4]) +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + n_bootstraps = 10 + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) - assert np.isclose(ISFC, true_ISFC).all(), \ - "Calculated ISFC (non collapse) does not match ground truth" + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic=np.median, + n_bootstraps=n_bootstraps, + ci_percentile=95, + return_distribution=True) + assert distribution.shape == (n_bootstraps, n_voxels) -<<<<<<< HEAD # Test one-sample bootstrap test with pairwise approach n_bootstraps = 10 @@ -450,7 +505,3 @@ def test_isfc_options(): test_timeshift_isc() test_phaseshift_isc() test_isfc_options() -======= - assert np.isclose(p, true_p).all(), \ - "Calculated p values (non collapse) do not match ground truth" ->>>>>>> parent of 94d10cc... Fixed location of test_isfc.py and added module imports From d2f91e59081e405ef5c3e3bbcd35e4433e5826d2 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sat, 6 Oct 2018 16:31:02 -0400 Subject: [PATCH 17/43] Check simulated data isn't inadvertently correlated in test, more verbose --- tests/isfc/test_isfc.py | 75 ++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index 08d187303..c940ace09 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -1,6 +1,7 @@ import numpy as np from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_ics, timeshift_isc, phaseshift_isc) +from scipy.spatial.distance import squareform # Create simple simulated data with high intersubject correlation @@ -31,10 +32,19 @@ def correlated_timeseries(n_subjects, n_TRs, noise=0, random_state=None): prng = np.random.RandomState(random_state) signal = prng.randn(n_TRs) - other = np.random.randn(n_TRs, n_subjects)[:, np.newaxis, :] + correlated = True + while correlated: + uncorrelated = np.random.randn(n_TRs, + n_subjects)[:, np.newaxis, :] + unc_max = np.amax(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + unc_mean = np.mean(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + if unc_max < .3 and np.abs(unc_mean) < .001: + correlated = False data = np.repeat(np.column_stack((signal, signal, ))[..., np.newaxis], 20, axis=2) - data = np.concatenate((data, other), axis=1) + data = np.concatenate((data, uncorrelated), axis=1) data = data + np.random.randn(n_TRs, 3, n_subjects) * noise return data @@ -49,6 +59,8 @@ def test_isc_input(): n_voxels = 30 random_state = 42 + print("Testing ISC inputs") + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=None, data_type='list', random_state=random_state) @@ -77,6 +89,8 @@ def test_isc_input(): # Check they're the same assert np.array_equal(iscs_list, iscs_array) + + print("Finished testing ISC inputs") # Check pairwise and leave-one-out, and summary statistics for ISC @@ -88,6 +102,8 @@ def test_isc_options(): n_voxels = 30 random_state = 42 + print("Testing ISC options") + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', random_state=random_state) @@ -109,11 +125,15 @@ def test_isc_options(): isc_min = isc(data, pairwise=False, summary_statistic=np.min) except ValueError: print("Correctly caught unexpected summary statistic") + + print("Finished testing ISC options") # Make sure ISC recovers correlations of 1 and less than 1 def test_isc_output(): + print("Testing ISC outputs") + data = correlated_timeseries(20, 60, noise=0, random_state=42) iscs = isc(data, pairwise=False) @@ -123,17 +143,21 @@ def test_isc_output(): iscs = isc(data, pairwise=True) assert np.all(iscs[:, :2] == 1.) assert np.all(iscs[:, -1] < 1.) + + print("Finished testing ISC outputs") # Test one-sample bootstrap test def test_bootstrap_isc(): - n_bootstraps = 10 # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 + n_bootstraps = 10 + + print("Testing bootstrap hypothesis test") data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', @@ -180,14 +204,14 @@ def test_bootstrap_isc(): assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .05 + assert p[0, 2] > .01 iscs = isc(data, pairwise=True) observed, ci, p = bootstrap_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .05 + assert p[0, 2] > .01 # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=False) @@ -199,16 +223,20 @@ def test_bootstrap_isc(): observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic=np.mean) assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + print("Finished testing bootstrap hypothesis test") + # Test permutation test with group assignments def test_permutation_isc(): - group_assignment = [1] * 10 + [2] * 10 - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 + group_assignment = [1] * 10 + [2] * 10 + + print("Testing permutation test") # Create dataset with two groups in pairwise approach data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, @@ -317,14 +345,14 @@ def test_permutation_isc(): assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .01 iscs = isc(data, pairwise=True) observed, p = permutation_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .01 # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) @@ -335,17 +363,21 @@ def test_permutation_isc(): iscs = isc(data, pairwise=True) observed, p = permutation_isc(iscs, pairwise=True, summary_statistic=np.mean) assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + print("Finished testing permutaton test") def test_timeshift_isc(): - # Circular time-shift on one sample, leave-one-out - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 + print("Testing circular time-shift") + + # Circular time-shift on one sample, leave-one-out data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=False, @@ -376,14 +408,14 @@ def test_timeshift_isc(): assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .01 iscs = isc(data, pairwise=True) observed, p = timeshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .01 # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) @@ -395,6 +427,8 @@ def test_timeshift_isc(): observed, p = timeshift_isc(data, pairwise=True, summary_statistic=np.mean) assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + print("Finished testing circular time-shift") + # Phase randomization test def test_phaseshift_isc(): @@ -404,6 +438,8 @@ def test_phaseshift_isc(): n_TRs = 60 n_voxels = 30 random_state = 42 + + print("Testing phase randomization") data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') @@ -428,14 +464,14 @@ def test_phaseshift_isc(): assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .01 iscs = isc(data, pairwise=True) observed, p = phaseshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .1 + assert p[0, 2] > .01 # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) @@ -446,6 +482,8 @@ def test_phaseshift_isc(): iscs = isc(data, pairwise=True) observed, p = phaseshift_isc(data, pairwise=True, summary_statistic=np.mean) assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + + print("Finished testing phase randomization") # Test ISFC @@ -457,6 +495,8 @@ def test_isfc_options(): n_voxels = 30 random_state = 42 + print("Testing ISFC options") + from brainiak.fcma.util import compute_correlation data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') @@ -494,7 +534,9 @@ def test_isfc_options(): isfcs = isfc(data, pairwise=True) for s in np.arange(len(iscs)): assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - + + print("Finished testing ISFC options") + if __name__ == '__main__': test_isc_input() @@ -505,3 +547,4 @@ def test_isfc_options(): test_timeshift_isc() test_phaseshift_isc() test_isfc_options() + print("Finished all ISC tests") From 8901841f8c7b48db2473e75285458632cc2d5274 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 7 Nov 2018 19:02:08 -0500 Subject: [PATCH 18/43] Added function in utils to get p-value from null distribution --- brainiak/isfc.py | 99 ++++++++++++++++++++++------------------- brainiak/utils/utils.py | 70 +++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 47 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index ad211d30c..ae8f9aa2f 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -22,12 +22,21 @@ # Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev # Princeton University, 2018 +#data aggregating +#summary statistic +#get p-value + import numpy as np +import logging from scipy.spatial.distance import squareform from scipy.stats import pearsonr, zscore from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation +from brainiak.utils.utils import compute_p_from_null_distribution + +logger = logging.getLogger(__name__) + def isc(data, pairwise=False, summary_statistic=None, verbose=True): """Intersubject correlation @@ -60,7 +69,7 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): Parameters ---------- - data : list or ndarray + data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISC pairwise : bool, default: False @@ -98,9 +107,7 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): f"or 3 dimensions (got {data.ndim})!") # Infer subjects, TRs, voxels and print for user to check - n_subjects = data.shape[2] - n_TRs = data.shape[0] - n_voxels = data.shape[1] + n_TRs, n_voxels, n_subjects = data.shape if verbose: print(f"Assuming {n_subjects} subjects with {n_TRs} time points " f"and {n_voxels} voxel(s) or ROI(s).") @@ -113,7 +120,7 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] summary_statistic = None if verbose: - print("Only two subjects! Simply computing Pearson correlation.") + logger.warning("Only two subjects! Simply computing Pearson correlation.") elif pairwise: iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: @@ -136,7 +143,7 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): raise ValueError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") return iscs - + def isfc(data, pairwise=False, summary_statistic=None, verbose=True): """Intersubject correlation @@ -169,7 +176,7 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): Parameters ---------- - data : list or ndarray + data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISFC pairwise : bool, default: False @@ -207,9 +214,7 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): f"or 3 dimensions (got {data.ndim})!") # Infer subjects, TRs, voxels and print for user to check - n_subjects = data.shape[2] - n_TRs = data.shape[0] - n_voxels = data.shape[1] + n_TRs, n_voxels, n_subjects = data.shape if verbose: print(f"Assuming {n_subjects} subjects with {n_TRs} time points " f"and {n_voxels} voxel(s) or ROI(s).") @@ -444,14 +449,14 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, shifted = distribution - observed # Get p-value for actual median from shifted distribution - p = ((np.sum(np.abs(shifted) >= np.abs(observed), axis=0) + 1) / - float((len(shifted) + 1)))[np.newaxis, :] + p = compute_p_from_null_distribution(observed, shifted, + side='two-sided', exact=False) if return_distribution: return observed, ci, p, distribution elif not return_distribution: return observed, ci, p - + def permutation_isc(iscs, group_assignment=None, pairwise=False, summary_statistic=np.median, n_permutations=1000, @@ -483,21 +488,16 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, for controlling false positive rates (FPR) for two-sample tests. This approach may yield inflated FPRs for one-sample tests. - The implementation is based on the following publications: + The implementation is based on the following publication: .. [Chen2016] "Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. - - .. [PhipsonSmyth2010] "Permutation p-values should never be zero: - calculating exact p-values when permutations are randomly drawn.", - B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics - and Molecular Biology, 9, 1544-6115. Parameters ---------- - iscs : list or ndarray, correlation matrix of iscs + iscs : list or ndarray, correlation matrix of ISCs ISC values for one or more voxels group_assignment : list or ndarray, group labels @@ -555,7 +555,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, elif type(group_assignment) == np.ndarray: group_assignment = group_assignment.tolist() else: - print("No group assignment provided, performing one-sample test.") + logger.warning("No group assignment provided, performing one-sample test.") if group_assignment and len(group_assignment) != n_subjects: raise ValueError(f"Group assignments ({len(group_assignment)}) " @@ -621,24 +621,25 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Set up permutation type (exact or Monte Carlo) if n_groups == 1: if n_permutations < 2**n_subjects: - print("One-sample approximate permutation test using sign-flipping " - "procedure with Monte Carlo resampling.") + logger.info("One-sample approximate permutation test using " + "sign-flipping procedure with Monte Carlo resampling.") exact_permutations = None elif n_permutations >= 2**n_subjects: - print("One-sample exact permutation test using sign-flipping " - f"procedure with 2**{n_subjects} ({2**n_subjects}) iterations.") + logger.info("One-sample exact permutation test using " + f"sign-flipping procedure with 2**{n_subjects} " + f"({2**n_subjects}) iterations.") exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) n_permutations = 2**n_subjects elif n_groups == 2: if n_permutations < np.math.factorial(n_subjects): - print("Two-sample approximate permutation test using " - "group randomization with Monte Carlo resampling.") + logger.info("Two-sample approximate permutation test using " + "group randomization with Monte Carlo resampling.") exact_permutations = None elif n_permutations >= np.math.factorial(n_subjects): - print("Two-sample exact permutation test using group " - f"randomization with {n_subjects}! " - f"({np.math.factorial(n_subjects)}) " - "iterations.") + logger.info("Two-sample exact permutation test using group " + f"randomization with {n_subjects}! " + f"({np.math.factorial(n_subjects)}) " + "iterations.") exact_permutations = list(it.permutations( np.arange(len(group_assignment)))) n_permutations = np.math.factorial(n_subjects) @@ -757,8 +758,12 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, assert distribution.shape == (n_permutations, n_voxels) # Get p-value for actual median from shifted distribution - p = ((np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + 1) / - float((len(distribution) + 1)))[np.newaxis, :] + if exact_permutations: + p = compute_p_from_null_distribution(observed, shifted, + side='two-sided', exact=True) + elif not exact_permutations: + p = compute_p_from_null_distribution(observed, shifted, + side='two-sided', exact=False) if return_distribution: return observed, p, distribution @@ -798,11 +803,11 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, Parameters ---------- - data : list or dict, time series data for multiple subjects - List or dictionary of response time series for multiple subjects - - pairwise : bool, default:False - Indicator of pairwise or leave-one-out, should match iscs variable + data : list or ndarray (n_TRs x n_voxels x n_subjects) + fMRI data for which to compute ISFC + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach summary_statistic : numpy function, default:np.median Summary statistic, either np.median (default) or np.mean @@ -916,8 +921,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, assert distribution.shape == (n_shifts, n_voxels) # Get p-value for actual median from shifted distribution - p = ((np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + 1) / - float((len(distribution) + 1)))[np.newaxis, :] + p = compute_p_from_null_distribution(observed, shifted, + side='two-sided', exact=False) if return_distribution: return observed, p, distribution @@ -957,11 +962,11 @@ def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, Parameters ---------- - data : list or dict, time series data for multiple subjects - List or dictionary of response time series for multiple subjects - - pairwise : bool, default:False - Indicator of pairwise or leave-one-out, should match iscs variable + data : list or ndarray (n_TRs x n_voxels x n_subjects) + fMRI data for which to compute ISFC + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach summary_statistic : numpy function, default:np.median Summary statistic, either np.median (default) or np.mean @@ -1095,8 +1100,8 @@ def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, assert distribution.shape == (n_shifts, n_voxels) # Get p-value for actual median from shifted distribution - p = ((np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + 1) / - float((len(distribution) + 1)))[np.newaxis, :] + p = compute_p_from_null_distribution(observed, shifted, + side='two-sided', exact=False) if return_distribution: return observed, p, distribution diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index 56119e02a..8d4858011 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -20,6 +20,9 @@ from sklearn.utils import check_random_state from scipy.fftpack import fft, ifft import math +import logging + +logger = logging.getLogger(__name__) """ @@ -815,3 +818,70 @@ def p_from_null(X, two_sided=False, p = 1 - max_null_ecdf(real_data) return p + + +def compute_p_from_null_distribution(observed, distribution, + side='two-sided', exact=False): + + """Compute p-value from null distribution + + Returns the p-value for an observed test statistic given a null + distribution. Performs either a 'two-sided' (i.e., two-tailed) + test (default) or a one-sided (i.e., one-tailed) test for either the + 'left' or 'right' side. First dimension of distribution should + correspond to the number of resampling iterations (e.g., the number + of permutations). For an exact test (exact=True), does not adjust + for the observed test statistic; otherwise, adjusts for observed + test statistic (prevents p-values of zero). + + The implementation is based on the following publication: + + .. [PhipsonSmyth2010] "Permutation p-values should never be zero: + calculating exact p-values when permutations are randomly drawn.", + B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics + and Molecular Biology, 9, 1544-6115. + + Parameters + ---------- + observed : float + Observed test statistic + + distribution : ndarray + Null distribution of test statistic + + side : str, default:'two-sided' + Perform one-sided ('left' or 'right') or 'two-sided' test + + Returns + ------- + p : float + p-value for observed test statistic based on null distribution + """ + + if side not in ('two-sided', 'left', 'right'): + raise ValueError("The value for 'side' must be either " + f"'two-sided', 'left', or 'right', got {side}") + + n_samples = len(distribution) + logger.info(f"Assuming {n_samples} resampling iterations") + + if side == 'two-sided': + # numerator for two-sided test + numerator = np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + elif side == 'left': + # numerator for one-sided test in left tail + numerator = np.sum(distribution <= observed, axis=0) + elif side == 'right': + # numerator for one-sided test in right tail + numerator = np.sum(distribution >= observed, axis=0) + + # If exact test (all possible permutations), do not adjust + if exact: + p = numerator / n_samples + + # If not exact test, adjust number of samples to account for + # observed statistic; prevents p-value from being zero + else: + p = (numerator + 1) / (n_samples + 1) + + return p \ No newline at end of file From 348c58765e6fecc02ff928ac4c29821cc0094b06 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 8 Nov 2018 20:05:32 -0500 Subject: [PATCH 19/43] New summary statistic function, minor changes to p-value computation --- brainiak/isfc.py | 213 +++++++++++++++++++++------------------- brainiak/utils/utils.py | 2 + tests/isfc/test_isfc.py | 72 +++++++------- 3 files changed, 151 insertions(+), 136 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index ae8f9aa2f..b92879f6a 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -22,10 +22,6 @@ # Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev # Princeton University, 2018 -#data aggregating -#summary statistic -#get p-value - import numpy as np import logging from scipy.spatial.distance import squareform @@ -49,8 +45,8 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): subjects. If summary_statistic is None, return N ISC values for N subjects (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, corresponding to the upper triangle of the pairwise correlation matrix - (see scipy.spatial.distance.squareform). Alternatively, supply either - np.mean or np.median to compute summary statistic of ISCs (Fisher Z will + (see scipy.spatial.distance.squareform). Alternatively, use either + 'mean' or 'median' to compute summary statistic of ISCs (Fisher Z will be applied and inverted if using mean). Input data should be a list where each item is a time-points by voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a single ndarray is @@ -72,11 +68,11 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISC - pairwise : bool, default: False + pairwise : bool, default:False Whether to use pairwise (True) or leave-one-out (False) approach - summary_statistic : None - Return all ISCs or collapse using np.mean or np.median + summary_statistic : None or str, default:None + Return all ISCs or collapse using 'mean' or 'median' Returns ------- @@ -133,14 +129,10 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): iscs = np.column_stack(voxel_iscs) # Summarize results (if requested) - if summary_statistic == np.mean: - iscs = np.tanh(summary_statistic(np.arctanh(iscs), axis=0))[np.newaxis, :] - elif summary_statistic == np.median: - iscs = summary_statistic(iscs, axis=0)[np.newaxis, :] - elif not summary_statistic: - pass - else: - raise ValueError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") + if summary_statistic: + iscs = compute_summary_statistic(iscs, summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] + return iscs @@ -156,8 +148,8 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): subjects. If summary_statistic is None, return N ISC values for N subjects (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, corresponding to the upper triangle of the pairwise correlation matrix - (see scipy.spatial.distance.squareform). Alternatively, supply either - np.mean or np.median to compute summary statistic of ISCs (Fisher Z will + (see scipy.spatial.distance.squareform). Alternatively, use either + 'mean' or 'median' to compute summary statistic of ISCs (Fisher Z will be applied and inverted if using mean). Input data should be a list where each item is a time-points by voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a single ndarray is @@ -182,8 +174,8 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): pairwise : bool, default: False Whether to use pairwise (True) or leave-one-out (False) approach - summary_statistic : None - Return all ISFCs or collapse using np.mean or np.median + summary_statistic : None or str, default:None + Return all ISFCs or collapse using 'mean' or 'median' Returns ------- @@ -260,18 +252,59 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): assert isfcs.shape == (n_voxels, n_voxels, n_subjects) # Summarize results (if requested) - if summary_statistic == np.mean: - isfcs = np.tanh(np.mean(np.arctanh(isfcs), axis=2)) - elif summary_statistic == np.median: - isfcs = np.median(isfcs, axis=2) - elif not summary_statistic: - pass - else: - raise ValueError("Unrecognized summary_statistic! Use None, np.median, or np.mean.") + if summary_statistic: + isfcs = compute_summary_statistic(isfcs, summary_statistic=summary_statistic, + axis=2) + return isfcs + + +def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): + + """Computes summary statistics for ISCs + + Computes either the 'mean' or 'median' across a set of ISCs. In the + case of the mean, ISC values are first Fisher Z transformed (arctanh), + averaged, then inverse Fisher Z transformed (tanh). + + The implementation is based on the following publication: + + .. [SilverDunlap1987] "Averaging corrlelation coefficients: should + Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, + Journal of Applied Psychology, 72, 146-148. + + Parameters + ---------- + iscs : list or ndarray + ISC values + + summary_statistic : str, default:'mean' + Summary statistic, 'mean' or 'median' + + axis : None or int or tuple of ints, optional + Axis or axes along which the means are computed. The default is to + compute the mean of the flattened array. + + Returns + ------- + statistic : float or ndarray + Summary statistic of ISC values + + """ + if summary_statistic not in ('mean', 'median'): + raise ValueError("Summary statistic must be 'mean' or 'median'") -def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, + # Compute summary statistic + if summary_statistic == 'mean': + statistic = np.tanh(np.nanmean(np.arctanh(iscs), axis=axis)) + elif summary_statistic == 'median': + statistic = np.nanmedian(iscs, axis=axis) + + return statistic + + +def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', n_bootstraps=1000, ci_percentile=95, return_distribution=False, random_state=None): @@ -289,7 +322,7 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, hypothesis test (Hall & Wilson, 1991). Uses subject-wise (not pair-wise) resampling in the pairwise approach. Returns the observed ISC, the confidence interval, and a p-value for the bootstrap hypothesis test. Optionally returns - the bootstrap distribution of summary statistics.According to Chen et al., + the bootstrap distribution of summary statistics. According to Chen et al., 2016, this is the preferred nonparametric approach for controlling false positive rates (FPR) for one-sample tests in the pairwise approach. @@ -311,8 +344,8 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, pairwise : bool, default:False Indicator of pairwise or leave-one-out, should match ISCs structure - summary_statistic : numpy function, default:np.median - Summary statistic, either np.median (default) or np.mean + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' n_bootstraps : int, default:1000 Number of bootstrap samples (subject-level with replacement) @@ -372,12 +405,8 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, "voxel(s) or ROI(s).") # Compute summary statistic for observed ISCs - if summary_statistic == np.mean: - observed = np.tanh(np.mean(np.arctanh(iscs), axis=0))[np.newaxis, :] - elif summary_statistic == np.median: - observed = summary_statistic(iscs, axis=0)[np.newaxis, :] - else: - raise TypeError("Unrecognized summary_statistic! Use np.median or np.mean.") + observed = compute_summary_statistic(iscs, summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] # Set up an empty list to build our bootstrap distribution distribution = [] @@ -427,12 +456,11 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, isc_sample = iscs[subject_sample, :] # Compute summary statistic for bootstrap ISCs per voxel - # (alternatively could construct distrubtion for all voxels + # (alternatively could construct distribution for all voxels # then compute statistics, but larger memory footprint) - if summary_statistic == np.mean: - distribution.append(np.tanh(np.nanmean(np.arctanh(isc_sample), axis=0))) - elif summary_statistic == np.median: - distribution.append(np.nanmedian(isc_sample, axis=0)) + distribution.append(compute_summary_statistic(isc_sample, + summary_statistic=summary_statistic, + axis=0)) # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) @@ -456,10 +484,10 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median, return observed, ci, p, distribution elif not return_distribution: return observed, ci, p - + def permutation_isc(iscs, group_assignment=None, pairwise=False, - summary_statistic=np.median, n_permutations=1000, + summary_statistic='median', n_permutations=1000, return_distribution=False, random_state=None): """Group-level permutation test for ISCs @@ -506,8 +534,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, pairwise : bool, default:False Indicator of pairwise or leave-one-out, should match ISCs variable - summary_statistic : numpy function, default:np.median - Summary statistic, either np.median (default) or np.mean + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' n_permutations : int, default:1000 Number of permutation iteration (randomizing group assignment) @@ -646,24 +674,17 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # If one group, just get observed summary statistic if n_groups == 1: - if summary_statistic == np.mean: - observed = np.tanh(np.mean(np.arctanh(iscs), axis=0))[np.newaxis, :] - elif summary_statistic == np.median: - observed = np.median(iscs, axis=0)[np.newaxis, :] - + observed = compute_summary_statistic(iscs, summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] + # If two groups, get the observed difference - elif n_groups == 2: - - if summary_statistic == np.mean: - observed = (np.tanh(np.mean(np.arctanh( - iscs[group_selector == group_labels[0], :]), axis=0)) - - np.tanh(np.mean(np.arctanh( - iscs[group_selector == group_labels[1], :]), axis=0))) - elif summary_statistic == np.median: - observed = (np.median( - iscs[group_selector == group_labels[0], :], axis=0) - - np.median( - iscs[group_selector == group_labels[1], :], axis=0)) + elif n_groups == 2: + observed = (compute_summary_statistic(iscs[group_selector == group_labels[0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic(iscs[group_selector == group_labels[1], :], + summary_statistic=summary_statistic, + axis=0)) observed = np.array(observed)[np.newaxis, :] # Set up an empty list to build our permutation distribution @@ -699,10 +720,9 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, isc_flipped = iscs * sign_flipper[:, np.newaxis] # Get summary statistics on sign-flipped ISCs - if summary_statistic == np.mean: - isc_sample = np.tanh(np.mean(np.arctanh(isc_flipped), axis=0)) - elif summary_statistic == np.median: - isc_sample = np.median(isc_flipped, axis=0) + isc_sample = compute_summary_statistic(isc_flipped, + summary_statistic=summary_statistic, + axis=0) # If two groups, set up group matrix get the observed difference elif n_groups == 2: @@ -734,17 +754,13 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, group_selector = np.array(group_assignment)[group_shuffler] # Get difference of within-group summary statistics - # with group permutation - if summary_statistic == np.mean: - isc_sample = (np.tanh(np.mean(np.arctanh( - iscs[group_selector == group_labels[0], :]), axis=0)) - - np.tanh(np.mean(np.arctanh( - iscs[group_selector == group_labels[1], :]), axis=0))) - elif summary_statistic == np.median: - isc_sample = (np.median( - iscs[group_selector == group_labels[0], :], axis=0) - - np.median( - iscs[group_selector == group_labels[1], :], axis=0)) + # with group permutation + isc_sample = (compute_summary_statistic(iscs[group_selector == group_labels[0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic(iscs[group_selector == group_labels[1], :], + summary_statistic=summary_statistic, + axis=0)) # Tack our permuted ISCs onto the permutation distribution distribution.append(isc_sample) @@ -759,10 +775,10 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Get p-value for actual median from shifted distribution if exact_permutations: - p = compute_p_from_null_distribution(observed, shifted, + p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=True) elif not exact_permutations: - p = compute_p_from_null_distribution(observed, shifted, + p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False) if return_distribution: @@ -771,7 +787,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, return observed, p -def timeshift_isc(data, pairwise=False, summary_statistic=np.median, +def timeshift_isc(data, pairwise=False, summary_statistic='median', n_shifts=1000, return_distribution=False, random_state=None): """Circular time-shift randomization for one-sample ISC test @@ -809,8 +825,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, pairwise : bool, default: False Whether to use pairwise (True) or leave-one-out (False) approach - summary_statistic : numpy function, default:np.median - Summary statistic, either np.median (default) or np.mean + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' n_shifts : int, default:1000 Number of randomly shifted samples @@ -906,10 +922,9 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects - if summary_statistic == np.mean: - shifted_isc = np.tanh(np.mean(np.arctanh(np.dstack(shifted_isc)), axis=2)) - elif summary_statistic == np.median: - shifted_isc = np.median(np.dstack(shifted_isc), axis=2) + shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), + summary_statistic=summary_statistic, + axis=2) distribution.append(shifted_isc) @@ -921,7 +936,7 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, assert distribution.shape == (n_shifts, n_voxels) # Get p-value for actual median from shifted distribution - p = compute_p_from_null_distribution(observed, shifted, + p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False) if return_distribution: @@ -930,7 +945,7 @@ def timeshift_isc(data, pairwise=False, summary_statistic=np.median, return observed, p -def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, +def phaseshift_isc(data, pairwise=False, summary_statistic='median', n_shifts=1000, return_distribution=False, random_state=None): """Phase randomization for one-sample ISC test @@ -968,8 +983,8 @@ def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, pairwise : bool, default: False Whether to use pairwise (True) or leave-one-out (False) approach - summary_statistic : numpy function, default:np.median - Summary statistic, either np.median (default) or np.mean + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' n_shifts : int, default:1000 Number of randomly shifted samples @@ -1085,11 +1100,9 @@ def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects - if summary_statistic == np.mean: - shifted_isc = np.tanh(np.mean(np.arctanh(np.dstack(shifted_isc)), axis=2)) - elif summary_statistic == np.median: - shifted_isc = np.median(np.dstack(shifted_isc), axis=2) - + shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), + summary_statistic=summary_statistic, + axis=2) distribution.append(shifted_isc) # Update random state for next iteration @@ -1100,10 +1113,10 @@ def phaseshift_isc(data, pairwise=False, summary_statistic=np.median, assert distribution.shape == (n_shifts, n_voxels) # Get p-value for actual median from shifted distribution - p = compute_p_from_null_distribution(observed, shifted, + p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False) if return_distribution: return observed, p, distribution elif not return_distribution: - return observed, p + return observed, p \ No newline at end of file diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index 8d4858011..feb2cd69c 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -883,5 +883,7 @@ def compute_p_from_null_distribution(observed, distribution, # observed statistic; prevents p-value from being zero else: p = (numerator + 1) / (n_samples + 1) + + p = p[np.newaxis, :] return p \ No newline at end of file diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index c940ace09..91ef904e0 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -115,14 +115,14 @@ def test_isc_options(): assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic=np.mean) + isc_mean = isc(data, pairwise=False, summary_statistic='mean') assert isc_mean.shape == (1, n_voxels) - isc_median = isc(data, pairwise=False, summary_statistic=np.median) + isc_median = isc(data, pairwise=False, summary_statistic='median') assert isc_median.shape == (1, n_voxels) try: - isc_min = isc(data, pairwise=False, summary_statistic=np.min) + isc_min = isc(data, pairwise=False, summary_statistic='min') except ValueError: print("Correctly caught unexpected summary statistic") @@ -165,7 +165,7 @@ def test_bootstrap_isc(): iscs = isc(data, pairwise=False, summary_statistic=None) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic=np.median, + summary_statistic='median', n_bootstraps=n_bootstraps, ci_percentile=95, return_distribution=True) @@ -176,7 +176,7 @@ def test_bootstrap_isc(): iscs = isc(data, pairwise=True, summary_statistic=None) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic=np.median, + summary_statistic='median', n_bootstraps=n_bootstraps, ci_percentile=95, return_distribution=True) @@ -187,7 +187,7 @@ def test_bootstrap_isc(): distributions = [] for random_state in [42, 42, None]: observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic=np.median, + summary_statistic='median', n_bootstraps=n_bootstraps, ci_percentile=95, return_distribution=True, @@ -215,13 +215,13 @@ def test_bootstrap_isc(): # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=False) - observed, ci, p = bootstrap_isc(iscs, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + observed, ci, p = bootstrap_isc(iscs, pairwise=False, summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=True) - observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='median')) print("Finished testing bootstrap hypothesis test") @@ -249,7 +249,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, pairwise=True, - summary_statistic=np.mean, + summary_statistic='mean', n_permutations=200, return_distribution=True) @@ -265,7 +265,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, pairwise=False, - summary_statistic=np.median, + summary_statistic='mean', n_permutations=200, return_distribution=True) @@ -277,7 +277,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic=np.median, + summary_statistic='median', n_permutations=200, return_distribution=True) @@ -286,7 +286,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, pairwise=True, - summary_statistic=np.median, + summary_statistic='median', n_permutations=200, return_distribution=True) @@ -298,7 +298,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic=np.median, + summary_statistic='median', n_permutations=10000, return_distribution=True) @@ -315,7 +315,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, pairwise=True, - summary_statistic=np.mean, + summary_statistic='mean', n_permutations=10000, return_distribution=True) @@ -333,7 +333,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, pairwise=False, - summary_statistic=np.mean, + summary_statistic='mean', n_permutations=10000, return_distribution=True) @@ -356,13 +356,13 @@ def test_permutation_isc(): # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) - observed, p = permutation_isc(iscs, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + observed, p = permutation_isc(iscs, pairwise=False, summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) - observed, p = permutation_isc(iscs, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + observed, p = permutation_isc(iscs, pairwise=True, summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) print("Finished testing permutaton test") @@ -381,7 +381,7 @@ def test_timeshift_isc(): data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic=np.median, + summary_statistic='median', n_shifts=200, return_distribution=True) @@ -389,7 +389,7 @@ def test_timeshift_isc(): data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic=np.median, + summary_statistic='median', n_shifts=200, return_distribution=True) @@ -397,7 +397,7 @@ def test_timeshift_isc(): data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic=np.mean, + summary_statistic='mean', n_shifts=200, return_distribution=True) # Check output p-values @@ -419,13 +419,13 @@ def test_timeshift_isc(): # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) - observed, p = timeshift_isc(data, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + observed, p = timeshift_isc(data, pairwise=False, summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) - observed, p = timeshift_isc(data, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + observed, p = timeshift_isc(data, pairwise=True, summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) print("Finished testing circular time-shift") @@ -444,7 +444,7 @@ def test_phaseshift_isc(): data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic=np.median, + summary_statistic='median', n_shifts=200, return_distribution=True) @@ -452,7 +452,7 @@ def test_phaseshift_isc(): data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic=np.mean, + summary_statistic='mean', n_shifts=200, return_distribution=True) @@ -475,13 +475,13 @@ def test_phaseshift_isc(): # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) - observed, p = phaseshift_isc(data, pairwise=False, summary_statistic=np.median) - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic=np.median)) + observed, p = phaseshift_isc(data, pairwise=False, summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) - observed, p = phaseshift_isc(data, pairwise=True, summary_statistic=np.mean) - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic=np.mean)) + observed, p = phaseshift_isc(data, pairwise=True, summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) print("Finished testing phase randomization") @@ -509,8 +509,8 @@ def test_isfc_options(): isfcs = isfc(data, pairwise=True, summary_statistic=None) # ISFC with summary statistics - isfcs = isfc(data, pairwise=True, summary_statistic=np.mean) - isfcs = isfc(data, pairwise=True, summary_statistic=np.median) + isfcs = isfc(data, pairwise=True, summary_statistic='mean') + isfcs = isfc(data, pairwise=True, summary_statistic='median') # Check output p-values data = correlated_timeseries(20, 60, noise=.5, From b1bf090064c09e2b28fac6303b547afb9248d5b8 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 8 Nov 2018 20:32:14 -0500 Subject: [PATCH 20/43] Removed 'verbose' functionality and replaced 'print' with 'logging' --- brainiak/isfc.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index b92879f6a..2f1c82a3d 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -34,7 +34,7 @@ logger = logging.getLogger(__name__) -def isc(data, pairwise=False, summary_statistic=None, verbose=True): +def isc(data, pairwise=False, summary_statistic=None): """Intersubject correlation For each voxel or ROI, compute the Pearson correlation between each @@ -102,11 +102,10 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): raise ValueError("Input ndarray should have 2 " f"or 3 dimensions (got {data.ndim})!") - # Infer subjects, TRs, voxels and print for user to check + # Infer subjects, TRs, voxels and log for user to check n_TRs, n_voxels, n_subjects = data.shape - if verbose: - print(f"Assuming {n_subjects} subjects with {n_TRs} time points " - f"and {n_voxels} voxel(s) or ROI(s).") + logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " + f"and {n_voxels} voxel(s) or ROI(s) for ISC analysis.") # Loop over each voxel or ROI voxel_iscs = [] @@ -115,8 +114,7 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): if n_subjects == 2: iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] summary_statistic = None - if verbose: - logger.warning("Only two subjects! Simply computing Pearson correlation.") + logger.warning("Only two subjects! Simply computing Pearson correlation.") elif pairwise: iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: @@ -136,7 +134,7 @@ def isc(data, pairwise=False, summary_statistic=None, verbose=True): return iscs -def isfc(data, pairwise=False, summary_statistic=None, verbose=True): +def isfc(data, pairwise=False, summary_statistic=None): """Intersubject correlation @@ -207,9 +205,8 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): # Infer subjects, TRs, voxels and print for user to check n_TRs, n_voxels, n_subjects = data.shape - if verbose: - print(f"Assuming {n_subjects} subjects with {n_TRs} time points " - f"and {n_voxels} voxel(s) or ROI(s).") + logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " + f"and {n_voxels} voxel(s) or ROI(s) for ISFC analysis.") # Handle just two subjects properly if n_subjects == 2: @@ -218,8 +215,7 @@ def isfc(data, pairwise=False, summary_statistic=None, verbose=True): isfcs = (isfcs + isfcs.T) / 2 assert isfcs.shape == (n_voxels, n_voxels) summary_statistic = None - if verbose: - print("Only two subjects! Computing ISFC between them.") + logger.warning("Only two subjects! Computing ISFC between them.") # Compute all pairwise ISFCs elif pairwise: @@ -401,8 +397,8 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', # Infer subjects, voxels and print for user to check n_voxels = iscs.shape[1] - print(f"Assuming {n_subjects} subjects with and {n_voxels} " - "voxel(s) or ROI(s).") + logger.info(f"Assuming {n_subjects} subjects with and {n_voxels} " + "voxel(s) or ROI(s) in bootstrap ISC test.") # Compute summary statistic for observed ISCs observed = compute_summary_statistic(iscs, summary_statistic=summary_statistic, @@ -643,8 +639,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Infer subjects, groups, voxels and print for user to check n_voxels = iscs.shape[1] - print(f"Assuming {n_subjects} subjects, {n_groups} group(s), " - f"and {n_voxels} voxel(s) or ROI(s).") + logging.info(f"Assuming {n_subjects} subjects, {n_groups} group(s), " + f"and {n_voxels} voxel(s) or ROI(s) for permutation ISC test.") # Set up permutation type (exact or Monte Carlo) if n_groups == 1: @@ -908,7 +904,7 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', # Compute null ISC on shifted data for pairwise approach shifted_isc = isc(shifted_data, pairwise=pairwise, - summary_statistic=summary_statistic, verbose=False) + summary_statistic=summary_statistic) # In leave-one-out, apply shift only to each left-out participant elif not pairwise: @@ -918,7 +914,7 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', shifted_subject = np.concatenate((data[-shift:, :, s], data[:-shift, :, s])) nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, - summary_statistic=None, verbose=False) + summary_statistic=None) shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects @@ -1072,7 +1068,7 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Compute null ISC on shifted data for pairwise approach shifted_isc = isc(shifted_data, pairwise=True, - summary_statistic=summary_statistic, verbose=False) + summary_statistic=summary_statistic) # In leave-one-out, apply shift only to each left-out participant elif not pairwise: @@ -1096,7 +1092,7 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Compute ISC of shifted left-out subject against mean of N-1 subjects nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, - summary_statistic=None, verbose=False) + summary_statistic=None) shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects From a4b5070bd3219d9d3b55f8fdb25d7f42d094f9aa Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Fri, 9 Nov 2018 11:45:45 -0500 Subject: [PATCH 21/43] Added 'axis' argument to p-value computation --- brainiak/isfc.py | 29 +++++++++++++++++++++++------ brainiak/utils/utils.py | 22 ++++++++++++---------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 2f1c82a3d..5ea623590 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -474,7 +474,11 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', # Get p-value for actual median from shifted distribution p = compute_p_from_null_distribution(observed, shifted, - side='two-sided', exact=False) + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] if return_distribution: return observed, ci, p, distribution @@ -772,10 +776,15 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Get p-value for actual median from shifted distribution if exact_permutations: p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=True) + side='two-sided', exact=True, + axis=0) elif not exact_permutations: p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False) + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] if return_distribution: return observed, p, distribution @@ -933,7 +942,11 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', # Get p-value for actual median from shifted distribution p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False) + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] if return_distribution: return observed, p, distribution @@ -1043,7 +1056,7 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', prng = np.random.RandomState(random_state) # Get randomized phase shifts - if data.shape[0] % 2 == 0: + if n_TRs % 2 == 0: # Why are we indexing from 1 not zero here? Vector is n_TRs / -1 long? pos_freq = np.arange(1, data.shape[0] // 2) neg_freq = np.arange(data.shape[0] - 1, data.shape[0] // 2, -1) @@ -1110,7 +1123,11 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Get p-value for actual median from shifted distribution p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False) + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] if return_distribution: return observed, p, distribution diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index feb2cd69c..2aa1469e1 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -821,18 +821,19 @@ def p_from_null(X, two_sided=False, def compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False): + side='two-sided', exact=False, + axis=None): """Compute p-value from null distribution Returns the p-value for an observed test statistic given a null distribution. Performs either a 'two-sided' (i.e., two-tailed) test (default) or a one-sided (i.e., one-tailed) test for either the - 'left' or 'right' side. First dimension of distribution should - correspond to the number of resampling iterations (e.g., the number - of permutations). For an exact test (exact=True), does not adjust + 'left' or 'right' side. For an exact test (exact=True), does not adjust for the observed test statistic; otherwise, adjusts for observed - test statistic (prevents p-values of zero). + test statistic (prevents p-values of zero). If a multidimensional + distribution is provided, use axis argument to specify which axis indexes + resampling iterations. The implementation is based on the following publication: @@ -852,6 +853,9 @@ def compute_p_from_null_distribution(observed, distribution, side : str, default:'two-sided' Perform one-sided ('left' or 'right') or 'two-sided' test + axis: None or int, default:None + Axis indicating resampling iterations in input distribution + Returns ------- p : float @@ -867,13 +871,13 @@ def compute_p_from_null_distribution(observed, distribution, if side == 'two-sided': # numerator for two-sided test - numerator = np.sum(np.abs(distribution) >= np.abs(observed), axis=0) + numerator = np.sum(np.abs(distribution) >= np.abs(observed), axis=axis) elif side == 'left': # numerator for one-sided test in left tail - numerator = np.sum(distribution <= observed, axis=0) + numerator = np.sum(distribution <= observed, axis=axis) elif side == 'right': # numerator for one-sided test in right tail - numerator = np.sum(distribution >= observed, axis=0) + numerator = np.sum(distribution >= observed, axis=axis) # If exact test (all possible permutations), do not adjust if exact: @@ -883,7 +887,5 @@ def compute_p_from_null_distribution(observed, distribution, # observed statistic; prevents p-value from being zero else: p = (numerator + 1) / (n_samples + 1) - - p = p[np.newaxis, :] return p \ No newline at end of file From 34356c5b01a47b897a7d41f1b9a3919a53f23b05 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Fri, 9 Nov 2018 13:09:19 -0500 Subject: [PATCH 22/43] Fixed explosive 'Only two subjects' logging warning for time-/phase-shift --- brainiak/isfc.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 5ea623590..8707cbf35 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -29,7 +29,7 @@ from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation -from brainiak.utils.utils import compute_p_from_null_distribution +#from brainiak.utils.utils import compute_p_from_null_distribution logger = logging.getLogger(__name__) @@ -566,6 +566,10 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, if iscs.ndim == 1: iscs = iscs[:, np.newaxis] + # Check for valid summary statistic + if summary_statistic not in ('mean', 'median'): + raise ValueError("Summary statistic must be 'mean' or 'median'") + # Check if incoming pairwise matrix is vectorized triangle if pairwise: try: @@ -637,7 +641,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, elif len(np.unique(group_assignment)) > 2: raise ValueError("This test is not valid for more than " - f"2 groups! (got {n_groups})") + f"2 groups! (got {len(np.unique(group_assignment))})") else: raise ValueError("Invalid group assignments!") @@ -886,6 +890,10 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', # Roll axis to get subjects in first dimension for loop if pairwise: data = np.rollaxis(data, 2, 0) + + # Temporarily suppress ISC warning when constructing distribution + logging_level = logger.getEffectiveLevel() + logger.setLevel('CRITICAL') # Iterate through randomized shifts to create null distribution distribution = [] @@ -933,6 +941,9 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', distribution.append(shifted_isc) + # Re-enable logging at original level + logger.setLevel(logging_level) + # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) @@ -1045,6 +1056,10 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Get actual observed ISC observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) + # Temporarily suppress ISC warning when constructing distribution + logging_level = logger.getEffectiveLevel() + logger.setLevel('CRITICAL') + # Iterate through randomized shifts to create null distribution distribution = [] for i in np.arange(n_shifts): @@ -1114,6 +1129,9 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', axis=2) distribution.append(shifted_isc) + # Re-enable logging at original level + logger.setLevel(logging_level) + # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) From 7a2994c9023f8ea9d443d36695101333ea794784 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Fri, 9 Nov 2018 14:21:35 -0500 Subject: [PATCH 23/43] Always returns distribution; switched all logger warnings to 'info' level --- brainiak/isfc.py | 80 ++++++--------------- tests/isfc/test_isfc.py | 152 +++++++++++++++++++++------------------- 2 files changed, 99 insertions(+), 133 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 8707cbf35..60b2eff3f 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -11,6 +11,7 @@ # 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. + """Intersubject correlation (ISC) analysis Functions for computing intersubject correlation (ISC) and related @@ -114,7 +115,7 @@ def isc(data, pairwise=False, summary_statistic=None): if n_subjects == 2: iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] summary_statistic = None - logger.warning("Only two subjects! Simply computing Pearson correlation.") + logger.info("Only two subjects! Simply computing Pearson correlation.") elif pairwise: iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: @@ -215,7 +216,7 @@ def isfc(data, pairwise=False, summary_statistic=None): isfcs = (isfcs + isfcs.T) / 2 assert isfcs.shape == (n_voxels, n_voxels) summary_statistic = None - logger.warning("Only two subjects! Computing ISFC between them.") + logger.info("Only two subjects! Computing ISFC between them.") # Compute all pairwise ISFCs elif pairwise: @@ -301,8 +302,7 @@ def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', - n_bootstraps=1000, ci_percentile=95, - return_distribution=False, random_state=None): + n_bootstraps=1000, ci_percentile=95, random_state=None): """One-sample group-level bootstrap hypothesis test for ISCs @@ -317,7 +317,7 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', actual summary statistic (effectively to zero) for two-tailed null hypothesis test (Hall & Wilson, 1991). Uses subject-wise (not pair-wise) resampling in the pairwise approach. Returns the observed ISC, the confidence - interval, and a p-value for the bootstrap hypothesis test. Optionally returns + interval, and a p-value for the bootstrap hypothesis test, as well as the bootstrap distribution of summary statistics. According to Chen et al., 2016, this is the preferred nonparametric approach for controlling false positive rates (FPR) for one-sample tests in the pairwise approach. @@ -348,9 +348,6 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', ci_percentile : int, default:95 Percentile for computing confidence intervals - - return_distribution : bool, default:False - Optionally return the bootstrap distribution of summary statistics random_state = int or None, default:None Initial random seed @@ -480,15 +477,12 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', # Reshape p-values to fit with data shape p = p[np.newaxis, :] - if return_distribution: - return observed, ci, p, distribution - elif not return_distribution: - return observed, ci, p + return observed, ci, p, distribution def permutation_isc(iscs, group_assignment=None, pairwise=False, summary_statistic='median', n_permutations=1000, - return_distribution=False, random_state=None): + random_state=None): """Group-level permutation test for ISCs @@ -511,7 +505,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, row/column order of the subject-by-subject square ISC matrix even though the input ISCs should be supplied as the vectorized upper triangle of the square ISC matrix. Returns the observed ISC and permutation-based p-value (two-tailed - test). Optionally returns the permutation distribution of summary statistics. + test), as well as the permutation distribution of summary statistic. According to Chen et al., 2016, this is the preferred nonparametric approach for controlling false positive rates (FPR) for two-sample tests. This approach may yield inflated FPRs for one-sample tests. @@ -539,10 +533,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, n_permutations : int, default:1000 Number of permutation iteration (randomizing group assignment) - - return_distribution : bool, default:False - Optionally return the bootstrap distribution of summary statistics - + random_state = int, None, or np.random.RandomState, default:None Initial random seed @@ -587,7 +578,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, elif type(group_assignment) == np.ndarray: group_assignment = group_assignment.tolist() else: - logger.warning("No group assignment provided, performing one-sample test.") + logger.info("No group assignment provided, performing one-sample test.") if group_assignment and len(group_assignment) != n_subjects: raise ValueError(f"Group assignments ({len(group_assignment)}) " @@ -790,14 +781,11 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Reshape p-values to fit with data shape p = p[np.newaxis, :] - if return_distribution: - return observed, p, distribution - elif not return_distribution: - return observed, p + return observed, p, distribution def timeshift_isc(data, pairwise=False, summary_statistic='median', - n_shifts=1000, return_distribution=False, random_state=None): + n_shifts=1000, random_state=None): """Circular time-shift randomization for one-sample ISC test @@ -811,9 +799,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', each item is a time-points by voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a single ndarray is supplied, the last dimension is assumed to correspond to subjects. - Returns the observed ISC and p-values (two-tailed test). Optionally - returns the null distribution of ISCs computed on randomly time- - shifted data. + Returns the observed ISC and p-values (two-tailed test), as well as + the null distribution of ISCs computed on randomly time-shifted data. This implementation is based on the following publications: @@ -840,9 +827,6 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', n_shifts : int, default:1000 Number of randomly shifted samples - return_distribution : bool, default:False - Optionally return the bootstrap distribution of summary statistics - random_state = int, None, or np.random.RandomState, default:None Initial random seed @@ -890,10 +874,6 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', # Roll axis to get subjects in first dimension for loop if pairwise: data = np.rollaxis(data, 2, 0) - - # Temporarily suppress ISC warning when constructing distribution - logging_level = logger.getEffectiveLevel() - logger.setLevel('CRITICAL') # Iterate through randomized shifts to create null distribution distribution = [] @@ -941,9 +921,6 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', distribution.append(shifted_isc) - # Re-enable logging at original level - logger.setLevel(logging_level) - # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) @@ -959,14 +936,11 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', # Reshape p-values to fit with data shape p = p[np.newaxis, :] - if return_distribution: - return observed, p, distribution - elif not return_distribution: - return observed, p + return observed, p, distribution def phaseshift_isc(data, pairwise=False, summary_statistic='median', - n_shifts=1000, return_distribution=False, random_state=None): + n_shifts=1000, random_state=None): """Phase randomization for one-sample ISC test @@ -980,9 +954,8 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', each item is a time-points by voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a single ndarray is supplied, the last dimension is assumed to correspond to subjects. - Returns the observed ISC and p-values (two-tailed test). Optionally - returns the null distribution of ISCs computed on phase-randomized - data. + Returns the observed ISC and p-values (two-tailed test), as well as + the null distribution of ISCs computed on phase-randomized data. This implementation is based on the following publications: @@ -1009,9 +982,6 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', n_shifts : int, default:1000 Number of randomly shifted samples - return_distribution : bool, default:False - Optionally return the bootstrap distribution of summary statistics - random_state = int, None, or np.random.RandomState, default:None Initial random seed @@ -1055,11 +1025,7 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Get actual observed ISC observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) - - # Temporarily suppress ISC warning when constructing distribution - logging_level = logger.getEffectiveLevel() - logger.setLevel('CRITICAL') - + # Iterate through randomized shifts to create null distribution distribution = [] for i in np.arange(n_shifts): @@ -1129,9 +1095,6 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', axis=2) distribution.append(shifted_isc) - # Re-enable logging at original level - logger.setLevel(logging_level) - # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) @@ -1147,7 +1110,4 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Reshape p-values to fit with data shape p = p[np.newaxis, :] - if return_distribution: - return observed, p, distribution - elif not return_distribution: - return observed, p \ No newline at end of file + return observed, p, distribution \ No newline at end of file diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index 91ef904e0..1dfb6e3ff 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -1,8 +1,11 @@ import numpy as np +import logging from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_ics, timeshift_isc, phaseshift_isc) from scipy.spatial.distance import squareform +logger = logging.getLogger(__name__) + # Create simple simulated data with high intersubject correlation def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, @@ -59,7 +62,7 @@ def test_isc_input(): n_voxels = 30 random_state = 42 - print("Testing ISC inputs") + logger.info("Testing ISC inputs") data = simulated_timeseries(n_subjects, n_TRs, n_voxels=None, data_type='list', @@ -90,7 +93,7 @@ def test_isc_input(): # Check they're the same assert np.array_equal(iscs_list, iscs_array) - print("Finished testing ISC inputs") + logger.info("Finished testing ISC inputs") # Check pairwise and leave-one-out, and summary statistics for ISC @@ -102,7 +105,7 @@ def test_isc_options(): n_voxels = 30 random_state = 42 - print("Testing ISC options") + logger.info("Testing ISC options") data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', @@ -124,15 +127,15 @@ def test_isc_options(): try: isc_min = isc(data, pairwise=False, summary_statistic='min') except ValueError: - print("Correctly caught unexpected summary statistic") + logger.info("Correctly caught unexpected summary statistic") - print("Finished testing ISC options") + logger.info("Finished testing ISC options") # Make sure ISC recovers correlations of 1 and less than 1 def test_isc_output(): - print("Testing ISC outputs") + logger.info("Testing ISC outputs") data = correlated_timeseries(20, 60, noise=0, random_state=42) @@ -144,7 +147,7 @@ def test_isc_output(): assert np.all(iscs[:, :2] == 1.) assert np.all(iscs[:, -1] < 1.) - print("Finished testing ISC outputs") + logger.info("Finished testing ISC outputs") # Test one-sample bootstrap test @@ -157,7 +160,7 @@ def test_bootstrap_isc(): random_state = 42 n_bootstraps = 10 - print("Testing bootstrap hypothesis test") + logger.info("Testing bootstrap hypothesis test") data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', @@ -167,8 +170,7 @@ def test_bootstrap_isc(): observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, summary_statistic='median', n_bootstraps=n_bootstraps, - ci_percentile=95, - return_distribution=True) + ci_percentile=95) assert distribution.shape == (n_bootstraps, n_voxels) # Test one-sample bootstrap test with pairwise approach @@ -178,8 +180,7 @@ def test_bootstrap_isc(): observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, summary_statistic='median', n_bootstraps=n_bootstraps, - ci_percentile=95, - return_distribution=True) + ci_percentile=95) assert distribution.shape == (n_bootstraps, n_voxels) # Check random seeds @@ -190,7 +191,6 @@ def test_bootstrap_isc(): summary_statistic='median', n_bootstraps=n_bootstraps, ci_percentile=95, - return_distribution=True, random_state=random_state) distributions.append(distribution) assert np.array_equal(distributions[0], distributions[1]) @@ -200,14 +200,14 @@ def test_bootstrap_isc(): data = correlated_timeseries(20, 60, noise=.5, random_state=42) iscs = isc(data, pairwise=False) - observed, ci, p = bootstrap_isc(iscs, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 iscs = isc(data, pairwise=True) - observed, ci, p = bootstrap_isc(iscs, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 @@ -215,15 +215,19 @@ def test_bootstrap_isc(): # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=False) - observed, ci, p = bootstrap_isc(iscs, pairwise=False, summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=True) - observed, ci, p = bootstrap_isc(iscs, pairwise=True, summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='median')) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='median')) - print("Finished testing bootstrap hypothesis test") + logger.info("Finished testing bootstrap hypothesis test") # Test permutation test with group assignments @@ -236,7 +240,7 @@ def test_permutation_isc(): random_state = 42 group_assignment = [1] * 10 + [2] * 10 - print("Testing permutation test") + logger.info("Testing permutation test") # Create dataset with two groups in pairwise approach data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, @@ -247,11 +251,11 @@ def test_permutation_isc(): random_state=4))) iscs = isc(data, pairwise=True, summary_statistic=None) - observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, pairwise=True, summary_statistic='mean', - n_permutations=200, - return_distribution=True) + n_permutations=200) # Create data with two groups in leave-one-out approach data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, @@ -263,11 +267,11 @@ def test_permutation_isc(): iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), isc(data_2, pairwise=False, summary_statistic=None))) - observed, p, distribution = permutation_isc(iscs, group_assignment=group_assignment, + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, pairwise=False, summary_statistic='mean', - n_permutations=200, - return_distribution=True) + n_permutations=200) # One-sample leave-one-out permutation test data = simulated_timeseries(n_subjects, n_TRs, @@ -278,8 +282,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, pairwise=False, summary_statistic='median', - n_permutations=200, - return_distribution=True) + n_permutations=200) # One-sample pairwise permutation test iscs = isc(data, pairwise=True, summary_statistic=None) @@ -287,8 +290,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, pairwise=True, summary_statistic='median', - n_permutations=200, - return_distribution=True) + n_permutations=200) # Small one-sample pairwise exact test data = simulated_timeseries(12, n_TRs, @@ -299,8 +301,7 @@ def test_permutation_isc(): observed, p, distribution = permutation_isc(iscs, pairwise=False, summary_statistic='median', - n_permutations=10000, - return_distribution=True) + n_permutations=10000) # Small two-sample pairwise exact test (and unequal groups) data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, @@ -316,8 +317,7 @@ def test_permutation_isc(): group_assignment=group_assignment, pairwise=True, summary_statistic='mean', - n_permutations=10000, - return_distribution=True) + n_permutations=10000) # Small two-sample leave-one-out exact test (and unequal groups) data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, @@ -334,21 +334,20 @@ def test_permutation_isc(): group_assignment=group_assignment, pairwise=False, summary_statistic='mean', - n_permutations=10000, - return_distribution=True) + n_permutations=10000) # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) iscs = isc(data, pairwise=False) - observed, p = permutation_isc(iscs, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 iscs = isc(data, pairwise=True) - observed, p = permutation_isc(iscs, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 @@ -356,15 +355,19 @@ def test_permutation_isc(): # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) - observed, p = permutation_isc(iscs, pairwise=False, summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) - observed, p = permutation_isc(iscs, pairwise=True, summary_statistic='mean') - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) + observed, p, distribution = permutation_isc(iscs, pairwise=True, + summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='mean')) - print("Finished testing permutaton test") + logger.info("Finished testing permutaton test") def test_timeshift_isc(): @@ -375,43 +378,40 @@ def test_timeshift_isc(): n_voxels = 30 random_state = 42 - print("Testing circular time-shift") + logger.info("Testing circular time-shift") # Circular time-shift on one sample, leave-one-out data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=False, summary_statistic='median', - n_shifts=200, - return_distribution=True) + n_shifts=200) # Circular time-shift on one sample, pairwise data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=True, summary_statistic='median', - n_shifts=200, - return_distribution=True) + n_shifts=200) # Circular time-shift on one sample, leave-one-out data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = timeshift_isc(data, pairwise=False, summary_statistic='mean', - n_shifts=200, - return_distribution=True) + n_shifts=200) # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) iscs = isc(data, pairwise=False) - observed, p = timeshift_isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 iscs = isc(data, pairwise=True) - observed, p = timeshift_isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 @@ -419,15 +419,19 @@ def test_timeshift_isc(): # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) - observed, p = timeshift_isc(data, pairwise=False, summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) - observed, p = timeshift_isc(data, pairwise=True, summary_statistic='mean') - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='mean')) - print("Finished testing circular time-shift") + logger.info("Finished testing circular time-shift") # Phase randomization test @@ -439,35 +443,33 @@ def test_phaseshift_isc(): n_voxels = 30 random_state = 42 - print("Testing phase randomization") + logger.info("Testing phase randomization") data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = phaseshift_isc(data, pairwise=True, summary_statistic='median', - n_shifts=200, - return_distribution=True) + n_shifts=200) # Phase randomization one-sample test, leave-one-out data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') observed, p, distribution = phaseshift_isc(data, pairwise=False, summary_statistic='mean', - n_shifts=200, - return_distribution=True) + n_shifts=200) # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) iscs = isc(data, pairwise=False) - observed, p = phaseshift_isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 iscs = isc(data, pairwise=True) - observed, p = phaseshift_isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 @@ -475,15 +477,19 @@ def test_phaseshift_isc(): # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) - observed, p = phaseshift_isc(data, pairwise=False, summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) - observed, p = phaseshift_isc(data, pairwise=True, summary_statistic='mean') - assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='mean')) - print("Finished testing phase randomization") + logger.info("Finished testing phase randomization") # Test ISFC @@ -495,7 +501,7 @@ def test_isfc_options(): n_voxels = 30 random_state = 42 - print("Testing ISFC options") + logger.info("Testing ISFC options") from brainiak.fcma.util import compute_correlation data = simulated_timeseries(n_subjects, n_TRs, @@ -535,7 +541,7 @@ def test_isfc_options(): for s in np.arange(len(iscs)): assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - print("Finished testing ISFC options") + logger.info("Finished testing ISFC options") if __name__ == '__main__': @@ -547,4 +553,4 @@ def test_isfc_options(): test_timeshift_isc() test_phaseshift_isc() test_isfc_options() - print("Finished all ISC tests") + logger.info("Finished all ISC tests") From 56e0e2e1cbd07695658de9e6ffc2553d3dd910ca Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Fri, 9 Nov 2018 14:31:46 -0500 Subject: [PATCH 24/43] Simplified template matrix for two-sample pairwise permutation test --- brainiak/isfc.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 60b2eff3f..3a1529c81 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -600,19 +600,21 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, sorter = np.array(group_assignment).argsort() unsorter = np.array(group_assignment).argsort().argsort() - # Populate a matrix with group assignments - group_matrix = np.vstack((np.hstack((np.full((groups[group_labels[0]], - groups[group_labels[0]]), - group_labels[0]), - np.full((groups[group_labels[0]], - groups[group_labels[1]]), - np.nan))), - np.hstack((np.full((groups[group_labels[1]], - groups[group_labels[0]]), - np.nan), - np.full((groups[group_labels[1]], - groups[group_labels[1]]), - group_labels[1]))))) + # Populate a matrix with group assignments + upper_left = np.full((groups[group_labels[0]], + groups[group_labels[0]]), + group_labels[0]) + upper_right = np.full((groups[group_labels[0]], + groups[group_labels[1]]), + np.nan) + lower_left = np.full((groups[group_labels[1]], + groups[group_labels[0]]), + np.nan) + lower_right = np.full((groups[group_labels[1]], + groups[group_labels[1]]), + group_labels[1]) + group_matrix = np.vstack((np.hstack((upper_left, upper_right)), + np.hstack((lower_left, lower_right)))) np.fill_diagonal(group_matrix, np.nan) # Unsort matrix and squareform to create selector From 35c3e726b89faa04df884622ac8d020c693889f7 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Fri, 9 Nov 2018 16:23:35 -0500 Subject: [PATCH 25/43] Added MAX_RANDOM_SEED constant --- brainiak/isfc.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 3a1529c81..7025d88e6 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -30,10 +30,12 @@ from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation -#from brainiak.utils.utils import compute_p_from_null_distribution +from brainiak.utils.utils import compute_p_from_null_distribution logger = logging.getLogger(__name__) +MAX_RANDOM_SEED = 2**32 - 1 + def isc(data, pairwise=False, summary_statistic=None): """Intersubject correlation @@ -456,7 +458,7 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', axis=0)) # Update random state for next iteration - random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) # Convert distribution to numpy array distribution = np.array(distribution) @@ -764,7 +766,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Update random state for next iteration if not exact_permutations: - random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) # Convert distribution to numpy array distribution = np.array(distribution) @@ -924,7 +926,7 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', distribution.append(shifted_isc) # Update random state for next iteration - random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) # Convert distribution to numpy array distribution = np.vstack(distribution) @@ -1098,7 +1100,7 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', distribution.append(shifted_isc) # Update random state for next iteration - random_state = np.random.RandomState(prng.randint(0, 2**32 - 1)) + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) # Convert distribution to numpy array distribution = np.vstack(distribution) From d05338d115e022f53595c6fecc28010fc9d95964 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sun, 11 Nov 2018 16:31:15 -0500 Subject: [PATCH 26/43] MaskedMultiSubjectData shape now n_TRs, n_voxels, n_subjects --- brainiak/image.py | 33 ++++++++++++++++++--------------- brainiak/isfc.py | 6 ++++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/brainiak/image.py b/brainiak/image.py index dd373e2e3..f86261d13 100644 --- a/brainiak/image.py +++ b/brainiak/image.py @@ -34,46 +34,49 @@ class MaskedMultiSubjectData(np.ndarray): - """Array in shape n_voxels, n_trs, n_subjects.""" + """Array with shape n_TRs, n_voxels, n_subjects.""" @classmethod def from_masked_images(cls: Type[T], masked_images: Iterable[np.ndarray], - n_sub: int) -> T: - """Create a new instance from masked images. + n_subjects: int) -> T: + """Create a new instance of MaskedMultiSubjecData from masked images. Parameters ---------- - masked_images - Images to concatenate. - n_sub - Number of subjects. Must match the number of images. + masked_images : iterator + Images from multiple subjects to stack along 3rd dimension + n_subjects : int + Number of subjects; must match the number of images Returns ------- T - A new instance. + A new instance of MaskedMultiSubjectData Raises ------ ValueError Images have different shapes. - The number of images differs from n_sub. + The number of images differs from n_subjects. """ images_iterator = iter(masked_images) first_image = next(images_iterator) - result = np.empty((first_image.shape[0], first_image.shape[1], n_sub)) + first_image_shape = first_image.T.shape + result = np.empty((first_image_shape[0], first_image_shape[1], + n_subjects)) for n_images, image in enumerate(itertools.chain([first_image], images_iterator)): - if image.shape != first_image.shape: + image = image.T + if image.shape != first_image_shape: raise ValueError("Image {} has different shape from first " "image: {} != {}".format(n_images, image.shape, - first_image.shape)) + first_image_shape)) result[:, :, n_images] = image n_images += 1 - if n_images != n_sub: - raise ValueError("n_sub != number of images: {} != {}" - .format(n_sub, n_images)) + if n_images != n_subjects: + raise ValueError("n_subjects != number of images: {} != {}" + .format(n_subjects, n_images)) return result.view(cls) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 7025d88e6..9446c84ed 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -110,14 +110,16 @@ def isc(data, pairwise=False, summary_statistic=None): logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " f"and {n_voxels} voxel(s) or ROI(s) for ISC analysis.") + if n_subjects == 2: + logger.info("Only two subjects! Simply computing Pearson correlation.") + summary_statistic = None + # Loop over each voxel or ROI voxel_iscs = [] for v in np.arange(n_voxels): voxel_data = data[:, v, :].T if n_subjects == 2: iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] - summary_statistic = None - logger.info("Only two subjects! Simply computing Pearson correlation.") elif pairwise: iscs = squareform(np.corrcoef(voxel_data), checks=False) elif not pairwise: From 180edcad691a1739971bbe5f54bcbd1cbde4afff Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sun, 11 Nov 2018 17:42:54 -0500 Subject: [PATCH 27/43] Separate mini-functions for checking ISC input assumptions --- brainiak/isfc.py | 336 ++++++++++++++++++++++------------------------- 1 file changed, 158 insertions(+), 178 deletions(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 9446c84ed..e611b9aa6 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -30,7 +30,7 @@ from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation -from brainiak.utils.utils import compute_p_from_null_distribution +#from brainiak.utils.utils import compute_p_from_null_distribution logger = logging.getLogger(__name__) @@ -50,15 +50,15 @@ def isc(data, pairwise=False, summary_statistic=None): corresponding to the upper triangle of the pairwise correlation matrix (see scipy.spatial.distance.squareform). Alternatively, use either 'mean' or 'median' to compute summary statistic of ISCs (Fisher Z will - be applied and inverted if using mean). Input data should be a list - where each item is a time-points by voxels ndarray for a given subject. - Multiple input ndarrays must be the same shape. If a single ndarray is - supplied, the last dimension is assumed to correspond to subjects. If - only two subjects are supplied, simply compute Pearson correlation - (precludes averaging in leave-one-out approach, and does not apply - summary statistic.) Output is an ndarray where the first dimension is - the number of subjects or pairs and the second dimension is the number - of voxels (or ROIs). + be applied if using mean). Input data should be a n_TRs by n_voxels by + n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) or a list + where each item is a n_TRs by n_voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a 2D array is supplied, + the last dimension is assumed to correspond to subjects. If only two + subjects are supplied, simply compute Pearson correlation (precludes + averaging in leave-one-out approach, and does not apply summary statistic). + Output is an ndarray where the first dimension is the number of subjects + or pairs and the second dimension is the number of voxels (or ROIs). The implementation is based on the following publication: @@ -83,33 +83,11 @@ def isc(data, pairwise=False, summary_statistic=None): ISC for each subject or pair (or summary statistic) per voxel """ - - # Convert list input to 3d and check shapes - if type(data) == list: - data_shape = data[0].shape - for i, d in enumerate(data): - if d.shape != data_shape: - raise ValueError("All ndarrays in input list " - "must be the same shape!") - if d.ndim == 1: - data[i] = d[:, np.newaxis] - data = np.dstack(data) - # Convert input ndarray to 3d and check shape - elif type(data) == np.ndarray: - if data.ndim == 2: - data = data[:, np.newaxis, :] - elif data.ndim == 3: - pass - else: - raise ValueError("Input ndarray should have 2 " - f"or 3 dimensions (got {data.ndim})!") - - # Infer subjects, TRs, voxels and log for user to check - n_TRs, n_voxels, n_subjects = data.shape - logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " - f"and {n_voxels} voxel(s) or ROI(s) for ISC analysis.") + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) + # No summary statistic if only two subjects if n_subjects == 2: logger.info("Only two subjects! Simply computing Pearson correlation.") summary_statistic = None @@ -141,26 +119,28 @@ def isc(data, pairwise=False, summary_statistic=None): def isfc(data, pairwise=False, summary_statistic=None): - """Intersubject correlation + """Intersubject functional correlation (ISFC) For each voxel or ROI, compute the Pearson correlation between each - subject's response time series and other subjects' response time series. - If pairwise is False (default), use the leave-one-out approach, where - correlation is computed between each subject and the average of the other - subjects. If pairwise is True, compute correlations between all pairs of - subjects. If summary_statistic is None, return N ISC values for N subjects - (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, - corresponding to the upper triangle of the pairwise correlation matrix - (see scipy.spatial.distance.squareform). Alternatively, use either - 'mean' or 'median' to compute summary statistic of ISCs (Fisher Z will - be applied and inverted if using mean). Input data should be a list - where each item is a time-points by voxels ndarray for a given subject. - Multiple input ndarrays must be the same shape. If a single ndarray is - supplied, the last dimension is assumed to correspond to subjects. If - only two subjects are supplied, simply ISFC between these two subjects - (precludes averaging in leave-one-out approach, and does not apply - summary statistic.) Output is an voxels by voxels by subjects (or pairs) - ndarray. + subject's response time series and other subjects' response time series + for all voxels or ROIs. If pairwise is False (default), use the + leave-one-out approach, where correlation is computed between each + subject and the average of the other subjects. If pairwise is True, + compute correlations between all pairs of subjects. If summary_statistic + is None, return N ISFC values for N subjects (leave-one-out) or N(N-1)/2 + ISFC values for each pair of N subjects, corresponding to the upper + triangle of the correlation matrix (see scipy.spatial.distance.squareform). + Alternatively, use either 'mean' or 'median' to compute summary statistic + of ISFCs (Fisher Z will be applied if using mean). Input should be n_TRs + by n_voxels by n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) + or a list where each item is a n_TRs by n_voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a 2D array is supplied, + the last dimension is assumed to correspond to subjects. If only two + subjects are supplied, simply compute ISFC between these two subjects + (precludes averaging in leave-one-out approach, and does not apply summary + statistic). Output is n_voxels by n_voxels array if summary_statistic is + supplied; otherwise output is n_voxels by n_voxels by n_subjects (or + n_pairs) array. The implementation is based on the following publication: @@ -187,32 +167,9 @@ def isfc(data, pairwise=False, summary_statistic=None): """ - # Convert list input to 3d and check shapes - if type(data) == list: - data_shape = data[0].shape - for i, d in enumerate(data): - if d.shape != data_shape: - raise ValueError("All ndarrays in input list " - "must be the same shape!") - if d.ndim == 1: - data[i] = d[:, np.newaxis] - data = np.dstack(data) - - # Convert input ndarray to 3d and check shape - elif type(data) == np.ndarray: - if data.ndim == 2: - data = data[:, np.newaxis, :] - elif data.ndim == 3: - pass - else: - raise ValueError("Input ndarray should have 2 " - f"or 3 dimensions (got {data.ndim})!") - - # Infer subjects, TRs, voxels and print for user to check - n_TRs, n_voxels, n_subjects = data.shape - logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " - f"and {n_voxels} voxel(s) or ROI(s) for ISFC analysis.") - + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) + # Handle just two subjects properly if n_subjects == 2: isfcs = compute_correlation(np.ascontiguousarray(data[..., 0].T), @@ -260,6 +217,118 @@ def isfc(data, pairwise=False, summary_statistic=None): return isfcs +def check_timeseries_input(data): + + """Checks response time series input data for ISC analysis + + Input data should be a n_TRs by n_voxels by n_subjects ndarray + (e.g., brainiak.image.MaskedMultiSubjectData) or a list where each + item is a n_TRs by n_voxels ndarray for a given subject. Multiple + input ndarrays must be the same shape. If a 2D array is supplied, + the last dimension is assumed to correspond to subjects. + + Parameters + ---------- + data : ndarray or list + Time series data + + Returns + ------- + iscs : ndarray + Array of ISC values + + n_TRs : int + Number of time points (TRs) + + n_voxels : int + Number of voxels (or ROIs) + + n_subjects : int + Number of subjects + + """ + + # Convert list input to 3d and check shapes + if type(data) == list: + data_shape = data[0].shape + for i, d in enumerate(data): + if d.shape != data_shape: + raise ValueError("All ndarrays in input list " + "must be the same shape!") + if d.ndim == 1: + data[i] = d[:, np.newaxis] + data = np.dstack(data) + + # Convert input ndarray to 3d and check shape + elif type(data) == np.ndarray: + if data.ndim == 2: + data = data[:, np.newaxis, :] + elif data.ndim == 3: + pass + else: + raise ValueError("Input ndarray should have 2 " + f"or 3 dimensions (got {data.ndim})!") + + # Infer subjects, TRs, voxels and log for user to check + n_TRs, n_voxels, n_subjects = data.shape + logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " + f"and {n_voxels} voxel(s) or ROI(s) for ISC analysis.") + + return data, n_TRs, n_voxels, n_subjects + + +def check_isc_input(iscs, pairwise=False): + + """Checks ISC inputs for statistical tests + + Input ISCs should be n_subjects (leave-one-out approach) or + n_pairs (pairwise approach) by n_voxels or n_ROIs array or a 1D + array (or list) of ISC values for a single voxel or ROI. + + Parameters + ---------- + iscs : ndarray or list + ISC values + + Returns + ------- + iscs : ndarray + Array of ISC values + + n_subjects : int + Number of subjects + + n_voxels : int + Number of voxels (or ROIs) + """ + + # Standardize structure of input data + if type(iscs) == list: + iscs = np.array(iscs)[:, np.newaxis] + + elif type(iscs) == np.ndarray: + if iscs.ndim == 1: + iscs = iscs[:, np.newaxis] + + # Check if incoming pairwise matrix is vectorized triangle + if pairwise: + try: + test_square = squareform(iscs[:, 0]) + n_subjects = test_square.shape[0] + except ValueError: + raise ValueError("For pairwise input, ISCs must be the " + "vectorized triangle of a square matrix.") + elif not pairwise: + n_subjects = iscs.shape[0] + + # Infer subjects, voxels and print for user to check + n_voxels = iscs.shape[1] + logger.info(f"Assuming {n_subjects} subjects with and {n_voxels} " + "voxel(s) or ROI(s) in bootstrap ISC test.") + + return iscs, n_subjects, n_voxels + + def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): """Computes summary statistics for ISCs @@ -373,33 +442,11 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', """ # Standardize structure of input data - if type(iscs) == list: - iscs = np.array(iscs)[:, np.newaxis] - - elif type(iscs) == np.ndarray: - if iscs.ndim == 1: - iscs = iscs[:, np.newaxis] - - # Check if incoming pairwise matrix is vectorized triangle - if pairwise: - try: - test_square = squareform(iscs[:, 0]) - n_subjects = test_square.shape[0] - except ValueError: - raise ValueError("For pairwise input, ISCs must be the " - "vectorized triangle of a square matrix.") - elif not pairwise: - n_subjects = iscs.shape[0] - - if n_subjects < 2: - raise ValueError("Input data seems to contain only one subject! " - "Needs two or more subjects. Check that input is " - "not summary statistic.") + iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) - # Infer subjects, voxels and print for user to check - n_voxels = iscs.shape[1] - logger.info(f"Assuming {n_subjects} subjects with and {n_voxels} " - "voxel(s) or ROI(s) in bootstrap ISC test.") + # Check for valid summary statistic + if summary_statistic not in ('mean', 'median'): + raise ValueError("Summary statistic must be 'mean' or 'median'") # Compute summary statistic for observed ISCs observed = compute_summary_statistic(iscs, summary_statistic=summary_statistic, @@ -554,28 +601,12 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, """ # Standardize structure of input data - if type(iscs) == list: - iscs = np.array(iscs)[:, np.newaxis] - - elif type(iscs) == np.ndarray: - if iscs.ndim == 1: - iscs = iscs[:, np.newaxis] - + iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) + # Check for valid summary statistic if summary_statistic not in ('mean', 'median'): raise ValueError("Summary statistic must be 'mean' or 'median'") - # Check if incoming pairwise matrix is vectorized triangle - if pairwise: - try: - test_square = squareform(iscs[:, 0]) - n_subjects = test_square.shape[0] - except ValueError: - raise ValueError("For pairwise input, ISCs must be the " - "vectorized triangle of a square matrix.") - elif not pairwise: - n_subjects = iscs.shape[0] - # Check match between group labels and ISCs if type(group_assignment) == list: pass @@ -642,11 +673,6 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, else: raise ValueError("Invalid group assignments!") - # Infer subjects, groups, voxels and print for user to check - n_voxels = iscs.shape[1] - logging.info(f"Assuming {n_subjects} subjects, {n_groups} group(s), " - f"and {n_voxels} voxel(s) or ROI(s) for permutation ISC test.") - # Set up permutation type (exact or Monte Carlo) if n_groups == 1: if n_permutations < 2**n_subjects: @@ -848,31 +874,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', Time-shifted null distribution if return_bootstrap=True """ - # Convert list input to 3d and check shapes - if type(data) == list: - data_shape = data[0].shape - for i, d in enumerate(data): - if d.shape != data_shape: - raise ValueError("All ndarrays in input list " - "must be the same shape!") - if d.ndim == 1: - data[i] = d[:, np.newaxis] - data = np.dstack(data) - - # Convert input ndarray to 3d and check shape - elif type(data) == np.ndarray: - if data.ndim == 2: - data = data[:, np.newaxis, :] - elif data.ndim == 3: - pass - else: - raise ValueError("Input ndarray should have 2 " - f"or 3 dimensions (got {data.ndim})!") - - # Infer subjects, TRs, voxels and print for user to check - n_subjects = data.shape[2] - n_TRs = data.shape[0] - n_voxels = data.shape[1] + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) # Get actual observed ISC observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) @@ -1003,31 +1006,8 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', Time-shifted null distribution if return_bootstrap=True """ - # Convert list input to 3d and check shapes - if type(data) == list: - data_shape = data[0].shape - for i, d in enumerate(data): - if d.shape != data_shape: - raise ValueError("All ndarrays in input list " - "must be the same shape!") - if d.ndim == 1: - data[i] = d[:, np.newaxis] - data = np.dstack(data) - - # Convert input ndarray to 3d and check shape - elif type(data) == np.ndarray: - if data.ndim == 2: - data = data[:, np.newaxis, :] - elif data.ndim == 3: - pass - else: - raise ValueError("Input ndarray should have 2 " - f"or 3 dimensions (got {data.ndim})!") - - # Infer subjects, TRs, voxels and print for user to check - n_subjects = data.shape[2] - n_TRs = data.shape[0] - n_voxels = data.shape[1] + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) # Get actual observed ISC observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) From 5c86ec0ca993d27f0cffc264143100bc9c0415d7 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Sun, 11 Nov 2018 17:57:46 -0500 Subject: [PATCH 28/43] Added Q as co-author on ISC package --- brainiak/isfc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index e611b9aa6..012ab4f74 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -20,7 +20,8 @@ """ -# Authors: Sam Nastase, Christopher Baldassano, Mai Nguyen, and Mor Regev +# Authors: Sam Nastase, Christopher Baldassano, Qihong Lu, +# Mai Nguyen, and Mor Regev # Princeton University, 2018 import numpy as np From 3d6d7f876443b8597bfec1ce30bb4871eea8da5a Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 19 Nov 2018 13:02:59 -0500 Subject: [PATCH 29/43] Fixed whitespace errors and too-long lines for Travis --- brainiak/image.py | 1 + brainiak/isfc.py | 520 +++++++++++++++++++++------------------- brainiak/utils/utils.py | 29 +-- 3 files changed, 288 insertions(+), 262 deletions(-) diff --git a/brainiak/image.py b/brainiak/image.py index f86261d13..75482d996 100644 --- a/brainiak/image.py +++ b/brainiak/image.py @@ -179,3 +179,4 @@ def mask_images(images: Iterable[SpatialImage], mask: np.ndarray, """ for images in multimask_images(images, (mask,), image_type): yield images[0] + diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 012ab4f74..706a44c21 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -31,7 +31,7 @@ from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation -#from brainiak.utils.utils import compute_p_from_null_distribution +from brainiak.utils.utils import compute_p_from_null_distribution logger = logging.getLogger(__name__) @@ -60,10 +60,10 @@ def isc(data, pairwise=False, summary_statistic=None): averaging in leave-one-out approach, and does not apply summary statistic). Output is an ndarray where the first dimension is the number of subjects or pairs and the second dimension is the number of voxels (or ROIs). - + The implementation is based on the following publication: - - .. [Hasson2004] "Intersubject synchronization of cortical activity + + .. [Hasson2004] "Intersubject synchronization of cortical activity during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, R. Malach, 2004, Science, 303, 1634-1640. @@ -71,10 +71,10 @@ def isc(data, pairwise=False, summary_statistic=None): ---------- data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISC - + pairwise : bool, default:False Whether to use pairwise (True) or leave-one-out (False) approach - + summary_statistic : None or str, default:None Return all ISCs or collapse using 'mean' or 'median' @@ -87,12 +87,12 @@ def isc(data, pairwise=False, summary_statistic=None): # Check response time series input format data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - - # No summary statistic if only two subjects + + # No summary statistic if only two subjects if n_subjects == 2: logger.info("Only two subjects! Simply computing Pearson correlation.") summary_statistic = None - + # Loop over each voxel or ROI voxel_iscs = [] for v in np.arange(n_voxels): @@ -106,20 +106,21 @@ def isc(data, pairwise=False, summary_statistic=None): np.mean(np.delete(voxel_data, s, axis=0), axis=0))[0] - for s, subject in enumerate(voxel_data)]) + for s, subject in enumerate(voxel_data)]) voxel_iscs.append(iscs) iscs = np.column_stack(voxel_iscs) - + # Summarize results (if requested) if summary_statistic: - iscs = compute_summary_statistic(iscs, summary_statistic=summary_statistic, + iscs = compute_summary_statistic(iscs, + summary_statistic=summary_statistic, axis=0)[np.newaxis, :] - + return iscs def isfc(data, pairwise=False, summary_statistic=None): - + """Intersubject functional correlation (ISFC) For each voxel or ROI, compute the Pearson correlation between each @@ -132,9 +133,9 @@ def isfc(data, pairwise=False, summary_statistic=None): ISFC values for each pair of N subjects, corresponding to the upper triangle of the correlation matrix (see scipy.spatial.distance.squareform). Alternatively, use either 'mean' or 'median' to compute summary statistic - of ISFCs (Fisher Z will be applied if using mean). Input should be n_TRs - by n_voxels by n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) - or a list where each item is a n_TRs by n_voxels ndarray for a given subject. + of ISFCs (Fisher Z is applied if using mean). Input should be n_TRs by + n_voxels by n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) + or a list where each item is a n_TRs by n_voxels ndarray per subject. Multiple input ndarrays must be the same shape. If a 2D array is supplied, the last dimension is assumed to correspond to subjects. If only two subjects are supplied, simply compute ISFC between these two subjects @@ -142,9 +143,9 @@ def isfc(data, pairwise=False, summary_statistic=None): statistic). Output is n_voxels by n_voxels array if summary_statistic is supplied; otherwise output is n_voxels by n_voxels by n_subjects (or n_pairs) array. - + The implementation is based on the following publication: - + .. [Simony2016] "Dynamic reconfiguration of the default mode network during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, @@ -154,10 +155,10 @@ def isfc(data, pairwise=False, summary_statistic=None): ---------- data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISFC - + pairwise : bool, default: False Whether to use pairwise (True) or leave-one-out (False) approach - + summary_statistic : None or str, default:None Return all ISFCs or collapse using 'mean' or 'median' @@ -167,10 +168,10 @@ def isfc(data, pairwise=False, summary_statistic=None): ISFC for each subject or pair (or summary statistic) per voxel """ - + # Check response time series input format data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - + # Handle just two subjects properly if n_subjects == 2: isfcs = compute_correlation(np.ascontiguousarray(data[..., 0].T), @@ -179,55 +180,58 @@ def isfc(data, pairwise=False, summary_statistic=None): assert isfcs.shape == (n_voxels, n_voxels) summary_statistic = None logger.info("Only two subjects! Computing ISFC between them.") - - # Compute all pairwise ISFCs + + # Compute all pairwise ISFCs elif pairwise: isfcs = [] for pair in it.combinations(np.arange(n_subjects), 2): - isfc_pair = compute_correlation(np.ascontiguousarray(data[..., pair[0]].T), - np.ascontiguousarray(data[..., pair[1]].T)) + isfc_pair = compute_correlation(np.ascontiguousarray( + data[..., pair[0]].T), + np.ascontiguousarray( + data[..., pair[1]].T)) isfc_pair = (isfc_pair + isfc_pair.T) / 2 isfcs.append(isfc_pair) isfcs = np.dstack(isfcs) assert isfcs.shape == (n_voxels, n_voxels, n_subjects * (n_subjects - 1) / 2) - + # Compute ISFCs using leave-one-out approach elif not pairwise: - + # Roll subject axis for loop data = np.rollaxis(data, 2, 0) - + # Compute leave-one-out ISFCs isfcs = [compute_correlation(np.ascontiguousarray(subject.T), np.ascontiguousarray(np.mean( np.delete(data, s, axis=0), - axis=0).T)) + axis=0).T)) for s, subject in enumerate(data)] - + # Transpose and average ISFC matrices for both directions isfcs = np.dstack([(isfc_matrix + isfc_matrix.T) / 2 for isfc_matrix in isfcs]) assert isfcs.shape == (n_voxels, n_voxels, n_subjects) - + # Summarize results (if requested) if summary_statistic: - isfcs = compute_summary_statistic(isfcs, summary_statistic=summary_statistic, + isfcs = compute_summary_statistic(isfcs, + summary_statistic=summary_statistic, axis=2) return isfcs def check_timeseries_input(data): - + """Checks response time series input data for ISC analysis - + Input data should be a n_TRs by n_voxels by n_subjects ndarray (e.g., brainiak.image.MaskedMultiSubjectData) or a list where each item is a n_TRs by n_voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a 2D array is supplied, the last dimension is assumed to correspond to subjects. - + Parameters ---------- data : ndarray or list @@ -237,18 +241,18 @@ def check_timeseries_input(data): ------- iscs : ndarray Array of ISC values - + n_TRs : int Number of time points (TRs) - + n_voxels : int Number of voxels (or ROIs) - + n_subjects : int Number of subjects """ - + # Convert list input to 3d and check shapes if type(data) == list: data_shape = data[0].shape @@ -263,29 +267,29 @@ def check_timeseries_input(data): # Convert input ndarray to 3d and check shape elif type(data) == np.ndarray: if data.ndim == 2: - data = data[:, np.newaxis, :] + data = data[:, np.newaxis, :] elif data.ndim == 3: pass else: raise ValueError("Input ndarray should have 2 " f"or 3 dimensions (got {data.ndim})!") - + # Infer subjects, TRs, voxels and log for user to check n_TRs, n_voxels, n_subjects = data.shape logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " f"and {n_voxels} voxel(s) or ROI(s) for ISC analysis.") - + return data, n_TRs, n_voxels, n_subjects def check_isc_input(iscs, pairwise=False): - + """Checks ISC inputs for statistical tests - + Input ISCs should be n_subjects (leave-one-out approach) or n_pairs (pairwise approach) by n_voxels or n_ROIs array or a 1D array (or list) of ISC values for a single voxel or ROI. - + Parameters ---------- iscs : ndarray or list @@ -295,22 +299,22 @@ def check_isc_input(iscs, pairwise=False): ------- iscs : ndarray Array of ISC values - + n_subjects : int Number of subjects - + n_voxels : int Number of voxels (or ROIs) """ - + # Standardize structure of input data if type(iscs) == list: iscs = np.array(iscs)[:, np.newaxis] - + elif type(iscs) == np.ndarray: if iscs.ndim == 1: iscs = iscs[:, np.newaxis] - + # Check if incoming pairwise matrix is vectorized triangle if pairwise: try: @@ -321,37 +325,37 @@ def check_isc_input(iscs, pairwise=False): "vectorized triangle of a square matrix.") elif not pairwise: n_subjects = iscs.shape[0] - + # Infer subjects, voxels and print for user to check n_voxels = iscs.shape[1] logger.info(f"Assuming {n_subjects} subjects with and {n_voxels} " "voxel(s) or ROI(s) in bootstrap ISC test.") - + return iscs, n_subjects, n_voxels def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): - + """Computes summary statistics for ISCs - + Computes either the 'mean' or 'median' across a set of ISCs. In the case of the mean, ISC values are first Fisher Z transformed (arctanh), averaged, then inverse Fisher Z transformed (tanh). - + The implementation is based on the following publication: - + .. [SilverDunlap1987] "Averaging corrlelation coefficients: should Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, Journal of Applied Psychology, 72, 146-148. - + Parameters ---------- iscs : list or ndarray ISC values - + summary_statistic : str, default:'mean' Summary statistic, 'mean' or 'median' - + axis : None or int or tuple of ints, optional Axis or axes along which the means are computed. The default is to compute the mean of the flattened array. @@ -360,24 +364,24 @@ def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): ------- statistic : float or ndarray Summary statistic of ISC values - + """ - + if summary_statistic not in ('mean', 'median'): raise ValueError("Summary statistic must be 'mean' or 'median'") - + # Compute summary statistic if summary_statistic == 'mean': statistic = np.tanh(np.nanmean(np.arctanh(iscs), axis=axis)) elif summary_statistic == 'median': statistic = np.nanmedian(iscs, axis=axis) - + return statistic - + def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', n_bootstraps=1000, ci_percentile=95, random_state=None): - + """One-sample group-level bootstrap hypothesis test for ISCs For ISCs from one more voxels or ROIs, resample subjects with replacement @@ -385,24 +389,25 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. ISC values should be either N ISC values for N subjects in the leave-one-out appraoch (pairwise=False), N(N-1)/2 ISC values for N subjects in the pairwise - approach (pairwise=True). In the pairwise approach, ISC values should + approach (pairwise=True). In the pairwise approach, ISC values should correspond to the vectorized upper triangle of a square corrlation matrix (see scipy.stats.distance.squareform). Shifts bootstrap distribution by actual summary statistic (effectively to zero) for two-tailed null hypothesis test (Hall & Wilson, 1991). Uses subject-wise (not pair-wise) - resampling in the pairwise approach. Returns the observed ISC, the confidence - interval, and a p-value for the bootstrap hypothesis test, as well as - the bootstrap distribution of summary statistics. According to Chen et al., - 2016, this is the preferred nonparametric approach for controlling false - positive rates (FPR) for one-sample tests in the pairwise approach. - + resampling in the pairwise approach. Returns the observed ISC, the + confidence interval, and a p-value for the bootstrap hypothesis test, as + well as the bootstrap distribution of summary statistics. According to + Chen et al., 2016, this is the preferred nonparametric approach for + controlling false positive rates (FPR) for one-sample tests in the pairwise + approach. + The implementation is based on the following publications: - - .. [Chen2016] "Untangling the relatedness among correlations, part I: + + .. [Chen2016] "Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. - + .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. @@ -422,7 +427,7 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', ci_percentile : int, default:95 Percentile for computing confidence intervals - + random_state = int or None, default:None Initial random seed @@ -436,29 +441,30 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', p : float, p-value p-value based on bootstrap hypothesis test - + distribution : ndarray, bootstraps by voxels (optional) Bootstrap distribution if return_bootstrap=True - + """ - + # Standardize structure of input data iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) - + # Check for valid summary statistic if summary_statistic not in ('mean', 'median'): raise ValueError("Summary statistic must be 'mean' or 'median'") - + # Compute summary statistic for observed ISCs - observed = compute_summary_statistic(iscs, summary_statistic=summary_statistic, - axis=0)[np.newaxis, :] - + observed = compute_summary_statistic(iscs, + summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] + # Set up an empty list to build our bootstrap distribution distribution = [] - + # Loop through n bootstrap iterations and populate distribution for i in np.arange(n_bootstraps): - + # Random seed to be deterministically re-randomized at each iteration if isinstance(random_state, np.random.RandomState): prng = random_state @@ -467,12 +473,12 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', # Randomly sample subject IDs with replacement subject_sample = sorted(prng.choice(np.arange(n_subjects), - size=n_subjects)) - + size=n_subjects)) + # Squareform and shuffle rows/columns of pairwise ISC matrix to # to retain correlation structure among ISCs, then get triangle if pairwise: - + # Loop through voxels isc_sample = [] for voxel_iscs in iscs.T: @@ -493,49 +499,50 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', voxel_sample[voxel_sample == 1.] = np.NaN isc_sample.append(voxel_sample) - + isc_sample = np.column_stack(isc_sample) # Get simple bootstrap sample if not pairwise elif not pairwise: isc_sample = iscs[subject_sample, :] - + # Compute summary statistic for bootstrap ISCs per voxel # (alternatively could construct distribution for all voxels # then compute statistics, but larger memory footprint) distribution.append(compute_summary_statistic(isc_sample, - summary_statistic=summary_statistic, - axis=0)) - + summary_statistic=summary_statistic, + axis=0)) + # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - + # Convert distribution to numpy array distribution = np.array(distribution) assert distribution.shape == (n_bootstraps, n_voxels) # Compute CIs of median from bootstrap distribution (default: 95%) ci = (np.percentile(distribution, (100 - ci_percentile)/2, axis=0), - np.percentile(distribution, ci_percentile + (100 - ci_percentile)/2, axis=0)) - + np.percentile(distribution, ci_percentile + (100 - ci_percentile)/2, + axis=0)) + # Shift bootstrap distribution to 0 for hypothesis test shifted = distribution - observed - + # Get p-value for actual median from shifted distribution p = compute_p_from_null_distribution(observed, shifted, side='two-sided', exact=False, axis=0) - + # Reshape p-values to fit with data shape p = p[np.newaxis, :] - + return observed, ci, p, distribution - + def permutation_isc(iscs, group_assignment=None, pairwise=False, summary_statistic='median', n_permutations=1000, random_state=None): - + """Group-level permutation test for ISCs For ISCs from one or more voxels or ROIs, permute group assignments to @@ -543,8 +550,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. If two groups, ISC values should stacked along first dimension (vertically), and a group_assignment list (or 1d array) of same length as the number of - subjects should be provided to indicate group labels. If no group_assignment - is provided, a one-sample test is performed using a sign-flipping procedure. + subjects should be provided to indicate groups. If no group_assignment + is provided, one-sample test is performed using a sign-flipping procedure. Performs exact test if number of possible permutations (2**N for one-sample sign-flipping, N! for two-sample shuffling) is less than or equal to number of requested permutation; otherwise, performs approximate permutation test @@ -552,21 +559,22 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, N subjects in the leave-one-out approach (pairwise=False) or N(N-1)/2 ISC values for N subjects in the pairwise approach (pairwise=True). In the pairwise approach, ISC values should correspond to the vectorized upper - triangle of a square corrlation matrix (see scipy.stats.distance.squareform). + triangle of a square corrlation matrix (scipy.stats.distance.squareform). Note that in the pairwise approach, group_assignment order should match the - row/column order of the subject-by-subject square ISC matrix even though the - input ISCs should be supplied as the vectorized upper triangle of the square - ISC matrix. Returns the observed ISC and permutation-based p-value (two-tailed - test), as well as the permutation distribution of summary statistic. - According to Chen et al., 2016, this is the preferred nonparametric approach - for controlling false positive rates (FPR) for two-sample tests. This approach - may yield inflated FPRs for one-sample tests. - + row/column order of the subject-by-subject square ISC matrix even though + the input ISCs should be supplied as the vectorized upper triangle of the + square ISC matrix. Returns the observed ISC and permutation-based p-value + (two-tailed test), as well as the permutation distribution of summary + statistic. According to Chen et al., 2016, this is the preferred + nonparametric approach for controlling false positive rates (FPR) for + two-sample tests. This approach may yield inflated FPRs for one-sample + tests. + The implementation is based on the following publication: - - .. [Chen2016] "Untangling the relatedness among correlations, part I: + + .. [Chen2016] "Untangling the relatedness among correlations, part I: nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. Parameters @@ -576,10 +584,10 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, group_assignment : list or ndarray, group labels Group labels matching order of ISC input - + pairwise : bool, default:False Indicator of pairwise or leave-one-out, should match ISCs variable - + summary_statistic : str, default:'median' Summary statistic, either 'median' (default) or 'mean' @@ -596,46 +604,47 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, p : float, p-value p-value based on permutation test - + distribution : ndarray, permutations by voxels (optional) Permutation distribution if return_bootstrap=True """ - + # Standardize structure of input data iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) - + # Check for valid summary statistic if summary_statistic not in ('mean', 'median'): raise ValueError("Summary statistic must be 'mean' or 'median'") - + # Check match between group labels and ISCs if type(group_assignment) == list: pass elif type(group_assignment) == np.ndarray: group_assignment = group_assignment.tolist() else: - logger.info("No group assignment provided, performing one-sample test.") - + logger.info("No group assignment provided, " + "performing one-sample test.") + if group_assignment and len(group_assignment) != n_subjects: raise ValueError(f"Group assignments ({len(group_assignment)}) " f"do not match number of subjects ({n_subjects})!") - + # Set up group selectors for two-group scenario if group_assignment and len(np.unique(group_assignment)) == 2: n_groups = 2 - + # Get group labels and counts group_labels = np.unique(group_assignment) groups = {group_labels[0]: group_assignment.count(group_labels[0]), group_labels[1]: group_assignment.count(group_labels[1])} # For two-sample pairwise approach set up selector from matrix - if pairwise == True: + if pairwise: # Sort the group_assignment variable if it came in shuffled # so it's easier to build group assignment matrix sorter = np.array(group_assignment).argsort() unsorter = np.array(group_assignment).argsort().argsort() - + # Populate a matrix with group assignments upper_left = np.full((groups[group_labels[0]], groups[group_labels[0]]), @@ -652,28 +661,28 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, group_matrix = np.vstack((np.hstack((upper_left, upper_right)), np.hstack((lower_left, lower_right)))) np.fill_diagonal(group_matrix, np.nan) - + # Unsort matrix and squareform to create selector group_selector = squareform(group_matrix[unsorter, :][:, unsorter], checks=False) - + # If leave-one-out approach, just user group assignment as selector - elif pairwise == False: + elif not pairwise: group_selector = group_assignment - + # Manage one-sample and incorrect group assignments elif not group_assignment or len(np.unique(group_assignment)) == 1: n_groups = 1 - + # If pairwise initialize matrix of ones for sign-flipping ones_matrix = np.ones((n_subjects, n_subjects)) - + elif len(np.unique(group_assignment)) > 2: raise ValueError("This test is not valid for more than " f"2 groups! (got {len(np.unique(group_assignment))})") else: raise ValueError("Invalid group assignments!") - + # Set up permutation type (exact or Monte Carlo) if n_groups == 1: if n_permutations < 2**n_subjects: @@ -699,28 +708,31 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, exact_permutations = list(it.permutations( np.arange(len(group_assignment)))) n_permutations = np.math.factorial(n_subjects) - + # If one group, just get observed summary statistic if n_groups == 1: - observed = compute_summary_statistic(iscs, summary_statistic=summary_statistic, - axis=0)[np.newaxis, :] + observed = compute_summary_statistic(iscs, + summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] # If two groups, get the observed difference - elif n_groups == 2: - observed = (compute_summary_statistic(iscs[group_selector == group_labels[0], :], - summary_statistic=summary_statistic, - axis=0) - - compute_summary_statistic(iscs[group_selector == group_labels[1], :], - summary_statistic=summary_statistic, - axis=0)) + elif n_groups == 2: + observed = (compute_summary_statistic( + iscs[group_selector == group_labels[0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic( + iscs[group_selector == group_labels[1], :], + summary_statistic=summary_statistic, + axis=0)) observed = np.array(observed)[np.newaxis, :] - + # Set up an empty list to build our permutation distribution distribution = [] - + # Loop through n permutation iterations and populate distribution for i in np.arange(n_permutations): - + # Random seed to be deterministically re-randomized at each iteration if exact_permutations: pass @@ -728,30 +740,31 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, prng = random_state else: prng = np.random.RandomState(random_state) - + # If one group, apply sign-flipping procedure if n_groups == 1: - + # Randomized sign-flips if exact_permutations: sign_flipper = np.array(exact_permutations[i]) elif not exact_permutations: - sign_flipper = prng.choice([-1, 1], size=n_subjects, replace=True) - + sign_flipper = prng.choice([-1, 1], size=n_subjects, + replace=True) + # If pairwise, apply sign-flips by rows and columns if pairwise: matrix_flipped = (ones_matrix * sign_flipper * sign_flipper[:, np.newaxis]) sign_flipper = squareform(matrix_flipped, checks=False) - + # Apply flips along ISC axis (same across voxels) isc_flipped = iscs * sign_flipper[:, np.newaxis] - + # Get summary statistics on sign-flipped ISCs isc_sample = compute_summary_statistic(isc_flipped, - summary_statistic=summary_statistic, - axis=0) - + summary_statistic=summary_statistic, + axis=0) + # If two groups, set up group matrix get the observed difference elif n_groups == 2: @@ -764,39 +777,44 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, elif not exact_permutations and not pairwise: group_shuffler = prng.permutation(np.arange( len(group_assignment))) - + # If pairwise approach, convert group assignments to matrix if pairwise: - + # Apply shuffler to group matrix rows/columns - group_shuffled = group_matrix[group_shuffler, :][:, group_shuffler] - + group_shuffled = group_matrix[ + group_shuffler, :][:, group_shuffler] + # Unsort shuffled matrix and squareform to create selector - group_selector = squareform(group_shuffled[unsorter, :][:, unsorter], - checks=False) - + group_selector = squareform( + group_shuffled[unsorter, :][:, unsorter], + checks=False) + # Shuffle group assignments in leave-one-out two sample test elif not pairwise: - + # Apply shuffler to group matrix rows/columns group_selector = np.array(group_assignment)[group_shuffler] - + # Get difference of within-group summary statistics - # with group permutation - isc_sample = (compute_summary_statistic(iscs[group_selector == group_labels[0], :], - summary_statistic=summary_statistic, - axis=0) - - compute_summary_statistic(iscs[group_selector == group_labels[1], :], - summary_statistic=summary_statistic, - axis=0)) - + # with group permutation + isc_sample = (compute_summary_statistic( + iscs[group_selector == group_labels[0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic( + iscs[group_selector == group_labels[1], :], + summary_statistic=summary_statistic, + axis=0)) + # Tack our permuted ISCs onto the permutation distribution - distribution.append(isc_sample) - + distribution.append(isc_sample) + # Update random state for next iteration if not exact_permutations: - random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - + random_state = np.random.RandomState(prng.randint( + 0, MAX_RANDOM_SEED)) + # Convert distribution to numpy array distribution = np.array(distribution) assert distribution.shape == (n_permutations, n_voxels) @@ -810,18 +828,18 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False, axis=0) - + # Reshape p-values to fit with data shape p = p[np.newaxis, :] - + return observed, p, distribution def timeshift_isc(data, pairwise=False, summary_statistic='median', n_shifts=1000, random_state=None): - + """Circular time-shift randomization for one-sample ISC test - + For each voxel or ROI, compute the actual ISC and p-values from a null distribution of ISCs where response time series are first circularly shifted by random intervals. If pairwise, @@ -834,32 +852,32 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', supplied, the last dimension is assumed to correspond to subjects. Returns the observed ISC and p-values (two-tailed test), as well as the null distribution of ISCs computed on randomly time-shifted data. - + This implementation is based on the following publications: - .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic + .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic responses during watching a movie: localization in space and frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, 2010, Frontiers in Neuroinformatics, 4, 5. .. [Kauppi2014] "A versatile software package for inter-subject - correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, + correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. Parameters ---------- data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISFC - + pairwise : bool, default: False Whether to use pairwise (True) or leave-one-out (False) approach summary_statistic : str, default:'median' Summary statistic, either 'median' (default) or 'mean' - + n_shifts : int, default:1000 Number of randomly shifted samples - + random_state = int, None, or np.random.RandomState, default:None Initial random seed @@ -870,90 +888,94 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', p : float, p-value p-value based on time-shifting randomization test - + distribution : ndarray, time-shifts by voxels (optional) Time-shifted null distribution if return_bootstrap=True """ # Check response time series input format data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - + # Get actual observed ISC - observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) - + observed = isc(data, pairwise=pairwise, + summary_statistic=summary_statistic) + # Roll axis to get subjects in first dimension for loop if pairwise: data = np.rollaxis(data, 2, 0) - + # Iterate through randomized shifts to create null distribution distribution = [] for i in np.arange(n_shifts): - + # Random seed to be deterministically re-randomized at each iteration if isinstance(random_state, np.random.RandomState): prng = random_state else: prng = np.random.RandomState(random_state) - + # Get a random set of shifts based on number of TRs, shifts = prng.choice(np.arange(n_TRs), size=n_subjects, replace=True) - + # In pairwise approach, apply all shifts then compute pairwise ISCs if pairwise: - + # Apply circular shift to each subject's time series shifted_data = [] - for subject, shift in zip(data, shifts): + for subject, shift in zip(data, shifts): shifted_data.append(np.concatenate( - (subject[-shift:, :], subject[:-shift, :]))) + (subject[-shift:, :], + subject[:-shift, :]))) shifted_data = np.dstack(shifted_data) # Compute null ISC on shifted data for pairwise approach shifted_isc = isc(shifted_data, pairwise=pairwise, summary_statistic=summary_statistic) - + # In leave-one-out, apply shift only to each left-out participant elif not pairwise: - + shifted_isc = [] for s, shift in enumerate(shifts): - shifted_subject = np.concatenate((data[-shift:, :, s], data[:-shift, :, s])) + shifted_subject = np.concatenate((data[-shift:, :, s], + data[:-shift, :, s])) nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) - loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, + loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), + pairwise=False, summary_statistic=None) shifted_isc.append(loo_isc) - + # Get summary statistics across left-out subjects shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), - summary_statistic=summary_statistic, - axis=2) - + summary_statistic=summary_statistic, + axis=2) + distribution.append(shifted_isc) - + # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - + # Convert distribution to numpy array distribution = np.vstack(distribution) assert distribution.shape == (n_shifts, n_voxels) # Get p-value for actual median from shifted distribution p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False, + side='two-sided', exact=False, axis=0) - + # Reshape p-values to fit with data shape p = p[np.newaxis, :] - + return observed, p, distribution - + def phaseshift_isc(data, pairwise=False, summary_statistic='median', n_shifts=1000, random_state=None): - + """Phase randomization for one-sample ISC test - + For each voxel or ROI, compute the actual ISC and p-values from a null distribution of ISCs where response time series are phase randomized prior to computing ISC. If pairwise, @@ -966,7 +988,7 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', supplied, the last dimension is assumed to correspond to subjects. Returns the observed ISC and p-values (two-tailed test), as well as the null distribution of ISCs computed on phase-randomized data. - + This implementation is based on the following publications: .. [Lerner2011] "Topographic mapping of a hierarchy of temporal @@ -982,16 +1004,16 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', ---------- data : list or ndarray (n_TRs x n_voxels x n_subjects) fMRI data for which to compute ISFC - + pairwise : bool, default: False Whether to use pairwise (True) or leave-one-out (False) approach summary_statistic : str, default:'median' Summary statistic, either 'median' (default) or 'mean' - + n_shifts : int, default:1000 Number of randomly shifted samples - + random_state = int, None, or np.random.RandomState, default:None Initial random seed @@ -1002,41 +1024,43 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', p : float, p-value p-value based on time-shifting randomization test - + distribution : ndarray, time-shifts by voxels (optional) Time-shifted null distribution if return_bootstrap=True """ # Check response time series input format data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - + # Get actual observed ISC - observed = isc(data, pairwise=pairwise, summary_statistic=summary_statistic) + observed = isc(data, pairwise=pairwise, + summary_statistic=summary_statistic) # Iterate through randomized shifts to create null distribution distribution = [] for i in np.arange(n_shifts): - + # Random seed to be deterministically re-randomized at each iteration if isinstance(random_state, np.random.RandomState): prng = random_state else: prng = np.random.RandomState(random_state) - + # Get randomized phase shifts if n_TRs % 2 == 0: - # Why are we indexing from 1 not zero here? Vector is n_TRs / -1 long? + # Why are we indexing from 1 not zero here? n_TRs / -1 long? pos_freq = np.arange(1, data.shape[0] // 2) neg_freq = np.arange(data.shape[0] - 1, data.shape[0] // 2, -1) else: pos_freq = np.arange(1, (data.shape[0] - 1) // 2 + 1) - neg_freq = np.arange(data.shape[0] - 1, (data.shape[0] - 1) // 2, -1) + neg_freq = np.arange(data.shape[0] - 1, + (data.shape[0] - 1) // 2, -1) phase_shifts = prng.rand(len(pos_freq), 1, n_subjects) * 2 * np.math.pi - + # In pairwise approach, apply all shifts then compute pairwise ISCs if pairwise: - + # Fast Fourier transform along time dimension of data fft_data = fft(data, axis=0) @@ -1050,41 +1074,40 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', # Compute null ISC on shifted data for pairwise approach shifted_isc = isc(shifted_data, pairwise=True, summary_statistic=summary_statistic) - + # In leave-one-out, apply shift only to each left-out participant elif not pairwise: - + # Roll subject axis in phaseshifts for loop phase_shifts = np.rollaxis(phase_shifts, 2, 0) - + shifted_isc = [] for s, shift in enumerate(phase_shifts): - + # Apply FFT to left-out subject fft_subject = fft(data[:, :, s], axis=0) - - # Shift pos and neg frequencies symmetrically, to keep signal real + + # Shift pos and neg frequencies symmetrically, keep signal real fft_subject[pos_freq, :] *= np.exp(1j * shift) fft_subject[neg_freq, :] *= np.exp(-1j * shift) # Inverse FFT to put data back in time domain for ISC shifted_subject = np.real(ifft(fft_subject, axis=0)) - # Compute ISC of shifted left-out subject against mean of N-1 subjects + # ISC of shifted left-out subject vs mean of N-1 subjects nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) - loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), pairwise=False, - summary_statistic=None) + loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), + pairwise=False, summary_statistic=None) shifted_isc.append(loo_isc) - + # Get summary statistics across left-out subjects shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), - summary_statistic=summary_statistic, - axis=2) + summary_statistic=summary_statistic, axis=2) distribution.append(shifted_isc) - + # Update random state for next iteration random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - + # Convert distribution to numpy array distribution = np.vstack(distribution) assert distribution.shape == (n_shifts, n_voxels) @@ -1093,8 +1116,9 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False, axis=0) - + # Reshape p-values to fit with data shape p = p[np.newaxis, :] - - return observed, p, distribution \ No newline at end of file + + return observed, p, distribution + diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index 2aa1469e1..4311e60d8 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -823,9 +823,9 @@ def p_from_null(X, two_sided=False, def compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False, axis=None): - + """Compute p-value from null distribution - + Returns the p-value for an observed test statistic given a null distribution. Performs either a 'two-sided' (i.e., two-tailed) test (default) or a one-sided (i.e., one-tailed) test for either the @@ -834,14 +834,14 @@ def compute_p_from_null_distribution(observed, distribution, test statistic (prevents p-values of zero). If a multidimensional distribution is provided, use axis argument to specify which axis indexes resampling iterations. - + The implementation is based on the following publication: - + .. [PhipsonSmyth2010] "Permutation p-values should never be zero: calculating exact p-values when permutations are randomly drawn.", B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics and Molecular Biology, 9, 1544-6115. - + Parameters ---------- observed : float @@ -849,7 +849,7 @@ def compute_p_from_null_distribution(observed, distribution, distribution : ndarray Null distribution of test statistic - + side : str, default:'two-sided' Perform one-sided ('left' or 'right') or 'two-sided' test @@ -861,14 +861,14 @@ def compute_p_from_null_distribution(observed, distribution, p : float p-value for observed test statistic based on null distribution """ - + if side not in ('two-sided', 'left', 'right'): raise ValueError("The value for 'side' must be either " f"'two-sided', 'left', or 'right', got {side}") - + n_samples = len(distribution) logger.info(f"Assuming {n_samples} resampling iterations") - + if side == 'two-sided': # numerator for two-sided test numerator = np.sum(np.abs(distribution) >= np.abs(observed), axis=axis) @@ -878,14 +878,15 @@ def compute_p_from_null_distribution(observed, distribution, elif side == 'right': # numerator for one-sided test in right tail numerator = np.sum(distribution >= observed, axis=axis) - - # If exact test (all possible permutations), do not adjust + + # If exact test all possible permutations and do not adjust if exact: p = numerator / n_samples - + # If not exact test, adjust number of samples to account for # observed statistic; prevents p-value from being zero else: p = (numerator + 1) / (n_samples + 1) - - return p \ No newline at end of file + + return p + From 304ad5006c049dc5c9cef568056374f50f18fa26 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 19 Nov 2018 13:08:18 -0500 Subject: [PATCH 30/43] Changed module name from isfc to isc, which is more general family of analyses --- brainiak/{isfc.py => isc.py} | 0 tests/{isfc => isc}/mask.nii.gz | Bin tests/{isfc => isc}/subj1.nii.gz | Bin tests/{isfc => isc}/subj2.nii.gz | Bin tests/{isfc/test_isfc.py => isc/test_isc.py} | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename brainiak/{isfc.py => isc.py} (100%) rename tests/{isfc => isc}/mask.nii.gz (100%) rename tests/{isfc => isc}/subj1.nii.gz (100%) rename tests/{isfc => isc}/subj2.nii.gz (100%) rename tests/{isfc/test_isfc.py => isc/test_isc.py} (99%) diff --git a/brainiak/isfc.py b/brainiak/isc.py similarity index 100% rename from brainiak/isfc.py rename to brainiak/isc.py diff --git a/tests/isfc/mask.nii.gz b/tests/isc/mask.nii.gz similarity index 100% rename from tests/isfc/mask.nii.gz rename to tests/isc/mask.nii.gz diff --git a/tests/isfc/subj1.nii.gz b/tests/isc/subj1.nii.gz similarity index 100% rename from tests/isfc/subj1.nii.gz rename to tests/isc/subj1.nii.gz diff --git a/tests/isfc/subj2.nii.gz b/tests/isc/subj2.nii.gz similarity index 100% rename from tests/isfc/subj2.nii.gz rename to tests/isc/subj2.nii.gz diff --git a/tests/isfc/test_isfc.py b/tests/isc/test_isc.py similarity index 99% rename from tests/isfc/test_isfc.py rename to tests/isc/test_isc.py index 1dfb6e3ff..9c6d5558d 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isc/test_isc.py @@ -1,6 +1,6 @@ import numpy as np import logging -from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_ics, +from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_ics, timeshift_isc, phaseshift_isc) from scipy.spatial.distance import squareform From dc297fc4de1af1a3853ad85db1bb5333707d5a6c Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Mon, 19 Nov 2018 18:27:25 -0500 Subject: [PATCH 31/43] Minor fixes for Travis, and ISC input check now accepts MaskedMultiSubjectData --- brainiak/image.py | 1 - brainiak/isc.py | 10 +++++----- brainiak/utils/utils.py | 4 ++-- tests/isc/test_isc.py | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/brainiak/image.py b/brainiak/image.py index 75482d996..f86261d13 100644 --- a/brainiak/image.py +++ b/brainiak/image.py @@ -179,4 +179,3 @@ def mask_images(images: Iterable[SpatialImage], mask: np.ndarray, """ for images in multimask_images(images, (mask,), image_type): yield images[0] - diff --git a/brainiak/isc.py b/brainiak/isc.py index 706a44c21..ed1e390c2 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -265,14 +265,14 @@ def check_timeseries_input(data): data = np.dstack(data) # Convert input ndarray to 3d and check shape - elif type(data) == np.ndarray: + elif isinstance(data, np.ndarray): if data.ndim == 2: data = data[:, np.newaxis, :] elif data.ndim == 3: pass else: raise ValueError("Input ndarray should have 2 " - f"or 3 dimensions (got {data.ndim})!") + "or 3 dimensions (got {0})!".format(data.ndim)) # Infer subjects, TRs, voxels and log for user to check n_TRs, n_voxels, n_subjects = data.shape @@ -311,7 +311,7 @@ def check_isc_input(iscs, pairwise=False): if type(iscs) == list: iscs = np.array(iscs)[:, np.newaxis] - elif type(iscs) == np.ndarray: + elif isinstance(iscs, np.ndarray): if iscs.ndim == 1: iscs = iscs[:, np.newaxis] @@ -509,7 +509,8 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', # Compute summary statistic for bootstrap ISCs per voxel # (alternatively could construct distribution for all voxels # then compute statistics, but larger memory footprint) - distribution.append(compute_summary_statistic(isc_sample, + distribution.append(compute_summary_statistic( + isc_sample, summary_statistic=summary_statistic, axis=0)) @@ -1121,4 +1122,3 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', p = p[np.newaxis, :] return observed, p, distribution - diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index 9fee0b634..8dbcd17a5 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -879,7 +879,8 @@ def compute_p_from_null_distribution(observed, distribution, if side not in ('two-sided', 'left', 'right'): raise ValueError("The value for 'side' must be either " - f"'two-sided', 'left', or 'right', got {side}") + "'two-sided', 'left', or 'right', got {0}". + format(side)) n_samples = len(distribution) logger.info(f"Assuming {n_samples} resampling iterations") @@ -904,4 +905,3 @@ def compute_p_from_null_distribution(observed, distribution, p = (numerator + 1) / (n_samples + 1) return p - diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py index 9c6d5558d..926bd241d 100644 --- a/tests/isc/test_isc.py +++ b/tests/isc/test_isc.py @@ -1,6 +1,6 @@ import numpy as np import logging -from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_ics, +from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, timeshift_isc, phaseshift_isc) from scipy.spatial.distance import squareform From 36b194df25c2936642bcbc48ba1d5ca54b3754be Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Tue, 4 Dec 2018 20:30:17 -0500 Subject: [PATCH 32/43] Fixed installation error, removed crud --- brainiak/isc.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/brainiak/isc.py b/brainiak/isc.py index ed1e390c2..8cec770cb 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -27,7 +27,7 @@ import numpy as np import logging from scipy.spatial.distance import squareform -from scipy.stats import pearsonr, zscore +from scipy.stats import pearsonr from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation @@ -712,7 +712,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # If one group, just get observed summary statistic if n_groups == 1: - observed = compute_summary_statistic(iscs, + observed = compute_summary_statistic( + iscs, summary_statistic=summary_statistic, axis=0)[np.newaxis, :] @@ -762,7 +763,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, isc_flipped = iscs * sign_flipper[:, np.newaxis] # Get summary statistics on sign-flipped ISCs - isc_sample = compute_summary_statistic(isc_flipped, + isc_sample = compute_summary_statistic( + isc_flipped, summary_statistic=summary_statistic, axis=0) @@ -948,7 +950,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects - shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), + shifted_isc = compute_summary_statistic( + np.dstack(shifted_isc), summary_statistic=summary_statistic, axis=2) @@ -1102,7 +1105,8 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects - shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), + shifted_isc = compute_summary_statistic( + np.dstack(shifted_isc), summary_statistic=summary_statistic, axis=2) distribution.append(shifted_isc) From 1f0546c48d989b874a2cbbd5061467d5c081530b Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 5 Dec 2018 11:18:08 -0500 Subject: [PATCH 33/43] =?UTF-8?q?Removed=20f-strings=20to=20please=20travi?= =?UTF-8?q?s=20.=C2=B7=C2=B4=C2=AF=C2=B7.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- brainiak/isc.py | 19 ++++++++++++------- brainiak/utils/utils.py | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/brainiak/isc.py b/brainiak/isc.py index ed1e390c2..378c21b27 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -27,7 +27,7 @@ import numpy as np import logging from scipy.spatial.distance import squareform -from scipy.stats import pearsonr, zscore +from scipy.stats import pearsonr from scipy.fftpack import fft, ifft import itertools as it from brainiak.fcma.util import compute_correlation @@ -276,8 +276,9 @@ def check_timeseries_input(data): # Infer subjects, TRs, voxels and log for user to check n_TRs, n_voxels, n_subjects = data.shape - logger.info(f"Assuming {n_subjects} subjects with {n_TRs} time points " - f"and {n_voxels} voxel(s) or ROI(s) for ISC analysis.") + logger.info("Assuming {0} subjects with {1} time points " + "and {2} voxel(s) or ROI(s) for ISC analysis.".format( + n_subjects, n_TRs, n_voxels)) return data, n_TRs, n_voxels, n_subjects @@ -712,7 +713,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # If one group, just get observed summary statistic if n_groups == 1: - observed = compute_summary_statistic(iscs, + observed = compute_summary_statistic( + iscs, summary_statistic=summary_statistic, axis=0)[np.newaxis, :] @@ -762,7 +764,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, isc_flipped = iscs * sign_flipper[:, np.newaxis] # Get summary statistics on sign-flipped ISCs - isc_sample = compute_summary_statistic(isc_flipped, + isc_sample = compute_summary_statistic( + isc_flipped, summary_statistic=summary_statistic, axis=0) @@ -948,7 +951,8 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects - shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), + shifted_isc = compute_summary_statistic( + np.dstack(shifted_isc), summary_statistic=summary_statistic, axis=2) @@ -1102,7 +1106,8 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', shifted_isc.append(loo_isc) # Get summary statistics across left-out subjects - shifted_isc = compute_summary_statistic(np.dstack(shifted_isc), + shifted_isc = compute_summary_statistic( + np.dstack(shifted_isc), summary_statistic=summary_statistic, axis=2) distribution.append(shifted_isc) diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index 8dbcd17a5..14effcf51 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -883,7 +883,7 @@ def compute_p_from_null_distribution(observed, distribution, format(side)) n_samples = len(distribution) - logger.info(f"Assuming {n_samples} resampling iterations") + logger.info("Assuming {0} resampling iterations".format(n_samples)) if side == 'two-sided': # numerator for two-sided test From 342d14f56e1041079c256cba63b7b769f585205e Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 5 Dec 2018 13:54:34 -0500 Subject: [PATCH 34/43] Re-added 'isfc' module with deprecation warning --- brainiak/isc.py | 24 +- brainiak/isfc.py | 1137 +++++++++++++++++++++++++++++++++++++++ tests/isfc/test_isfc.py | 556 +++++++++++++++++++ 3 files changed, 1707 insertions(+), 10 deletions(-) create mode 100644 brainiak/isfc.py create mode 100644 tests/isfc/test_isfc.py diff --git a/brainiak/isc.py b/brainiak/isc.py index 378c21b27..18450f0f0 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -329,8 +329,8 @@ def check_isc_input(iscs, pairwise=False): # Infer subjects, voxels and print for user to check n_voxels = iscs.shape[1] - logger.info(f"Assuming {n_subjects} subjects with and {n_voxels} " - "voxel(s) or ROI(s) in bootstrap ISC test.") + logger.info("Assuming {n_subjects} subjects with and {0} " + "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_voxels)) return iscs, n_subjects, n_voxels @@ -628,8 +628,9 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, "performing one-sample test.") if group_assignment and len(group_assignment) != n_subjects: - raise ValueError(f"Group assignments ({len(group_assignment)}) " - f"do not match number of subjects ({n_subjects})!") + raise ValueError("Group assignments ({0}) " + "do not match number of subjects ({1})!".format( + len(group_assignment), n_subjects)) # Set up group selectors for two-group scenario if group_assignment and len(np.unique(group_assignment)) == 2: @@ -681,7 +682,8 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, elif len(np.unique(group_assignment)) > 2: raise ValueError("This test is not valid for more than " - f"2 groups! (got {len(np.unique(group_assignment))})") + "2 groups! (got {0})".format( + len(np.unique(group_assignment)))) else: raise ValueError("Invalid group assignments!") @@ -693,8 +695,9 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, exact_permutations = None elif n_permutations >= 2**n_subjects: logger.info("One-sample exact permutation test using " - f"sign-flipping procedure with 2**{n_subjects} " - f"({2**n_subjects}) iterations.") + "sign-flipping procedure with 2**{0} " + "({1}) iterations.".format(n_subjects, + 2**n_subjects)) exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) n_permutations = 2**n_subjects elif n_groups == 2: @@ -704,9 +707,10 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, exact_permutations = None elif n_permutations >= np.math.factorial(n_subjects): logger.info("Two-sample exact permutation test using group " - f"randomization with {n_subjects}! " - f"({np.math.factorial(n_subjects)}) " - "iterations.") + "randomization with {0}! " + "({1}) iterations.".format( + n_subjects, + np.math.factorial(n_subjects))) exact_permutations = list(it.permutations( np.arange(len(group_assignment)))) n_permutations = np.math.factorial(n_subjects) diff --git a/brainiak/isfc.py b/brainiak/isfc.py new file mode 100644 index 000000000..f170af828 --- /dev/null +++ b/brainiak/isfc.py @@ -0,0 +1,1137 @@ +# Copyright 2017 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. + +"""Intersubject correlation (ISC) analysis + +Functions for computing intersubject correlation (ISC) and related +analyses (e.g., intersubject funtional correlations; ISFC), as well +as statistical tests designed specifically for ISC analyses. + +""" + +# Authors: Sam Nastase, Christopher Baldassano, Qihong Lu, +# Mai Nguyen, and Mor Regev +# Princeton University, 2018 + +import numpy as np +import logging +import warnings +from scipy.spatial.distance import squareform +from scipy.stats import pearsonr +from scipy.fftpack import fft, ifft +import itertools as it +from brainiak.fcma.util import compute_correlation +from brainiak.utils.utils import compute_p_from_null_distribution + +logger = logging.getLogger(__name__) + +MAX_RANDOM_SEED = 2**32 - 1 + +warnings.simplefilter('always', DeprecationWarning) +warnings.warn("'isfc' module name will be deprecated in an " + "upcoming version, use 'isc' instead", DeprecationWarning) + +def isc(data, pairwise=False, summary_statistic=None): + """Intersubject correlation + + For each voxel or ROI, compute the Pearson correlation between each + subject's response time series and other subjects' response time series. + If pairwise is False (default), use the leave-one-out approach, where + correlation is computed between each subject and the average of the other + subjects. If pairwise is True, compute correlations between all pairs of + subjects. If summary_statistic is None, return N ISC values for N subjects + (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, + corresponding to the upper triangle of the pairwise correlation matrix + (see scipy.spatial.distance.squareform). Alternatively, use either + 'mean' or 'median' to compute summary statistic of ISCs (Fisher Z will + be applied if using mean). Input data should be a n_TRs by n_voxels by + n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) or a list + where each item is a n_TRs by n_voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a 2D array is supplied, + the last dimension is assumed to correspond to subjects. If only two + subjects are supplied, simply compute Pearson correlation (precludes + averaging in leave-one-out approach, and does not apply summary statistic). + Output is an ndarray where the first dimension is the number of subjects + or pairs and the second dimension is the number of voxels (or ROIs). + + The implementation is based on the following publication: + + .. [Hasson2004] "Intersubject synchronization of cortical activity + during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, + R. Malach, 2004, Science, 303, 1634-1640. + + Parameters + ---------- + data : list or ndarray (n_TRs x n_voxels x n_subjects) + fMRI data for which to compute ISC + + pairwise : bool, default:False + Whether to use pairwise (True) or leave-one-out (False) approach + + summary_statistic : None or str, default:None + Return all ISCs or collapse using 'mean' or 'median' + + Returns + ------- + iscs : subjects or pairs by voxels ndarray + ISC for each subject or pair (or summary statistic) per voxel + + """ + + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) + + # No summary statistic if only two subjects + if n_subjects == 2: + logger.info("Only two subjects! Simply computing Pearson correlation.") + summary_statistic = None + + # Loop over each voxel or ROI + voxel_iscs = [] + for v in np.arange(n_voxels): + voxel_data = data[:, v, :].T + if n_subjects == 2: + iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] + elif pairwise: + iscs = squareform(np.corrcoef(voxel_data), checks=False) + elif not pairwise: + iscs = np.array([pearsonr(subject, + np.mean(np.delete(voxel_data, + s, axis=0), + axis=0))[0] + for s, subject in enumerate(voxel_data)]) + voxel_iscs.append(iscs) + iscs = np.column_stack(voxel_iscs) + + # Summarize results (if requested) + if summary_statistic: + iscs = compute_summary_statistic(iscs, + summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] + + return iscs + + +def isfc(data, pairwise=False, summary_statistic=None): + + """Intersubject functional correlation (ISFC) + + For each voxel or ROI, compute the Pearson correlation between each + subject's response time series and other subjects' response time series + for all voxels or ROIs. If pairwise is False (default), use the + leave-one-out approach, where correlation is computed between each + subject and the average of the other subjects. If pairwise is True, + compute correlations between all pairs of subjects. If summary_statistic + is None, return N ISFC values for N subjects (leave-one-out) or N(N-1)/2 + ISFC values for each pair of N subjects, corresponding to the upper + triangle of the correlation matrix (see scipy.spatial.distance.squareform). + Alternatively, use either 'mean' or 'median' to compute summary statistic + of ISFCs (Fisher Z is applied if using mean). Input should be n_TRs by + n_voxels by n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) + or a list where each item is a n_TRs by n_voxels ndarray per subject. + Multiple input ndarrays must be the same shape. If a 2D array is supplied, + the last dimension is assumed to correspond to subjects. If only two + subjects are supplied, simply compute ISFC between these two subjects + (precludes averaging in leave-one-out approach, and does not apply summary + statistic). Output is n_voxels by n_voxels array if summary_statistic is + supplied; otherwise output is n_voxels by n_voxels by n_subjects (or + n_pairs) array. + + The implementation is based on the following publication: + + .. [Simony2016] "Dynamic reconfiguration of the default mode network + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. + + Parameters + ---------- + data : list or ndarray (n_TRs x n_voxels x n_subjects) + fMRI data for which to compute ISFC + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach + + summary_statistic : None or str, default:None + Return all ISFCs or collapse using 'mean' or 'median' + + Returns + ------- + isfcs : subjects or pairs by voxels ndarray + ISFC for each subject or pair (or summary statistic) per voxel + + """ + + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) + + # Handle just two subjects properly + if n_subjects == 2: + isfcs = compute_correlation(np.ascontiguousarray(data[..., 0].T), + np.ascontiguousarray(data[..., 1].T)) + isfcs = (isfcs + isfcs.T) / 2 + assert isfcs.shape == (n_voxels, n_voxels) + summary_statistic = None + logger.info("Only two subjects! Computing ISFC between them.") + + # Compute all pairwise ISFCs + elif pairwise: + isfcs = [] + for pair in it.combinations(np.arange(n_subjects), 2): + isfc_pair = compute_correlation(np.ascontiguousarray( + data[..., pair[0]].T), + np.ascontiguousarray( + data[..., pair[1]].T)) + isfc_pair = (isfc_pair + isfc_pair.T) / 2 + isfcs.append(isfc_pair) + isfcs = np.dstack(isfcs) + assert isfcs.shape == (n_voxels, n_voxels, + n_subjects * (n_subjects - 1) / 2) + + # Compute ISFCs using leave-one-out approach + elif not pairwise: + + # Roll subject axis for loop + data = np.rollaxis(data, 2, 0) + + # Compute leave-one-out ISFCs + isfcs = [compute_correlation(np.ascontiguousarray(subject.T), + np.ascontiguousarray(np.mean( + np.delete(data, s, axis=0), + axis=0).T)) + for s, subject in enumerate(data)] + + # Transpose and average ISFC matrices for both directions + isfcs = np.dstack([(isfc_matrix + isfc_matrix.T) / 2 + for isfc_matrix in isfcs]) + assert isfcs.shape == (n_voxels, n_voxels, n_subjects) + + # Summarize results (if requested) + if summary_statistic: + isfcs = compute_summary_statistic(isfcs, + summary_statistic=summary_statistic, + axis=2) + + return isfcs + + +def check_timeseries_input(data): + + """Checks response time series input data for ISC analysis + + Input data should be a n_TRs by n_voxels by n_subjects ndarray + (e.g., brainiak.image.MaskedMultiSubjectData) or a list where each + item is a n_TRs by n_voxels ndarray for a given subject. Multiple + input ndarrays must be the same shape. If a 2D array is supplied, + the last dimension is assumed to correspond to subjects. + + Parameters + ---------- + data : ndarray or list + Time series data + + Returns + ------- + iscs : ndarray + Array of ISC values + + n_TRs : int + Number of time points (TRs) + + n_voxels : int + Number of voxels (or ROIs) + + n_subjects : int + Number of subjects + + """ + + # Convert list input to 3d and check shapes + if type(data) == list: + data_shape = data[0].shape + for i, d in enumerate(data): + if d.shape != data_shape: + raise ValueError("All ndarrays in input list " + "must be the same shape!") + if d.ndim == 1: + data[i] = d[:, np.newaxis] + data = np.dstack(data) + + # Convert input ndarray to 3d and check shape + elif isinstance(data, np.ndarray): + if data.ndim == 2: + data = data[:, np.newaxis, :] + elif data.ndim == 3: + pass + else: + raise ValueError("Input ndarray should have 2 " + "or 3 dimensions (got {0})!".format(data.ndim)) + + # Infer subjects, TRs, voxels and log for user to check + n_TRs, n_voxels, n_subjects = data.shape + logger.info("Assuming {0} subjects with {1} time points " + "and {2} voxel(s) or ROI(s) for ISC analysis.".format( + n_subjects, n_TRs, n_voxels)) + + return data, n_TRs, n_voxels, n_subjects + + +def check_isc_input(iscs, pairwise=False): + + """Checks ISC inputs for statistical tests + + Input ISCs should be n_subjects (leave-one-out approach) or + n_pairs (pairwise approach) by n_voxels or n_ROIs array or a 1D + array (or list) of ISC values for a single voxel or ROI. + + Parameters + ---------- + iscs : ndarray or list + ISC values + + Returns + ------- + iscs : ndarray + Array of ISC values + + n_subjects : int + Number of subjects + + n_voxels : int + Number of voxels (or ROIs) + """ + + # Standardize structure of input data + if type(iscs) == list: + iscs = np.array(iscs)[:, np.newaxis] + + elif isinstance(iscs, np.ndarray): + if iscs.ndim == 1: + iscs = iscs[:, np.newaxis] + + # Check if incoming pairwise matrix is vectorized triangle + if pairwise: + try: + test_square = squareform(iscs[:, 0]) + n_subjects = test_square.shape[0] + except ValueError: + raise ValueError("For pairwise input, ISCs must be the " + "vectorized triangle of a square matrix.") + elif not pairwise: + n_subjects = iscs.shape[0] + + # Infer subjects, voxels and print for user to check + n_voxels = iscs.shape[1] + logger.info("Assuming {n_subjects} subjects with and {0} " + "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_voxels)) + + return iscs, n_subjects, n_voxels + + +def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): + + """Computes summary statistics for ISCs + + Computes either the 'mean' or 'median' across a set of ISCs. In the + case of the mean, ISC values are first Fisher Z transformed (arctanh), + averaged, then inverse Fisher Z transformed (tanh). + + The implementation is based on the following publication: + + .. [SilverDunlap1987] "Averaging corrlelation coefficients: should + Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, + Journal of Applied Psychology, 72, 146-148. + + Parameters + ---------- + iscs : list or ndarray + ISC values + + summary_statistic : str, default:'mean' + Summary statistic, 'mean' or 'median' + + axis : None or int or tuple of ints, optional + Axis or axes along which the means are computed. The default is to + compute the mean of the flattened array. + + Returns + ------- + statistic : float or ndarray + Summary statistic of ISC values + + """ + + if summary_statistic not in ('mean', 'median'): + raise ValueError("Summary statistic must be 'mean' or 'median'") + + # Compute summary statistic + if summary_statistic == 'mean': + statistic = np.tanh(np.nanmean(np.arctanh(iscs), axis=axis)) + elif summary_statistic == 'median': + statistic = np.nanmedian(iscs, axis=axis) + + return statistic + + +def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', + n_bootstraps=1000, ci_percentile=95, random_state=None): + + """One-sample group-level bootstrap hypothesis test for ISCs + + For ISCs from one more voxels or ROIs, resample subjects with replacement + to construct a bootstrap distribution. Input is a list or ndarray of + ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. ISC values + should be either N ISC values for N subjects in the leave-one-out appraoch + (pairwise=False), N(N-1)/2 ISC values for N subjects in the pairwise + approach (pairwise=True). In the pairwise approach, ISC values should + correspond to the vectorized upper triangle of a square corrlation matrix + (see scipy.stats.distance.squareform). Shifts bootstrap distribution by + actual summary statistic (effectively to zero) for two-tailed null + hypothesis test (Hall & Wilson, 1991). Uses subject-wise (not pair-wise) + resampling in the pairwise approach. Returns the observed ISC, the + confidence interval, and a p-value for the bootstrap hypothesis test, as + well as the bootstrap distribution of summary statistics. According to + Chen et al., 2016, this is the preferred nonparametric approach for + controlling false positive rates (FPR) for one-sample tests in the pairwise + approach. + + The implementation is based on the following publications: + + .. [Chen2016] "Untangling the relatedness among correlations, part I: + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + + .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", + P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. + + Parameters + ---------- + iscs : list or ndarray, ISCs by voxels array + ISC values for one or more voxels + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs structure + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + n_bootstraps : int, default:1000 + Number of bootstrap samples (subject-level with replacement) + + ci_percentile : int, default:95 + Percentile for computing confidence intervals + + random_state = int or None, default:None + Initial random seed + + Returns + ------- + observed : float, median (or mean) ISC value + Summary statistic for actual ISCs + + ci : tuple, bootstrap confidence intervals + Confidence intervals generated from bootstrap distribution + + p : float, p-value + p-value based on bootstrap hypothesis test + + distribution : ndarray, bootstraps by voxels (optional) + Bootstrap distribution if return_bootstrap=True + + """ + + # Standardize structure of input data + iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) + + # Check for valid summary statistic + if summary_statistic not in ('mean', 'median'): + raise ValueError("Summary statistic must be 'mean' or 'median'") + + # Compute summary statistic for observed ISCs + observed = compute_summary_statistic(iscs, + summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] + + # Set up an empty list to build our bootstrap distribution + distribution = [] + + # Loop through n bootstrap iterations and populate distribution + for i in np.arange(n_bootstraps): + + # Random seed to be deterministically re-randomized at each iteration + if isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # Randomly sample subject IDs with replacement + subject_sample = sorted(prng.choice(np.arange(n_subjects), + size=n_subjects)) + + # Squareform and shuffle rows/columns of pairwise ISC matrix to + # to retain correlation structure among ISCs, then get triangle + if pairwise: + + # Loop through voxels + isc_sample = [] + for voxel_iscs in iscs.T: + + # Square the triangle and fill diagonal + voxel_iscs = squareform(voxel_iscs) + np.fill_diagonal(voxel_iscs, 1) + + # Check that pairwise ISC matrix is square and symmetric + assert voxel_iscs.shape[0] == voxel_iscs.shape[1] + assert np.allclose(voxel_iscs, voxel_iscs.T) + + # Shuffle square correlation matrix and get triangle + voxel_sample = voxel_iscs[subject_sample, :][:, subject_sample] + voxel_sample = squareform(voxel_sample, checks=False) + + # Censor off-diagonal 1s for same-subject pairs + voxel_sample[voxel_sample == 1.] = np.NaN + + isc_sample.append(voxel_sample) + + isc_sample = np.column_stack(isc_sample) + + # Get simple bootstrap sample if not pairwise + elif not pairwise: + isc_sample = iscs[subject_sample, :] + + # Compute summary statistic for bootstrap ISCs per voxel + # (alternatively could construct distribution for all voxels + # then compute statistics, but larger memory footprint) + distribution.append(compute_summary_statistic( + isc_sample, + summary_statistic=summary_statistic, + axis=0)) + + # Update random state for next iteration + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) + + # Convert distribution to numpy array + distribution = np.array(distribution) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Compute CIs of median from bootstrap distribution (default: 95%) + ci = (np.percentile(distribution, (100 - ci_percentile)/2, axis=0), + np.percentile(distribution, ci_percentile + (100 - ci_percentile)/2, + axis=0)) + + # Shift bootstrap distribution to 0 for hypothesis test + shifted = distribution - observed + + # Get p-value for actual median from shifted distribution + p = compute_p_from_null_distribution(observed, shifted, + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] + + return observed, ci, p, distribution + + +def permutation_isc(iscs, group_assignment=None, pairwise=False, + summary_statistic='median', n_permutations=1000, + random_state=None): + + """Group-level permutation test for ISCs + + For ISCs from one or more voxels or ROIs, permute group assignments to + construct a permutation distribution. Input is a list or ndarray of + ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. If two groups, + ISC values should stacked along first dimension (vertically), and a + group_assignment list (or 1d array) of same length as the number of + subjects should be provided to indicate groups. If no group_assignment + is provided, one-sample test is performed using a sign-flipping procedure. + Performs exact test if number of possible permutations (2**N for one-sample + sign-flipping, N! for two-sample shuffling) is less than or equal to number + of requested permutation; otherwise, performs approximate permutation test + using Monte Carlo resampling. ISC values should either be N ISC values for + N subjects in the leave-one-out approach (pairwise=False) or N(N-1)/2 ISC + values for N subjects in the pairwise approach (pairwise=True). In the + pairwise approach, ISC values should correspond to the vectorized upper + triangle of a square corrlation matrix (scipy.stats.distance.squareform). + Note that in the pairwise approach, group_assignment order should match the + row/column order of the subject-by-subject square ISC matrix even though + the input ISCs should be supplied as the vectorized upper triangle of the + square ISC matrix. Returns the observed ISC and permutation-based p-value + (two-tailed test), as well as the permutation distribution of summary + statistic. According to Chen et al., 2016, this is the preferred + nonparametric approach for controlling false positive rates (FPR) for + two-sample tests. This approach may yield inflated FPRs for one-sample + tests. + + The implementation is based on the following publication: + + .. [Chen2016] "Untangling the relatedness among correlations, part I: + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + + Parameters + ---------- + iscs : list or ndarray, correlation matrix of ISCs + ISC values for one or more voxels + + group_assignment : list or ndarray, group labels + Group labels matching order of ISC input + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs variable + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + n_permutations : int, default:1000 + Number of permutation iteration (randomizing group assignment) + + random_state = int, None, or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + observed : float, ISC summary statistic or difference + Actual ISC or group difference (excluding between-group ISCs) + + p : float, p-value + p-value based on permutation test + + distribution : ndarray, permutations by voxels (optional) + Permutation distribution if return_bootstrap=True + """ + + # Standardize structure of input data + iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) + + # Check for valid summary statistic + if summary_statistic not in ('mean', 'median'): + raise ValueError("Summary statistic must be 'mean' or 'median'") + + # Check match between group labels and ISCs + if type(group_assignment) == list: + pass + elif type(group_assignment) == np.ndarray: + group_assignment = group_assignment.tolist() + else: + logger.info("No group assignment provided, " + "performing one-sample test.") + + if group_assignment and len(group_assignment) != n_subjects: + raise ValueError("Group assignments ({0}) " + "do not match number of subjects ({1})!".format( + len(group_assignment), n_subjects)) + + # Set up group selectors for two-group scenario + if group_assignment and len(np.unique(group_assignment)) == 2: + n_groups = 2 + + # Get group labels and counts + group_labels = np.unique(group_assignment) + groups = {group_labels[0]: group_assignment.count(group_labels[0]), + group_labels[1]: group_assignment.count(group_labels[1])} + + # For two-sample pairwise approach set up selector from matrix + if pairwise: + # Sort the group_assignment variable if it came in shuffled + # so it's easier to build group assignment matrix + sorter = np.array(group_assignment).argsort() + unsorter = np.array(group_assignment).argsort().argsort() + + # Populate a matrix with group assignments + upper_left = np.full((groups[group_labels[0]], + groups[group_labels[0]]), + group_labels[0]) + upper_right = np.full((groups[group_labels[0]], + groups[group_labels[1]]), + np.nan) + lower_left = np.full((groups[group_labels[1]], + groups[group_labels[0]]), + np.nan) + lower_right = np.full((groups[group_labels[1]], + groups[group_labels[1]]), + group_labels[1]) + group_matrix = np.vstack((np.hstack((upper_left, upper_right)), + np.hstack((lower_left, lower_right)))) + np.fill_diagonal(group_matrix, np.nan) + + # Unsort matrix and squareform to create selector + group_selector = squareform(group_matrix[unsorter, :][:, unsorter], + checks=False) + + # If leave-one-out approach, just user group assignment as selector + elif not pairwise: + group_selector = group_assignment + + # Manage one-sample and incorrect group assignments + elif not group_assignment or len(np.unique(group_assignment)) == 1: + n_groups = 1 + + # If pairwise initialize matrix of ones for sign-flipping + ones_matrix = np.ones((n_subjects, n_subjects)) + + elif len(np.unique(group_assignment)) > 2: + raise ValueError("This test is not valid for more than " + "2 groups! (got {0})".format( + len(np.unique(group_assignment)))) + else: + raise ValueError("Invalid group assignments!") + + # Set up permutation type (exact or Monte Carlo) + if n_groups == 1: + if n_permutations < 2**n_subjects: + logger.info("One-sample approximate permutation test using " + "sign-flipping procedure with Monte Carlo resampling.") + exact_permutations = None + elif n_permutations >= 2**n_subjects: + logger.info("One-sample exact permutation test using " + "sign-flipping procedure with 2**{0} " + "({1}) iterations.".format(n_subjects, + 2**n_subjects)) + exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) + n_permutations = 2**n_subjects + elif n_groups == 2: + if n_permutations < np.math.factorial(n_subjects): + logger.info("Two-sample approximate permutation test using " + "group randomization with Monte Carlo resampling.") + exact_permutations = None + elif n_permutations >= np.math.factorial(n_subjects): + logger.info("Two-sample exact permutation test using group " + "randomization with {0}! " + "({1}) iterations.".format( + n_subjects, + np.math.factorial(n_subjects))) + exact_permutations = list(it.permutations( + np.arange(len(group_assignment)))) + n_permutations = np.math.factorial(n_subjects) + + # If one group, just get observed summary statistic + if n_groups == 1: + observed = compute_summary_statistic( + iscs, + summary_statistic=summary_statistic, + axis=0)[np.newaxis, :] + + # If two groups, get the observed difference + elif n_groups == 2: + observed = (compute_summary_statistic( + iscs[group_selector == group_labels[0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic( + iscs[group_selector == group_labels[1], :], + summary_statistic=summary_statistic, + axis=0)) + observed = np.array(observed)[np.newaxis, :] + + # Set up an empty list to build our permutation distribution + distribution = [] + + # Loop through n permutation iterations and populate distribution + for i in np.arange(n_permutations): + + # Random seed to be deterministically re-randomized at each iteration + if exact_permutations: + pass + elif isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # If one group, apply sign-flipping procedure + if n_groups == 1: + + # Randomized sign-flips + if exact_permutations: + sign_flipper = np.array(exact_permutations[i]) + elif not exact_permutations: + sign_flipper = prng.choice([-1, 1], size=n_subjects, + replace=True) + + # If pairwise, apply sign-flips by rows and columns + if pairwise: + matrix_flipped = (ones_matrix * sign_flipper + * sign_flipper[:, np.newaxis]) + sign_flipper = squareform(matrix_flipped, checks=False) + + # Apply flips along ISC axis (same across voxels) + isc_flipped = iscs * sign_flipper[:, np.newaxis] + + # Get summary statistics on sign-flipped ISCs + isc_sample = compute_summary_statistic( + isc_flipped, + summary_statistic=summary_statistic, + axis=0) + + # If two groups, set up group matrix get the observed difference + elif n_groups == 2: + + # Shuffle the group assignments + if exact_permutations: + group_shuffler = np.array(exact_permutations[i]) + elif not exact_permutations and pairwise: + group_shuffler = prng.permutation(np.arange( + len(np.array(group_assignment)[sorter]))) + elif not exact_permutations and not pairwise: + group_shuffler = prng.permutation(np.arange( + len(group_assignment))) + + # If pairwise approach, convert group assignments to matrix + if pairwise: + + # Apply shuffler to group matrix rows/columns + group_shuffled = group_matrix[ + group_shuffler, :][:, group_shuffler] + + # Unsort shuffled matrix and squareform to create selector + group_selector = squareform( + group_shuffled[unsorter, :][:, unsorter], + checks=False) + + # Shuffle group assignments in leave-one-out two sample test + elif not pairwise: + + # Apply shuffler to group matrix rows/columns + group_selector = np.array(group_assignment)[group_shuffler] + + # Get difference of within-group summary statistics + # with group permutation + isc_sample = (compute_summary_statistic( + iscs[group_selector == group_labels[0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic( + iscs[group_selector == group_labels[1], :], + summary_statistic=summary_statistic, + axis=0)) + + # Tack our permuted ISCs onto the permutation distribution + distribution.append(isc_sample) + + # Update random state for next iteration + if not exact_permutations: + random_state = np.random.RandomState(prng.randint( + 0, MAX_RANDOM_SEED)) + + # Convert distribution to numpy array + distribution = np.array(distribution) + assert distribution.shape == (n_permutations, n_voxels) + + # Get p-value for actual median from shifted distribution + if exact_permutations: + p = compute_p_from_null_distribution(observed, distribution, + side='two-sided', exact=True, + axis=0) + elif not exact_permutations: + p = compute_p_from_null_distribution(observed, distribution, + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] + + return observed, p, distribution + + +def timeshift_isc(data, pairwise=False, summary_statistic='median', + n_shifts=1000, random_state=None): + + """Circular time-shift randomization for one-sample ISC test + + For each voxel or ROI, compute the actual ISC and p-values + from a null distribution of ISCs where response time series + are first circularly shifted by random intervals. If pairwise, + apply time-shift randomization to each subjects and compute pairwise + ISCs. If leave-one-out approach is used (pairwise=False), apply + the random time-shift to only the left-out subject in each iteration + of the leave-one-out procedure. Input data should be a list where + each item is a time-points by voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a single ndarray is + supplied, the last dimension is assumed to correspond to subjects. + Returns the observed ISC and p-values (two-tailed test), as well as + the null distribution of ISCs computed on randomly time-shifted data. + + This implementation is based on the following publications: + + .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic + responses during watching a movie: localization in space and + frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, + 2010, Frontiers in Neuroinformatics, 4, 5. + + .. [Kauppi2014] "A versatile software package for inter-subject + correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, + J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. + + Parameters + ---------- + data : list or ndarray (n_TRs x n_voxels x n_subjects) + fMRI data for which to compute ISFC + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + n_shifts : int, default:1000 + Number of randomly shifted samples + + random_state = int, None, or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + observed : float, observed ISC (without time-shifting) + Actual ISCs + + p : float, p-value + p-value based on time-shifting randomization test + + distribution : ndarray, time-shifts by voxels (optional) + Time-shifted null distribution if return_bootstrap=True + """ + + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) + + # Get actual observed ISC + observed = isc(data, pairwise=pairwise, + summary_statistic=summary_statistic) + + # Roll axis to get subjects in first dimension for loop + if pairwise: + data = np.rollaxis(data, 2, 0) + + # Iterate through randomized shifts to create null distribution + distribution = [] + for i in np.arange(n_shifts): + + # Random seed to be deterministically re-randomized at each iteration + if isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # Get a random set of shifts based on number of TRs, + shifts = prng.choice(np.arange(n_TRs), size=n_subjects, + replace=True) + + # In pairwise approach, apply all shifts then compute pairwise ISCs + if pairwise: + + # Apply circular shift to each subject's time series + shifted_data = [] + for subject, shift in zip(data, shifts): + shifted_data.append(np.concatenate( + (subject[-shift:, :], + subject[:-shift, :]))) + shifted_data = np.dstack(shifted_data) + + # Compute null ISC on shifted data for pairwise approach + shifted_isc = isc(shifted_data, pairwise=pairwise, + summary_statistic=summary_statistic) + + # In leave-one-out, apply shift only to each left-out participant + elif not pairwise: + + shifted_isc = [] + for s, shift in enumerate(shifts): + shifted_subject = np.concatenate((data[-shift:, :, s], + data[:-shift, :, s])) + nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) + loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), + pairwise=False, + summary_statistic=None) + shifted_isc.append(loo_isc) + + # Get summary statistics across left-out subjects + shifted_isc = compute_summary_statistic( + np.dstack(shifted_isc), + summary_statistic=summary_statistic, + axis=2) + + distribution.append(shifted_isc) + + # Update random state for next iteration + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) + + # Convert distribution to numpy array + distribution = np.vstack(distribution) + assert distribution.shape == (n_shifts, n_voxels) + + # Get p-value for actual median from shifted distribution + p = compute_p_from_null_distribution(observed, distribution, + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] + + return observed, p, distribution + + +def phaseshift_isc(data, pairwise=False, summary_statistic='median', + n_shifts=1000, random_state=None): + + """Phase randomization for one-sample ISC test + + For each voxel or ROI, compute the actual ISC and p-values + from a null distribution of ISCs where response time series + are phase randomized prior to computing ISC. If pairwise, + apply phase randomization to each subject and compute pairwise + ISCs. If leave-one-out approach is used (pairwise=False), only + apply phase randomization to the left-out subject in each iteration + of the leave-one-out procedure. Input data should be a list where + each item is a time-points by voxels ndarray for a given subject. + Multiple input ndarrays must be the same shape. If a single ndarray is + supplied, the last dimension is assumed to correspond to subjects. + Returns the observed ISC and p-values (two-tailed test), as well as + the null distribution of ISCs computed on phase-randomized data. + + This implementation is based on the following publications: + + .. [Lerner2011] "Topographic mapping of a hierarchy of temporal + receptive windows using a narrated story.", Y. Lerner, C. J. Honey, + L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. + + .. [Simony2016] "Dynamic reconfiguration of the default mode network + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. + + Parameters + ---------- + data : list or ndarray (n_TRs x n_voxels x n_subjects) + fMRI data for which to compute ISFC + + pairwise : bool, default: False + Whether to use pairwise (True) or leave-one-out (False) approach + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + n_shifts : int, default:1000 + Number of randomly shifted samples + + random_state = int, None, or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + observed : float, observed ISC (without time-shifting) + Actual ISCs + + p : float, p-value + p-value based on time-shifting randomization test + + distribution : ndarray, time-shifts by voxels (optional) + Time-shifted null distribution if return_bootstrap=True + """ + + # Check response time series input format + data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) + + # Get actual observed ISC + observed = isc(data, pairwise=pairwise, + summary_statistic=summary_statistic) + + # Iterate through randomized shifts to create null distribution + distribution = [] + for i in np.arange(n_shifts): + + # Random seed to be deterministically re-randomized at each iteration + if isinstance(random_state, np.random.RandomState): + prng = random_state + else: + prng = np.random.RandomState(random_state) + + # Get randomized phase shifts + if n_TRs % 2 == 0: + # Why are we indexing from 1 not zero here? n_TRs / -1 long? + pos_freq = np.arange(1, data.shape[0] // 2) + neg_freq = np.arange(data.shape[0] - 1, data.shape[0] // 2, -1) + else: + pos_freq = np.arange(1, (data.shape[0] - 1) // 2 + 1) + neg_freq = np.arange(data.shape[0] - 1, + (data.shape[0] - 1) // 2, -1) + + phase_shifts = prng.rand(len(pos_freq), 1, n_subjects) * 2 * np.math.pi + + # In pairwise approach, apply all shifts then compute pairwise ISCs + if pairwise: + + # Fast Fourier transform along time dimension of data + fft_data = fft(data, axis=0) + + # Shift pos and neg frequencies symmetrically, to keep signal real + fft_data[pos_freq, :, :] *= np.exp(1j * phase_shifts) + fft_data[neg_freq, :, :] *= np.exp(-1j * phase_shifts) + + # Inverse FFT to put data back in time domain for ISC + shifted_data = np.real(ifft(fft_data, axis=0)) + + # Compute null ISC on shifted data for pairwise approach + shifted_isc = isc(shifted_data, pairwise=True, + summary_statistic=summary_statistic) + + # In leave-one-out, apply shift only to each left-out participant + elif not pairwise: + + # Roll subject axis in phaseshifts for loop + phase_shifts = np.rollaxis(phase_shifts, 2, 0) + + shifted_isc = [] + for s, shift in enumerate(phase_shifts): + + # Apply FFT to left-out subject + fft_subject = fft(data[:, :, s], axis=0) + + # Shift pos and neg frequencies symmetrically, keep signal real + fft_subject[pos_freq, :] *= np.exp(1j * shift) + fft_subject[neg_freq, :] *= np.exp(-1j * shift) + + # Inverse FFT to put data back in time domain for ISC + shifted_subject = np.real(ifft(fft_subject, axis=0)) + + # ISC of shifted left-out subject vs mean of N-1 subjects + nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) + loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), + pairwise=False, summary_statistic=None) + shifted_isc.append(loo_isc) + + # Get summary statistics across left-out subjects + shifted_isc = compute_summary_statistic( + np.dstack(shifted_isc), + summary_statistic=summary_statistic, axis=2) + distribution.append(shifted_isc) + + # Update random state for next iteration + random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) + + # Convert distribution to numpy array + distribution = np.vstack(distribution) + assert distribution.shape == (n_shifts, n_voxels) + + # Get p-value for actual median from shifted distribution + p = compute_p_from_null_distribution(observed, distribution, + side='two-sided', exact=False, + axis=0) + + # Reshape p-values to fit with data shape + p = p[np.newaxis, :] + + return observed, p, distribution diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py new file mode 100644 index 000000000..c556f41cf --- /dev/null +++ b/tests/isfc/test_isfc.py @@ -0,0 +1,556 @@ +import numpy as np +import logging +from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_isc, + timeshift_isc, phaseshift_isc) +from scipy.spatial.distance import squareform + +logger = logging.getLogger(__name__) + + +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data + + +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + correlated = True + while correlated: + uncorrelated = np.random.randn(n_TRs, + n_subjects)[:, np.newaxis, :] + unc_max = np.amax(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + unc_mean = np.mean(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + if unc_max < .3 and np.abs(unc_mean) < .001: + correlated = False + data = np.repeat(np.column_stack((signal, signal, + ))[..., np.newaxis], 20, axis=2) + data = np.concatenate((data, uncorrelated), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data + + +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC inputs") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + logger.info("Finished testing ISC inputs") + + +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) + + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) + + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic='mean') + assert isc_mean.shape == (1, n_voxels) + + isc_median = isc(data, pairwise=False, summary_statistic='median') + assert isc_median.shape == (1, n_voxels) + + try: + isc_min = isc(data, pairwise=False, summary_statistic='min') + except ValueError: + logger.info("Correctly caught unexpected summary statistic") + + logger.info("Finished testing ISC options") + + +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + logger.info("Testing ISC outputs") + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.all(iscs[:, :2] == 1.) + assert np.all(iscs[:, -1] < 1.) + + logger.info("Finished testing ISC outputs") + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + n_bootstraps = 10 + + logger.info("Testing bootstrap hypothesis test") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Test one-sample bootstrap test with pairwise approach + n_bootstraps = 10 + + iscs = isc(data, pairwise=True, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Check random seeds + iscs = isc(data, pairwise=False, summary_statistic=None) + distributions = [] + for random_state in [42, 42, None]: + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95, + random_state=random_state) + distributions.append(distribution) + assert np.array_equal(distributions[0], distributions[1]) + assert not np.array_equal(distributions[1], distributions[2]) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='median')) + + logger.info("Finished testing bootstrap hypothesis test") + + +# Test permutation test with group assignments +def test_permutation_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + group_assignment = [1] * 10 + [2] * 10 + + logger.info("Testing permutation test") + + # Create dataset with two groups in pairwise approach + data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=5, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=200) + + # Create data with two groups in leave-one-out approach + data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=10, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=200) + + # One-sample leave-one-out permutation test + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic='median', + n_permutations=200) + + # One-sample pairwise permutation test + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=True, + summary_statistic='median', + n_permutations=200) + + # Small one-sample pairwise exact test + data = simulated_timeseries(12, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic='median', + n_permutations=10000) + + # Small two-sample pairwise exact test (and unequal groups) + data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=10000) + + # Small two-sample leave-one-out exact test (and unequal groups) + data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc(iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=10000) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True, + summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='mean')) + + logger.info("Finished testing permutaton test") + + +def test_timeshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing circular time-shift") + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, pairwise + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='mean')) + + logger.info("Finished testing circular time-shift") + + +# Phase randomization test +def test_phaseshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing phase randomization") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Phase randomization one-sample test, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0, 0] < .05 and p[0, 1] < .05 + assert p[0, 2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='mean')) + + logger.info("Finished testing phase randomization") + + +# Test ISFC +def test_isfc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISFC options") + + from brainiak.fcma.util import compute_correlation + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + isfcs = isfc(data, pairwise=False, summary_statistic=None) + + # Just two subjects + isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None) + + # ISFC with pairwise approach + isfcs = isfc(data, pairwise=True, summary_statistic=None) + + # ISFC with summary statistics + isfcs = isfc(data, pairwise=True, summary_statistic='mean') + isfcs = isfc(data, pairwise=True, summary_statistic='median') + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + isfcs = isfc(data, pairwise=False) + assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) + assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) + + isfcs = isfc(data, pairwise=True) + assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) + assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=False) + isfcs = isfc(data, pairwise=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=True) + isfcs = isfc(data, pairwise=True) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + + logger.info("Finished testing ISFC options") + + +if __name__ == '__main__': + test_isc_input() + test_isc_options() + test_isc_output() + test_bootstrap_isc() + test_permutation_isc() + test_timeshift_isc() + test_phaseshift_isc() + test_isfc_options() + logger.info("Finished all ISC tests") From 7a58b310be720690d6984179dde9eae7ef1f781d Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 5 Dec 2018 20:30:49 -0500 Subject: [PATCH 35/43] New smaller permutation_isc functions to reduce complex --- brainiak/isc.py | 434 ++++++++++++++++++++++++++------------- brainiak/isfc.py | 435 +++++++++++++++++++++++++++------------- tests/isc/test_isc.py | 190 +++++++++--------- tests/isfc/test_isfc.py | 188 ++++++++--------- 4 files changed, 774 insertions(+), 473 deletions(-) diff --git a/brainiak/isc.py b/brainiak/isc.py index 18450f0f0..e2d6a0556 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -230,7 +230,9 @@ def check_timeseries_input(data): (e.g., brainiak.image.MaskedMultiSubjectData) or a list where each item is a n_TRs by n_voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a 2D array is supplied, - the last dimension is assumed to correspond to subjects. + the last dimension is assumed to correspond to subjects. This + function is only intended to be used internally by other + functions in this module (e.g., isc, isfc). Parameters ---------- @@ -289,7 +291,9 @@ def check_isc_input(iscs, pairwise=False): Input ISCs should be n_subjects (leave-one-out approach) or n_pairs (pairwise approach) by n_voxels or n_ROIs array or a 1D - array (or list) of ISC values for a single voxel or ROI. + array (or list) of ISC values for a single voxel or ROI. This + function is only intended to be used internally by other + functions in this module (e.g., bootstrap_isc, permutation_isc). Parameters ---------- @@ -329,8 +333,9 @@ def check_isc_input(iscs, pairwise=False): # Infer subjects, voxels and print for user to check n_voxels = iscs.shape[1] - logger.info("Assuming {n_subjects} subjects with and {0} " - "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_voxels)) + logger.info("Assuming {0} subjects with and {1} " + "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_subjects, + n_voxels)) return iscs, n_subjects, n_voxels @@ -541,7 +546,258 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', return observed, ci, p, distribution -def permutation_isc(iscs, group_assignment=None, pairwise=False, +def check_group_assignment(group_assignment, n_subjects): + if type(group_assignment) == list: + pass + elif type(group_assignment) == np.ndarray: + group_assignment = group_assignment.tolist() + else: + logger.info("No group assignment provided, " + "performing one-sample test.") + + if group_assignment and len(group_assignment) != n_subjects: + raise ValueError("Group assignments ({0}) " + "do not match number of subjects ({1})!".format( + len(group_assignment), n_subjects)) + return group_assignment + + +def get_group_parameters(group_assignment, n_subjects, pairwise=False): + + # Set up dictionary to contain group info + group_parameters = {'group_assignment': group_assignment, + 'n_subjects': n_subjects, + 'group_labels': None, 'groups': None, + 'sorter': None, 'unsorter': None, + 'group_matrix': None, 'group_selector': None} + + # Set up group selectors for two-group scenario + if group_assignment and len(np.unique(group_assignment)) == 2: + group_parameters['n_groups'] = 2 + + # Get group labels and counts + group_labels = np.unique(group_assignment) + groups = {group_labels[0]: group_assignment.count(group_labels[0]), + group_labels[1]: group_assignment.count(group_labels[1])} + + # For two-sample pairwise approach set up selector from matrix + if pairwise: + # Sort the group_assignment variable if it came in shuffled + # so it's easier to build group assignment matrix + sorter = np.array(group_assignment).argsort() + unsorter = np.array(group_assignment).argsort().argsort() + + # Populate a matrix with group assignments + upper_left = np.full((groups[group_labels[0]], + groups[group_labels[0]]), + group_labels[0]) + upper_right = np.full((groups[group_labels[0]], + groups[group_labels[1]]), + np.nan) + lower_left = np.full((groups[group_labels[1]], + groups[group_labels[0]]), + np.nan) + lower_right = np.full((groups[group_labels[1]], + groups[group_labels[1]]), + group_labels[1]) + group_matrix = np.vstack((np.hstack((upper_left, upper_right)), + np.hstack((lower_left, lower_right)))) + np.fill_diagonal(group_matrix, np.nan) + group_parameters['group_matrix'] = group_matrix + + # Unsort matrix and squareform to create selector + group_parameters['group_selector'] = squareform( + group_matrix[unsorter, :][:, unsorter], + checks=False) + group_parameters['sorter'] = sorter + group_parameters['unsorter'] = unsorter + + # If leave-one-out approach, just user group assignment as selector + else: + group_parameters['group_selector'] = group_assignment + + # Save these parameters for later + group_parameters['groups'] = groups + group_parameters['group_labels'] = group_labels + + # Manage one-sample and incorrect group assignments + elif not group_assignment or len(np.unique(group_assignment)) == 1: + group_parameters['n_groups'] = 1 + + # If pairwise initialize matrix of ones for sign-flipping + if pairwise: + group_parameters['group_matrix'] = np.ones(( + group_parameters['n_subjects'], + group_parameters['n_subjects'])) + + elif len(np.unique(group_assignment)) > 2: + raise ValueError("This test is not valid for more than " + "2 groups! (got {0})".format( + len(np.unique(group_assignment)))) + else: + raise ValueError("Invalid group assignments!") + + return group_parameters + + +def permute_one_sample_iscs(iscs, group_parameters, i, pairwise=False, + summary_statistic='median', group_matrix=None, + exact_permutations=None, prng=None): + + """Applies one-sample permutations to ISC data + + Input ISCs should be n_subjects (leave-one-out approach) or + n_pairs (pairwise approach) by n_voxels or n_ROIs array. + This function is only intended to be used internally by the + permutation_isc function in this module. + + Parameters + ---------- + iscs : ndarray or list + ISC values + + group_parameters : dict + Dictionary of group parameters + + i : int + Permutation iteration + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs variable + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + exact_permutations : list + List of permutations + + prng = None or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + isc_sample : ndarray + Array of permuted ISC values + + """ + + # Randomized sign-flips + if exact_permutations: + sign_flipper = np.array(exact_permutations[i]) + else: + sign_flipper = prng.choice([-1, 1], + size=group_parameters['n_subjects'], + replace=True) + + # If pairwise, apply sign-flips by rows and columns + if pairwise: + matrix_flipped = (group_parameters['group_matrix'] * sign_flipper + * sign_flipper[ + :, np.newaxis]) + sign_flipper = squareform(matrix_flipped, checks=False) + + # Apply flips along ISC axis (same across voxels) + isc_flipped = iscs * sign_flipper[:, np.newaxis] + + # Get summary statistics on sign-flipped ISCs + isc_sample = compute_summary_statistic( + isc_flipped, + summary_statistic=summary_statistic, + axis=0) + + return isc_sample + + +def permute_two_sample_iscs(iscs, group_parameters, i, pairwise=False, + summary_statistic='median', + exact_permutations=None, prng=None): + + """Applies two-sample permutations to ISC data + + Input ISCs should be n_subjects (leave-one-out approach) or + n_pairs (pairwise approach) by n_voxels or n_ROIs array. + This function is only intended to be used internally by the + permutation_isc function in this module. + + Parameters + ---------- + iscs : ndarray or list + ISC values + + group_parameters : dict + Dictionary of group parameters + + i : int + Permutation iteration + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs variable + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + exact_permutations : list + List of permutations + + prng = None or np.random.RandomState, default:None + Initial random seed + Indicator of pairwise or leave-one-out, should match ISCs variable + + Returns + ------- + isc_sample : ndarray + Array of permuted ISC values + + """ + + # Shuffle the group assignments + if exact_permutations: + group_shuffler = np.array(exact_permutations[i]) + elif not exact_permutations and pairwise: + group_shuffler = prng.permutation(np.arange( + len(np.array(group_parameters['group_assignment'])[ + group_parameters['sorter']]))) + elif not exact_permutations and not pairwise: + group_shuffler = prng.permutation(np.arange( + len(group_parameters['group_assignment']))) + + # If pairwise approach, convert group assignments to matrix + if pairwise: + + # Apply shuffler to group matrix rows/columns + group_shuffled = group_parameters['group_matrix'][ + group_shuffler, :][:, group_shuffler] + + # Unsort shuffled matrix and squareform to create selector + group_selector = squareform(group_shuffled[ + group_parameters['unsorter'], :] + [:, group_parameters['unsorter']], + checks=False) + + # Shuffle group assignments in leave-one-out two sample test + elif not pairwise: + + # Apply shuffler to group matrix rows/columns + group_selector = np.array( + group_parameters['group_assignment'])[group_shuffler] + + # Get difference of within-group summary statistics + # with group permutation + isc_sample = (compute_summary_statistic( + iscs[group_selector == group_parameters[ + 'group_labels'][0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic( + iscs[group_selector == group_parameters[ + 'group_labels'][1], :], + summary_statistic=summary_statistic, + axis=0)) + + return isc_sample + + +def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 summary_statistic='median', n_permutations=1000, random_state=None): @@ -619,93 +875,34 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, raise ValueError("Summary statistic must be 'mean' or 'median'") # Check match between group labels and ISCs - if type(group_assignment) == list: - pass - elif type(group_assignment) == np.ndarray: - group_assignment = group_assignment.tolist() - else: - logger.info("No group assignment provided, " - "performing one-sample test.") - - if group_assignment and len(group_assignment) != n_subjects: - raise ValueError("Group assignments ({0}) " - "do not match number of subjects ({1})!".format( - len(group_assignment), n_subjects)) - - # Set up group selectors for two-group scenario - if group_assignment and len(np.unique(group_assignment)) == 2: - n_groups = 2 - - # Get group labels and counts - group_labels = np.unique(group_assignment) - groups = {group_labels[0]: group_assignment.count(group_labels[0]), - group_labels[1]: group_assignment.count(group_labels[1])} + group_assignment = check_group_assignment(group_assignment, + n_subjects) - # For two-sample pairwise approach set up selector from matrix - if pairwise: - # Sort the group_assignment variable if it came in shuffled - # so it's easier to build group assignment matrix - sorter = np.array(group_assignment).argsort() - unsorter = np.array(group_assignment).argsort().argsort() - - # Populate a matrix with group assignments - upper_left = np.full((groups[group_labels[0]], - groups[group_labels[0]]), - group_labels[0]) - upper_right = np.full((groups[group_labels[0]], - groups[group_labels[1]]), - np.nan) - lower_left = np.full((groups[group_labels[1]], - groups[group_labels[0]]), - np.nan) - lower_right = np.full((groups[group_labels[1]], - groups[group_labels[1]]), - group_labels[1]) - group_matrix = np.vstack((np.hstack((upper_left, upper_right)), - np.hstack((lower_left, lower_right)))) - np.fill_diagonal(group_matrix, np.nan) - - # Unsort matrix and squareform to create selector - group_selector = squareform(group_matrix[unsorter, :][:, unsorter], - checks=False) - - # If leave-one-out approach, just user group assignment as selector - elif not pairwise: - group_selector = group_assignment - - # Manage one-sample and incorrect group assignments - elif not group_assignment or len(np.unique(group_assignment)) == 1: - n_groups = 1 - - # If pairwise initialize matrix of ones for sign-flipping - ones_matrix = np.ones((n_subjects, n_subjects)) - - elif len(np.unique(group_assignment)) > 2: - raise ValueError("This test is not valid for more than " - "2 groups! (got {0})".format( - len(np.unique(group_assignment)))) - else: - raise ValueError("Invalid group assignments!") + # Get group parameters + group_parameters = get_group_parameters(group_assignment, n_subjects, + pairwise=pairwise) # Set up permutation type (exact or Monte Carlo) - if n_groups == 1: + if group_parameters['n_groups'] == 1: if n_permutations < 2**n_subjects: logger.info("One-sample approximate permutation test using " "sign-flipping procedure with Monte Carlo resampling.") exact_permutations = None - elif n_permutations >= 2**n_subjects: + else: logger.info("One-sample exact permutation test using " "sign-flipping procedure with 2**{0} " "({1}) iterations.".format(n_subjects, 2**n_subjects)) exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) n_permutations = 2**n_subjects - elif n_groups == 2: + + # Check for exact test for two groups + else: if n_permutations < np.math.factorial(n_subjects): logger.info("Two-sample approximate permutation test using " "group randomization with Monte Carlo resampling.") exact_permutations = None - elif n_permutations >= np.math.factorial(n_subjects): + else: logger.info("Two-sample exact permutation test using group " "randomization with {0}! " "({1}) iterations.".format( @@ -716,20 +913,22 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, n_permutations = np.math.factorial(n_subjects) # If one group, just get observed summary statistic - if n_groups == 1: + if group_parameters['n_groups'] == 1: observed = compute_summary_statistic( iscs, summary_statistic=summary_statistic, axis=0)[np.newaxis, :] # If two groups, get the observed difference - elif n_groups == 2: + else: observed = (compute_summary_statistic( - iscs[group_selector == group_labels[0], :], + iscs[group_parameters['group_selector'] == + group_parameters['group_labels'][0], :], summary_statistic=summary_statistic, axis=0) - compute_summary_statistic( - iscs[group_selector == group_labels[1], :], + iscs[group_parameters['group_selector'] == + group_parameters['group_labels'][1], :], summary_statistic=summary_statistic, axis=0)) observed = np.array(observed)[np.newaxis, :] @@ -742,78 +941,29 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Random seed to be deterministically re-randomized at each iteration if exact_permutations: - pass + prng = None elif isinstance(random_state, np.random.RandomState): prng = random_state else: prng = np.random.RandomState(random_state) # If one group, apply sign-flipping procedure - if n_groups == 1: - - # Randomized sign-flips - if exact_permutations: - sign_flipper = np.array(exact_permutations[i]) - elif not exact_permutations: - sign_flipper = prng.choice([-1, 1], size=n_subjects, - replace=True) - - # If pairwise, apply sign-flips by rows and columns - if pairwise: - matrix_flipped = (ones_matrix * sign_flipper - * sign_flipper[:, np.newaxis]) - sign_flipper = squareform(matrix_flipped, checks=False) - - # Apply flips along ISC axis (same across voxels) - isc_flipped = iscs * sign_flipper[:, np.newaxis] - - # Get summary statistics on sign-flipped ISCs - isc_sample = compute_summary_statistic( - isc_flipped, + if group_parameters['n_groups'] == 1: + isc_sample = permute_one_sample_iscs( + iscs, group_parameters, i, + pairwise=pairwise, summary_statistic=summary_statistic, - axis=0) + exact_permutations=exact_permutations, + prng=prng) # If two groups, set up group matrix get the observed difference - elif n_groups == 2: - - # Shuffle the group assignments - if exact_permutations: - group_shuffler = np.array(exact_permutations[i]) - elif not exact_permutations and pairwise: - group_shuffler = prng.permutation(np.arange( - len(np.array(group_assignment)[sorter]))) - elif not exact_permutations and not pairwise: - group_shuffler = prng.permutation(np.arange( - len(group_assignment))) - - # If pairwise approach, convert group assignments to matrix - if pairwise: - - # Apply shuffler to group matrix rows/columns - group_shuffled = group_matrix[ - group_shuffler, :][:, group_shuffler] - - # Unsort shuffled matrix and squareform to create selector - group_selector = squareform( - group_shuffled[unsorter, :][:, unsorter], - checks=False) - - # Shuffle group assignments in leave-one-out two sample test - elif not pairwise: - - # Apply shuffler to group matrix rows/columns - group_selector = np.array(group_assignment)[group_shuffler] - - # Get difference of within-group summary statistics - # with group permutation - isc_sample = (compute_summary_statistic( - iscs[group_selector == group_labels[0], :], - summary_statistic=summary_statistic, - axis=0) - - compute_summary_statistic( - iscs[group_selector == group_labels[1], :], + else: + isc_sample = permute_two_sample_iscs( + iscs, group_parameters, i, + pairwise=pairwise, summary_statistic=summary_statistic, - axis=0)) + exact_permutations=exact_permutations, + prng=prng) # Tack our permuted ISCs onto the permutation distribution distribution.append(isc_sample) @@ -832,7 +982,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=True, axis=0) - elif not exact_permutations: + else: p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False, axis=0) diff --git a/brainiak/isfc.py b/brainiak/isfc.py index f170af828..681ef136d 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -42,6 +42,7 @@ warnings.warn("'isfc' module name will be deprecated in an " "upcoming version, use 'isc' instead", DeprecationWarning) + def isc(data, pairwise=False, summary_statistic=None): """Intersubject correlation @@ -234,7 +235,9 @@ def check_timeseries_input(data): (e.g., brainiak.image.MaskedMultiSubjectData) or a list where each item is a n_TRs by n_voxels ndarray for a given subject. Multiple input ndarrays must be the same shape. If a 2D array is supplied, - the last dimension is assumed to correspond to subjects. + the last dimension is assumed to correspond to subjects. This + function is only intended to be used internally by other + functions in this module (e.g., isc, isfc). Parameters ---------- @@ -293,7 +296,9 @@ def check_isc_input(iscs, pairwise=False): Input ISCs should be n_subjects (leave-one-out approach) or n_pairs (pairwise approach) by n_voxels or n_ROIs array or a 1D - array (or list) of ISC values for a single voxel or ROI. + array (or list) of ISC values for a single voxel or ROI. This + function is only intended to be used internally by other + functions in this module (e.g., bootstrap_isc, permutation_isc). Parameters ---------- @@ -333,8 +338,9 @@ def check_isc_input(iscs, pairwise=False): # Infer subjects, voxels and print for user to check n_voxels = iscs.shape[1] - logger.info("Assuming {n_subjects} subjects with and {0} " - "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_voxels)) + logger.info("Assuming {0} subjects with and {1} " + "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_subjects, + n_voxels)) return iscs, n_subjects, n_voxels @@ -545,7 +551,258 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', return observed, ci, p, distribution -def permutation_isc(iscs, group_assignment=None, pairwise=False, +def check_group_assignment(group_assignment, n_subjects): + if type(group_assignment) == list: + pass + elif type(group_assignment) == np.ndarray: + group_assignment = group_assignment.tolist() + else: + logger.info("No group assignment provided, " + "performing one-sample test.") + + if group_assignment and len(group_assignment) != n_subjects: + raise ValueError("Group assignments ({0}) " + "do not match number of subjects ({1})!".format( + len(group_assignment), n_subjects)) + return group_assignment + + +def get_group_parameters(group_assignment, n_subjects, pairwise=False): + + # Set up dictionary to contain group info + group_parameters = {'group_assignment': group_assignment, + 'n_subjects': n_subjects, + 'group_labels': None, 'groups': None, + 'sorter': None, 'unsorter': None, + 'group_matrix': None, 'group_selector': None} + + # Set up group selectors for two-group scenario + if group_assignment and len(np.unique(group_assignment)) == 2: + group_parameters['n_groups'] = 2 + + # Get group labels and counts + group_labels = np.unique(group_assignment) + groups = {group_labels[0]: group_assignment.count(group_labels[0]), + group_labels[1]: group_assignment.count(group_labels[1])} + + # For two-sample pairwise approach set up selector from matrix + if pairwise: + # Sort the group_assignment variable if it came in shuffled + # so it's easier to build group assignment matrix + sorter = np.array(group_assignment).argsort() + unsorter = np.array(group_assignment).argsort().argsort() + + # Populate a matrix with group assignments + upper_left = np.full((groups[group_labels[0]], + groups[group_labels[0]]), + group_labels[0]) + upper_right = np.full((groups[group_labels[0]], + groups[group_labels[1]]), + np.nan) + lower_left = np.full((groups[group_labels[1]], + groups[group_labels[0]]), + np.nan) + lower_right = np.full((groups[group_labels[1]], + groups[group_labels[1]]), + group_labels[1]) + group_matrix = np.vstack((np.hstack((upper_left, upper_right)), + np.hstack((lower_left, lower_right)))) + np.fill_diagonal(group_matrix, np.nan) + group_parameters['group_matrix'] = group_matrix + + # Unsort matrix and squareform to create selector + group_parameters['group_selector'] = squareform( + group_matrix[unsorter, :][:, unsorter], + checks=False) + group_parameters['sorter'] = sorter + group_parameters['unsorter'] = unsorter + + # If leave-one-out approach, just user group assignment as selector + else: + group_parameters['group_selector'] = group_assignment + + # Save these parameters for later + group_parameters['groups'] = groups + group_parameters['group_labels'] = group_labels + + # Manage one-sample and incorrect group assignments + elif not group_assignment or len(np.unique(group_assignment)) == 1: + group_parameters['n_groups'] = 1 + + # If pairwise initialize matrix of ones for sign-flipping + if pairwise: + group_parameters['group_matrix'] = np.ones(( + group_parameters['n_subjects'], + group_parameters['n_subjects'])) + + elif len(np.unique(group_assignment)) > 2: + raise ValueError("This test is not valid for more than " + "2 groups! (got {0})".format( + len(np.unique(group_assignment)))) + else: + raise ValueError("Invalid group assignments!") + + return group_parameters + + +def permute_one_sample_iscs(iscs, group_parameters, i, pairwise=False, + summary_statistic='median', group_matrix=None, + exact_permutations=None, prng=None): + + """Applies one-sample permutations to ISC data + + Input ISCs should be n_subjects (leave-one-out approach) or + n_pairs (pairwise approach) by n_voxels or n_ROIs array. + This function is only intended to be used internally by the + permutation_isc function in this module. + + Parameters + ---------- + iscs : ndarray or list + ISC values + + group_parameters : dict + Dictionary of group parameters + + i : int + Permutation iteration + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs variable + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + exact_permutations : list + List of permutations + + prng = None or np.random.RandomState, default:None + Initial random seed + + Returns + ------- + isc_sample : ndarray + Array of permuted ISC values + + """ + + # Randomized sign-flips + if exact_permutations: + sign_flipper = np.array(exact_permutations[i]) + else: + sign_flipper = prng.choice([-1, 1], + size=group_parameters['n_subjects'], + replace=True) + + # If pairwise, apply sign-flips by rows and columns + if pairwise: + matrix_flipped = (group_parameters['group_matrix'] * sign_flipper + * sign_flipper[ + :, np.newaxis]) + sign_flipper = squareform(matrix_flipped, checks=False) + + # Apply flips along ISC axis (same across voxels) + isc_flipped = iscs * sign_flipper[:, np.newaxis] + + # Get summary statistics on sign-flipped ISCs + isc_sample = compute_summary_statistic( + isc_flipped, + summary_statistic=summary_statistic, + axis=0) + + return isc_sample + + +def permute_two_sample_iscs(iscs, group_parameters, i, pairwise=False, + summary_statistic='median', + exact_permutations=None, prng=None): + + """Applies two-sample permutations to ISC data + + Input ISCs should be n_subjects (leave-one-out approach) or + n_pairs (pairwise approach) by n_voxels or n_ROIs array. + This function is only intended to be used internally by the + permutation_isc function in this module. + + Parameters + ---------- + iscs : ndarray or list + ISC values + + group_parameters : dict + Dictionary of group parameters + + i : int + Permutation iteration + + pairwise : bool, default:False + Indicator of pairwise or leave-one-out, should match ISCs variable + + summary_statistic : str, default:'median' + Summary statistic, either 'median' (default) or 'mean' + + exact_permutations : list + List of permutations + + prng = None or np.random.RandomState, default:None + Initial random seed + Indicator of pairwise or leave-one-out, should match ISCs variable + + Returns + ------- + isc_sample : ndarray + Array of permuted ISC values + + """ + + # Shuffle the group assignments + if exact_permutations: + group_shuffler = np.array(exact_permutations[i]) + elif not exact_permutations and pairwise: + group_shuffler = prng.permutation(np.arange( + len(np.array(group_parameters['group_assignment'])[ + group_parameters['sorter']]))) + elif not exact_permutations and not pairwise: + group_shuffler = prng.permutation(np.arange( + len(group_parameters['group_assignment']))) + + # If pairwise approach, convert group assignments to matrix + if pairwise: + + # Apply shuffler to group matrix rows/columns + group_shuffled = group_parameters['group_matrix'][ + group_shuffler, :][:, group_shuffler] + + # Unsort shuffled matrix and squareform to create selector + group_selector = squareform(group_shuffled[ + group_parameters['unsorter'], :] + [:, group_parameters['unsorter']], + checks=False) + + # Shuffle group assignments in leave-one-out two sample test + elif not pairwise: + + # Apply shuffler to group matrix rows/columns + group_selector = np.array( + group_parameters['group_assignment'])[group_shuffler] + + # Get difference of within-group summary statistics + # with group permutation + isc_sample = (compute_summary_statistic( + iscs[group_selector == group_parameters[ + 'group_labels'][0], :], + summary_statistic=summary_statistic, + axis=0) - + compute_summary_statistic( + iscs[group_selector == group_parameters[ + 'group_labels'][1], :], + summary_statistic=summary_statistic, + axis=0)) + + return isc_sample + + +def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 summary_statistic='median', n_permutations=1000, random_state=None): @@ -623,93 +880,34 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, raise ValueError("Summary statistic must be 'mean' or 'median'") # Check match between group labels and ISCs - if type(group_assignment) == list: - pass - elif type(group_assignment) == np.ndarray: - group_assignment = group_assignment.tolist() - else: - logger.info("No group assignment provided, " - "performing one-sample test.") - - if group_assignment and len(group_assignment) != n_subjects: - raise ValueError("Group assignments ({0}) " - "do not match number of subjects ({1})!".format( - len(group_assignment), n_subjects)) - - # Set up group selectors for two-group scenario - if group_assignment and len(np.unique(group_assignment)) == 2: - n_groups = 2 - - # Get group labels and counts - group_labels = np.unique(group_assignment) - groups = {group_labels[0]: group_assignment.count(group_labels[0]), - group_labels[1]: group_assignment.count(group_labels[1])} + group_assignment = check_group_assignment(group_assignment, + n_subjects) - # For two-sample pairwise approach set up selector from matrix - if pairwise: - # Sort the group_assignment variable if it came in shuffled - # so it's easier to build group assignment matrix - sorter = np.array(group_assignment).argsort() - unsorter = np.array(group_assignment).argsort().argsort() - - # Populate a matrix with group assignments - upper_left = np.full((groups[group_labels[0]], - groups[group_labels[0]]), - group_labels[0]) - upper_right = np.full((groups[group_labels[0]], - groups[group_labels[1]]), - np.nan) - lower_left = np.full((groups[group_labels[1]], - groups[group_labels[0]]), - np.nan) - lower_right = np.full((groups[group_labels[1]], - groups[group_labels[1]]), - group_labels[1]) - group_matrix = np.vstack((np.hstack((upper_left, upper_right)), - np.hstack((lower_left, lower_right)))) - np.fill_diagonal(group_matrix, np.nan) - - # Unsort matrix and squareform to create selector - group_selector = squareform(group_matrix[unsorter, :][:, unsorter], - checks=False) - - # If leave-one-out approach, just user group assignment as selector - elif not pairwise: - group_selector = group_assignment - - # Manage one-sample and incorrect group assignments - elif not group_assignment or len(np.unique(group_assignment)) == 1: - n_groups = 1 - - # If pairwise initialize matrix of ones for sign-flipping - ones_matrix = np.ones((n_subjects, n_subjects)) - - elif len(np.unique(group_assignment)) > 2: - raise ValueError("This test is not valid for more than " - "2 groups! (got {0})".format( - len(np.unique(group_assignment)))) - else: - raise ValueError("Invalid group assignments!") + # Get group parameters + group_parameters = get_group_parameters(group_assignment, n_subjects, + pairwise=pairwise) # Set up permutation type (exact or Monte Carlo) - if n_groups == 1: + if group_parameters['n_groups'] == 1: if n_permutations < 2**n_subjects: logger.info("One-sample approximate permutation test using " "sign-flipping procedure with Monte Carlo resampling.") exact_permutations = None - elif n_permutations >= 2**n_subjects: + else: logger.info("One-sample exact permutation test using " "sign-flipping procedure with 2**{0} " "({1}) iterations.".format(n_subjects, 2**n_subjects)) exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) n_permutations = 2**n_subjects - elif n_groups == 2: + + # Check for exact test for two groups + else: if n_permutations < np.math.factorial(n_subjects): logger.info("Two-sample approximate permutation test using " "group randomization with Monte Carlo resampling.") exact_permutations = None - elif n_permutations >= np.math.factorial(n_subjects): + else: logger.info("Two-sample exact permutation test using group " "randomization with {0}! " "({1}) iterations.".format( @@ -720,20 +918,22 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, n_permutations = np.math.factorial(n_subjects) # If one group, just get observed summary statistic - if n_groups == 1: + if group_parameters['n_groups'] == 1: observed = compute_summary_statistic( iscs, summary_statistic=summary_statistic, axis=0)[np.newaxis, :] # If two groups, get the observed difference - elif n_groups == 2: + else: observed = (compute_summary_statistic( - iscs[group_selector == group_labels[0], :], + iscs[group_parameters['group_selector'] == + group_parameters['group_labels'][0], :], summary_statistic=summary_statistic, axis=0) - compute_summary_statistic( - iscs[group_selector == group_labels[1], :], + iscs[group_parameters['group_selector'] == + group_parameters['group_labels'][1], :], summary_statistic=summary_statistic, axis=0)) observed = np.array(observed)[np.newaxis, :] @@ -746,78 +946,29 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # Random seed to be deterministically re-randomized at each iteration if exact_permutations: - pass + prng = None elif isinstance(random_state, np.random.RandomState): prng = random_state else: prng = np.random.RandomState(random_state) # If one group, apply sign-flipping procedure - if n_groups == 1: - - # Randomized sign-flips - if exact_permutations: - sign_flipper = np.array(exact_permutations[i]) - elif not exact_permutations: - sign_flipper = prng.choice([-1, 1], size=n_subjects, - replace=True) - - # If pairwise, apply sign-flips by rows and columns - if pairwise: - matrix_flipped = (ones_matrix * sign_flipper - * sign_flipper[:, np.newaxis]) - sign_flipper = squareform(matrix_flipped, checks=False) - - # Apply flips along ISC axis (same across voxels) - isc_flipped = iscs * sign_flipper[:, np.newaxis] - - # Get summary statistics on sign-flipped ISCs - isc_sample = compute_summary_statistic( - isc_flipped, + if group_parameters['n_groups'] == 1: + isc_sample = permute_one_sample_iscs( + iscs, group_parameters, i, + pairwise=pairwise, summary_statistic=summary_statistic, - axis=0) + exact_permutations=exact_permutations, + prng=prng) # If two groups, set up group matrix get the observed difference - elif n_groups == 2: - - # Shuffle the group assignments - if exact_permutations: - group_shuffler = np.array(exact_permutations[i]) - elif not exact_permutations and pairwise: - group_shuffler = prng.permutation(np.arange( - len(np.array(group_assignment)[sorter]))) - elif not exact_permutations and not pairwise: - group_shuffler = prng.permutation(np.arange( - len(group_assignment))) - - # If pairwise approach, convert group assignments to matrix - if pairwise: - - # Apply shuffler to group matrix rows/columns - group_shuffled = group_matrix[ - group_shuffler, :][:, group_shuffler] - - # Unsort shuffled matrix and squareform to create selector - group_selector = squareform( - group_shuffled[unsorter, :][:, unsorter], - checks=False) - - # Shuffle group assignments in leave-one-out two sample test - elif not pairwise: - - # Apply shuffler to group matrix rows/columns - group_selector = np.array(group_assignment)[group_shuffler] - - # Get difference of within-group summary statistics - # with group permutation - isc_sample = (compute_summary_statistic( - iscs[group_selector == group_labels[0], :], - summary_statistic=summary_statistic, - axis=0) - - compute_summary_statistic( - iscs[group_selector == group_labels[1], :], + else: + isc_sample = permute_two_sample_iscs( + iscs, group_parameters, i, + pairwise=pairwise, summary_statistic=summary_statistic, - axis=0)) + exact_permutations=exact_permutations, + prng=prng) # Tack our permuted ISCs onto the permutation distribution distribution.append(isc_sample) @@ -836,7 +987,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=True, axis=0) - elif not exact_permutations: + else: p = compute_p_from_null_distribution(observed, distribution, side='two-sided', exact=False, axis=0) diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py index 926bd241d..47da62099 100644 --- a/tests/isc/test_isc.py +++ b/tests/isc/test_isc.py @@ -1,14 +1,14 @@ import numpy as np import logging from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, - timeshift_isc, phaseshift_isc) + timeshift_isc, phaseshift_isc) from scipy.spatial.distance import squareform logger = logging.getLogger(__name__) # Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, noise=1, data_type='array', random_state=None): prng = np.random.RandomState(random_state) @@ -25,7 +25,7 @@ def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, if data_type == 'array': if n_voxels: data = np.dstack(data) - elif not n_voxels: + elif not n_voxels: data = np.column_stack(data) return data @@ -45,8 +45,8 @@ def correlated_timeseries(n_subjects, n_TRs, noise=0, uncorrelated[:, 0, :].T), checks=False)) if unc_max < .3 and np.abs(unc_mean) < .001: correlated = False - data = np.repeat(np.column_stack((signal, signal, - ))[..., np.newaxis], 20, axis=2) + data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], + 20, axis=2) data = np.concatenate((data, uncorrelated), axis=1) data = data + np.random.randn(n_TRs, 3, n_subjects) * noise return data @@ -55,15 +55,15 @@ def correlated_timeseries(n_subjects, n_TRs, noise=0, # Compute ISCs using different input types # List of subjects with one voxel/ROI def test_isc_input(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 - + logger.info("Testing ISC inputs") - + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=None, data_type='list', random_state=random_state) @@ -92,7 +92,7 @@ def test_isc_input(): # Check they're the same assert np.array_equal(iscs_list, iscs_array) - + logger.info("Finished testing ISC inputs") @@ -104,13 +104,13 @@ def test_isc_options(): n_TRs = 60 n_voxels = 30 random_state = 42 - + logger.info("Testing ISC options") - + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', random_state=random_state) - + iscs_loo = isc(data, pairwise=False, summary_statistic=None) assert iscs_loo.shape == (n_subjects, n_voxels) @@ -125,43 +125,43 @@ def test_isc_options(): assert isc_median.shape == (1, n_voxels) try: - isc_min = isc(data, pairwise=False, summary_statistic='min') + isc(data, pairwise=False, summary_statistic='min') except ValueError: logger.info("Correctly caught unexpected summary statistic") - + logger.info("Finished testing ISC options") # Make sure ISC recovers correlations of 1 and less than 1 def test_isc_output(): - + logger.info("Testing ISC outputs") - + data = correlated_timeseries(20, 60, noise=0, random_state=42) iscs = isc(data, pairwise=False) assert np.all(iscs[:, :2] == 1.) assert np.all(iscs[:, -1] < 1.) - + iscs = isc(data, pairwise=True) assert np.all(iscs[:, :2] == 1.) assert np.all(iscs[:, -1] < 1.) - + logger.info("Finished testing ISC outputs") - - + + # Test one-sample bootstrap test def test_bootstrap_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 n_bootstraps = 10 - + logger.info("Testing bootstrap hypothesis test") - + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', random_state=random_state) @@ -187,15 +187,16 @@ def test_bootstrap_isc(): iscs = isc(data, pairwise=False, summary_statistic=None) distributions = [] for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95, - random_state=random_state) + observed, ci, p, distribution = bootstrap_isc( + iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95, + random_state=random_state) distributions.append(distribution) assert np.array_equal(distributions[0], distributions[1]) assert not np.array_equal(distributions[1], distributions[2]) - + # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) @@ -205,41 +206,41 @@ def test_bootstrap_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=False) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=True) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='median')) - + logger.info("Finished testing bootstrap hypothesis test") - - + + # Test permutation test with group assignments def test_permutation_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 group_assignment = [1] * 10 + [2] * 10 - + logger.info("Testing permutation test") # Create dataset with two groups in pairwise approach @@ -251,11 +252,12 @@ def test_permutation_isc(): random_state=4))) iscs = isc(data, pairwise=True, summary_statistic=None) - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=200) + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=200) # Create data with two groups in leave-one-out approach data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, @@ -267,11 +269,12 @@ def test_permutation_isc(): iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), isc(data_2, pairwise=False, summary_statistic=None))) - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=200) + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=200) # One-sample leave-one-out permutation test data = simulated_timeseries(n_subjects, n_TRs, @@ -298,26 +301,26 @@ def test_permutation_isc(): random_state=random_state) iscs = isc(data, pairwise=False, summary_statistic=None) - observed, p, distribution = permutation_isc(iscs, - pairwise=False, + observed, p, distribution = permutation_isc(iscs, pairwise=False, summary_statistic='median', n_permutations=10000) # Small two-sample pairwise exact test (and unequal groups) data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), + noise=1, data_type='array', + random_state=3), simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) + noise=50, data_type='array', + random_state=4))) iscs = isc(data, pairwise=True, summary_statistic=None) group_assignment = [1, 1, 1, 2, 2, 2, 2] - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=10000) + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=10000) # Small two-sample leave-one-out exact test (and unequal groups) data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, @@ -330,12 +333,13 @@ def test_permutation_isc(): isc(data_2, pairwise=False, summary_statistic=None))) group_assignment = [1, 1, 1, 2, 2, 2, 2] - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=10000) - + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=10000) + # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) @@ -345,39 +349,38 @@ def test_permutation_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, p, distribution = permutation_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) observed, p, distribution = permutation_isc(iscs, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) observed, p, distribution = permutation_isc(iscs, pairwise=True, summary_statistic='mean') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) - + logger.info("Finished testing permutaton test") def test_timeshift_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 - random_state = 42 - + logger.info("Testing circular time-shift") # Circular time-shift on one sample, leave-one-out @@ -409,40 +412,39 @@ def test_timeshift_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, p, distribution = timeshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) observed, p, distribution = timeshift_isc(data, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) observed, p, distribution = timeshift_isc(data, pairwise=True, summary_statistic='mean') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) - + logger.info("Finished testing circular time-shift") - + # Phase randomization test def test_phaseshift_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 - random_state = 42 - + logger.info("Testing phase randomization") data = simulated_timeseries(n_subjects, n_TRs, @@ -457,7 +459,7 @@ def test_phaseshift_isc(): observed, p, distribution = phaseshift_isc(data, pairwise=False, summary_statistic='mean', n_shifts=200) - + # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) @@ -467,43 +469,41 @@ def test_phaseshift_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, p, distribution = phaseshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) observed, p, distribution = phaseshift_isc(data, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) observed, p, distribution = phaseshift_isc(data, pairwise=True, summary_statistic='mean') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) - + logger.info("Finished testing phase randomization") -# Test ISFC +# Test ISFC def test_isfc_options(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 - random_state = 42 - + logger.info("Testing ISFC options") - - from brainiak.fcma.util import compute_correlation + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') isfcs = isfc(data, pairwise=False, summary_statistic=None) @@ -524,25 +524,25 @@ def test_isfc_options(): isfcs = isfc(data, pairwise=False) assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - + isfcs = isfc(data, pairwise=True) assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - + # Check that ISC and ISFC diagonal are identical iscs = isc(data, pairwise=False) isfcs = isfc(data, pairwise=False) for s in np.arange(len(iscs)): assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - + # Check that ISC and ISFC diagonal are identical iscs = isc(data, pairwise=True) isfcs = isfc(data, pairwise=True) for s in np.arange(len(iscs)): assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - + logger.info("Finished testing ISFC options") - + if __name__ == '__main__': test_isc_input() diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index c556f41cf..d687811fc 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -8,7 +8,7 @@ # Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, noise=1, data_type='array', random_state=None): prng = np.random.RandomState(random_state) @@ -25,7 +25,7 @@ def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, if data_type == 'array': if n_voxels: data = np.dstack(data) - elif not n_voxels: + elif not n_voxels: data = np.column_stack(data) return data @@ -45,8 +45,8 @@ def correlated_timeseries(n_subjects, n_TRs, noise=0, uncorrelated[:, 0, :].T), checks=False)) if unc_max < .3 and np.abs(unc_mean) < .001: correlated = False - data = np.repeat(np.column_stack((signal, signal, - ))[..., np.newaxis], 20, axis=2) + data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], + 20, axis=2) data = np.concatenate((data, uncorrelated), axis=1) data = data + np.random.randn(n_TRs, 3, n_subjects) * noise return data @@ -55,15 +55,15 @@ def correlated_timeseries(n_subjects, n_TRs, noise=0, # Compute ISCs using different input types # List of subjects with one voxel/ROI def test_isc_input(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 - + logger.info("Testing ISC inputs") - + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=None, data_type='list', random_state=random_state) @@ -92,7 +92,7 @@ def test_isc_input(): # Check they're the same assert np.array_equal(iscs_list, iscs_array) - + logger.info("Finished testing ISC inputs") @@ -104,13 +104,13 @@ def test_isc_options(): n_TRs = 60 n_voxels = 30 random_state = 42 - + logger.info("Testing ISC options") - + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', random_state=random_state) - + iscs_loo = isc(data, pairwise=False, summary_statistic=None) assert iscs_loo.shape == (n_subjects, n_voxels) @@ -125,43 +125,43 @@ def test_isc_options(): assert isc_median.shape == (1, n_voxels) try: - isc_min = isc(data, pairwise=False, summary_statistic='min') + isc(data, pairwise=False, summary_statistic='min') except ValueError: logger.info("Correctly caught unexpected summary statistic") - + logger.info("Finished testing ISC options") # Make sure ISC recovers correlations of 1 and less than 1 def test_isc_output(): - + logger.info("Testing ISC outputs") - + data = correlated_timeseries(20, 60, noise=0, random_state=42) iscs = isc(data, pairwise=False) assert np.all(iscs[:, :2] == 1.) assert np.all(iscs[:, -1] < 1.) - + iscs = isc(data, pairwise=True) assert np.all(iscs[:, :2] == 1.) assert np.all(iscs[:, -1] < 1.) - + logger.info("Finished testing ISC outputs") - - + + # Test one-sample bootstrap test def test_bootstrap_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 n_bootstraps = 10 - + logger.info("Testing bootstrap hypothesis test") - + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array', random_state=random_state) @@ -187,15 +187,16 @@ def test_bootstrap_isc(): iscs = isc(data, pairwise=False, summary_statistic=None) distributions = [] for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95, - random_state=random_state) + observed, ci, p, distribution = bootstrap_isc( + iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95, + random_state=random_state) distributions.append(distribution) assert np.array_equal(distributions[0], distributions[1]) assert not np.array_equal(distributions[1], distributions[2]) - + # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) @@ -205,41 +206,41 @@ def test_bootstrap_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=False) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and bootstrap observed are same iscs = isc(data, pairwise=True) observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='median')) - + logger.info("Finished testing bootstrap hypothesis test") - - + + # Test permutation test with group assignments def test_permutation_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 random_state = 42 group_assignment = [1] * 10 + [2] * 10 - + logger.info("Testing permutation test") # Create dataset with two groups in pairwise approach @@ -251,11 +252,12 @@ def test_permutation_isc(): random_state=4))) iscs = isc(data, pairwise=True, summary_statistic=None) - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=200) + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=200) # Create data with two groups in leave-one-out approach data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, @@ -267,11 +269,12 @@ def test_permutation_isc(): iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), isc(data_2, pairwise=False, summary_statistic=None))) - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=200) + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=200) # One-sample leave-one-out permutation test data = simulated_timeseries(n_subjects, n_TRs, @@ -298,26 +301,26 @@ def test_permutation_isc(): random_state=random_state) iscs = isc(data, pairwise=False, summary_statistic=None) - observed, p, distribution = permutation_isc(iscs, - pairwise=False, + observed, p, distribution = permutation_isc(iscs, pairwise=False, summary_statistic='median', n_permutations=10000) # Small two-sample pairwise exact test (and unequal groups) data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), + noise=1, data_type='array', + random_state=3), simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) + noise=50, data_type='array', + random_state=4))) iscs = isc(data, pairwise=True, summary_statistic=None) group_assignment = [1, 1, 1, 2, 2, 2, 2] - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=10000) + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=10000) # Small two-sample leave-one-out exact test (and unequal groups) data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, @@ -330,12 +333,13 @@ def test_permutation_isc(): isc(data_2, pairwise=False, summary_statistic=None))) group_assignment = [1, 1, 1, 2, 2, 2, 2] - observed, p, distribution = permutation_isc(iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=10000) - + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=10000) + # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) @@ -345,39 +349,38 @@ def test_permutation_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, p, distribution = permutation_isc(iscs, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) observed, p, distribution = permutation_isc(iscs, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) observed, p, distribution = permutation_isc(iscs, pairwise=True, summary_statistic='mean') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) - + logger.info("Finished testing permutaton test") def test_timeshift_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 - random_state = 42 - + logger.info("Testing circular time-shift") # Circular time-shift on one sample, leave-one-out @@ -409,40 +412,39 @@ def test_timeshift_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, p, distribution = timeshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) observed, p, distribution = timeshift_isc(data, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) observed, p, distribution = timeshift_isc(data, pairwise=True, summary_statistic='mean') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) - + logger.info("Finished testing circular time-shift") - + # Phase randomization test def test_phaseshift_isc(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 - random_state = 42 - + logger.info("Testing phase randomization") data = simulated_timeseries(n_subjects, n_TRs, @@ -457,7 +459,7 @@ def test_phaseshift_isc(): observed, p, distribution = phaseshift_isc(data, pairwise=False, summary_statistic='mean', n_shifts=200) - + # Check output p-values data = correlated_timeseries(20, 60, noise=.5, random_state=42) @@ -467,43 +469,41 @@ def test_phaseshift_isc(): assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + iscs = isc(data, pairwise=True) observed, p, distribution = phaseshift_isc(data, pairwise=True) assert np.all(iscs[:, :2] > .5) assert np.all(iscs[:, -1] < .5) assert p[0, 0] < .05 and p[0, 1] < .05 assert p[0, 2] > .01 - + # Check that ISC computation and permutation observed are same iscs = isc(data, pairwise=False) observed, p, distribution = phaseshift_isc(data, pairwise=False, summary_statistic='median') assert np.array_equal(observed, isc(data, pairwise=False, summary_statistic='median')) - + # Check that ISC computation and permuation observed are same iscs = isc(data, pairwise=True) observed, p, distribution = phaseshift_isc(data, pairwise=True, summary_statistic='mean') assert np.array_equal(observed, isc(data, pairwise=True, summary_statistic='mean')) - + logger.info("Finished testing phase randomization") -# Test ISFC +# Test ISFC def test_isfc_options(): - + # Set parameters for toy time series data n_subjects = 20 n_TRs = 60 n_voxels = 30 - random_state = 42 - + logger.info("Testing ISFC options") - - from brainiak.fcma.util import compute_correlation + data = simulated_timeseries(n_subjects, n_TRs, n_voxels=n_voxels, data_type='array') isfcs = isfc(data, pairwise=False, summary_statistic=None) @@ -524,25 +524,25 @@ def test_isfc_options(): isfcs = isfc(data, pairwise=False) assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - + isfcs = isfc(data, pairwise=True) assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - + # Check that ISC and ISFC diagonal are identical iscs = isc(data, pairwise=False) isfcs = isfc(data, pairwise=False) for s in np.arange(len(iscs)): assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - + # Check that ISC and ISFC diagonal are identical iscs = isc(data, pairwise=True) isfcs = isfc(data, pairwise=True) for s in np.arange(len(iscs)): assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) - + logger.info("Finished testing ISFC options") - + if __name__ == '__main__': test_isc_input() From 5bbafe97cbe28c98085f2078745f8a9456a97a61 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 5 Dec 2018 22:32:29 -0500 Subject: [PATCH 36/43] Fixed test_images.py for new MMSD shape --- tests/image/test_image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/image/test_image.py b/tests/image/test_image.py index 7c56f139f..4796bc3f1 100644 --- a/tests/image/test_image.py +++ b/tests/image/test_image.py @@ -34,7 +34,8 @@ def test_from_masked_images(self, masked_images, masked_multi_subject_data): result = MaskedMultiSubjectData.from_masked_images(masked_images, len(masked_images)) - assert np.array_equal(result, masked_multi_subject_data) + assert np.array_equal(np.moveaxis(result, 1, 0), + masked_multi_subject_data) @pytest.fixture From 196b94d333b08cb9f02b47c2bb7dffcb42269dfe Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 5 Dec 2018 22:59:12 -0500 Subject: [PATCH 37/43] Set tolerance for allclose in ISC/ISFC test comparison --- tests/isc/test_isc.py | 4 ++-- tests/isfc/test_isfc.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py index 47da62099..12c49bf81 100644 --- a/tests/isc/test_isc.py +++ b/tests/isc/test_isc.py @@ -533,13 +533,13 @@ def test_isfc_options(): iscs = isc(data, pairwise=False) isfcs = isfc(data, pairwise=False) for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :], rtol=1e-03) # Check that ISC and ISFC diagonal are identical iscs = isc(data, pairwise=True) isfcs = isfc(data, pairwise=True) for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :], rtol=1e-03) logger.info("Finished testing ISFC options") diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py index d687811fc..2c5508446 100644 --- a/tests/isfc/test_isfc.py +++ b/tests/isfc/test_isfc.py @@ -533,13 +533,13 @@ def test_isfc_options(): iscs = isc(data, pairwise=False) isfcs = isfc(data, pairwise=False) for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :], rtol=1e-03) # Check that ISC and ISFC diagonal are identical iscs = isc(data, pairwise=True) isfcs = isfc(data, pairwise=True) for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :]) + assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :], rtol=1e-03) logger.info("Finished testing ISFC options") From d2e00a2182e27d9c587afeb48e72de5bf19da535 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Wed, 5 Dec 2018 23:34:17 -0500 Subject: [PATCH 38/43] Fixed dedent in docstring refs for html rendering --- brainiak/isc.py | 48 ++++++++++++++++++++--------------------- brainiak/isfc.py | 48 ++++++++++++++++++++--------------------- brainiak/utils/utils.py | 6 +++--- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/brainiak/isc.py b/brainiak/isc.py index e2d6a0556..af57a3fd6 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -64,8 +64,8 @@ def isc(data, pairwise=False, summary_statistic=None): The implementation is based on the following publication: .. [Hasson2004] "Intersubject synchronization of cortical activity - during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, - R. Malach, 2004, Science, 303, 1634-1640. + during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, + R. Malach, 2004, Science, 303, 1634-1640. Parameters ---------- @@ -147,9 +147,9 @@ def isfc(data, pairwise=False, summary_statistic=None): The implementation is based on the following publication: .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. Parameters ---------- @@ -351,8 +351,8 @@ def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): The implementation is based on the following publication: .. [SilverDunlap1987] "Averaging corrlelation coefficients: should - Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, - Journal of Applied Psychology, 72, 146-148. + Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, + Journal of Applied Psychology, 72, 146-148. Parameters ---------- @@ -410,12 +410,12 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', The implementation is based on the following publications: .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", - P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. + P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. Parameters ---------- @@ -831,9 +831,9 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 The implementation is based on the following publication: .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. Parameters ---------- @@ -1014,13 +1014,13 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', This implementation is based on the following publications: .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic - responses during watching a movie: localization in space and - frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, - 2010, Frontiers in Neuroinformatics, 4, 5. + responses during watching a movie: localization in space and + frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, + 2010, Frontiers in Neuroinformatics, 4, 5. .. [Kauppi2014] "A versatile software package for inter-subject - correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, - J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. + correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, + J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. Parameters ---------- @@ -1151,13 +1151,13 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', This implementation is based on the following publications: .. [Lerner2011] "Topographic mapping of a hierarchy of temporal - receptive windows using a narrated story.", Y. Lerner, C. J. Honey, - L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. + receptive windows using a narrated story.", Y. Lerner, C. J. Honey, + L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. Parameters ---------- diff --git a/brainiak/isfc.py b/brainiak/isfc.py index 681ef136d..0048dbd05 100644 --- a/brainiak/isfc.py +++ b/brainiak/isfc.py @@ -69,8 +69,8 @@ def isc(data, pairwise=False, summary_statistic=None): The implementation is based on the following publication: .. [Hasson2004] "Intersubject synchronization of cortical activity - during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, - R. Malach, 2004, Science, 303, 1634-1640. + during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, + R. Malach, 2004, Science, 303, 1634-1640. Parameters ---------- @@ -152,9 +152,9 @@ def isfc(data, pairwise=False, summary_statistic=None): The implementation is based on the following publication: .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. Parameters ---------- @@ -356,8 +356,8 @@ def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): The implementation is based on the following publication: .. [SilverDunlap1987] "Averaging corrlelation coefficients: should - Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, - Journal of Applied Psychology, 72, 146-148. + Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, + Journal of Applied Psychology, 72, 146-148. Parameters ---------- @@ -415,12 +415,12 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', The implementation is based on the following publications: .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", - P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. + P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. Parameters ---------- @@ -836,9 +836,9 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 The implementation is based on the following publication: .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. Parameters ---------- @@ -1019,13 +1019,13 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', This implementation is based on the following publications: .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic - responses during watching a movie: localization in space and - frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, - 2010, Frontiers in Neuroinformatics, 4, 5. + responses during watching a movie: localization in space and + frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, + 2010, Frontiers in Neuroinformatics, 4, 5. .. [Kauppi2014] "A versatile software package for inter-subject - correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, - J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. + correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, + J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. Parameters ---------- @@ -1156,13 +1156,13 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', This implementation is based on the following publications: .. [Lerner2011] "Topographic mapping of a hierarchy of temporal - receptive windows using a narrated story.", Y. Lerner, C. J. Honey, - L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. + receptive windows using a narrated story.", Y. Lerner, C. J. Honey, + L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. Parameters ---------- diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index 14effcf51..c0dc36c07 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -853,9 +853,9 @@ def compute_p_from_null_distribution(observed, distribution, The implementation is based on the following publication: .. [PhipsonSmyth2010] "Permutation p-values should never be zero: - calculating exact p-values when permutations are randomly drawn.", - B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics - and Molecular Biology, 9, 1544-6115. + calculating exact p-values when permutations are randomly drawn.", + B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics + and Molecular Biology, 9, 1544-6115. Parameters ---------- From 38530a34dc6611a8fbbe814b107e15cc00c58b27 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 6 Dec 2018 14:54:48 -0500 Subject: [PATCH 39/43] Removed deprecated isfc.py entirely, fixed [Chen2016] --- brainiak/isc.py | 7 +- brainiak/isfc.py | 1288 --------------------------------------- tests/isfc/test_isfc.py | 556 ----------------- 3 files changed, 1 insertion(+), 1850 deletions(-) delete mode 100644 brainiak/isfc.py delete mode 100644 tests/isfc/test_isfc.py diff --git a/brainiak/isc.py b/brainiak/isc.py index af57a3fd6..09f8062cb 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -828,12 +828,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 two-sample tests. This approach may yield inflated FPRs for one-sample tests. - The implementation is based on the following publication: - - .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + The implementation is based on the following publication: [Chen2016]_ Parameters ---------- diff --git a/brainiak/isfc.py b/brainiak/isfc.py deleted file mode 100644 index 0048dbd05..000000000 --- a/brainiak/isfc.py +++ /dev/null @@ -1,1288 +0,0 @@ -# Copyright 2017 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. - -"""Intersubject correlation (ISC) analysis - -Functions for computing intersubject correlation (ISC) and related -analyses (e.g., intersubject funtional correlations; ISFC), as well -as statistical tests designed specifically for ISC analyses. - -""" - -# Authors: Sam Nastase, Christopher Baldassano, Qihong Lu, -# Mai Nguyen, and Mor Regev -# Princeton University, 2018 - -import numpy as np -import logging -import warnings -from scipy.spatial.distance import squareform -from scipy.stats import pearsonr -from scipy.fftpack import fft, ifft -import itertools as it -from brainiak.fcma.util import compute_correlation -from brainiak.utils.utils import compute_p_from_null_distribution - -logger = logging.getLogger(__name__) - -MAX_RANDOM_SEED = 2**32 - 1 - -warnings.simplefilter('always', DeprecationWarning) -warnings.warn("'isfc' module name will be deprecated in an " - "upcoming version, use 'isc' instead", DeprecationWarning) - - -def isc(data, pairwise=False, summary_statistic=None): - """Intersubject correlation - - For each voxel or ROI, compute the Pearson correlation between each - subject's response time series and other subjects' response time series. - If pairwise is False (default), use the leave-one-out approach, where - correlation is computed between each subject and the average of the other - subjects. If pairwise is True, compute correlations between all pairs of - subjects. If summary_statistic is None, return N ISC values for N subjects - (leave-one-out) or N(N-1)/2 ISC values for each pair of N subjects, - corresponding to the upper triangle of the pairwise correlation matrix - (see scipy.spatial.distance.squareform). Alternatively, use either - 'mean' or 'median' to compute summary statistic of ISCs (Fisher Z will - be applied if using mean). Input data should be a n_TRs by n_voxels by - n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) or a list - where each item is a n_TRs by n_voxels ndarray for a given subject. - Multiple input ndarrays must be the same shape. If a 2D array is supplied, - the last dimension is assumed to correspond to subjects. If only two - subjects are supplied, simply compute Pearson correlation (precludes - averaging in leave-one-out approach, and does not apply summary statistic). - Output is an ndarray where the first dimension is the number of subjects - or pairs and the second dimension is the number of voxels (or ROIs). - - The implementation is based on the following publication: - - .. [Hasson2004] "Intersubject synchronization of cortical activity - during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, - R. Malach, 2004, Science, 303, 1634-1640. - - Parameters - ---------- - data : list or ndarray (n_TRs x n_voxels x n_subjects) - fMRI data for which to compute ISC - - pairwise : bool, default:False - Whether to use pairwise (True) or leave-one-out (False) approach - - summary_statistic : None or str, default:None - Return all ISCs or collapse using 'mean' or 'median' - - Returns - ------- - iscs : subjects or pairs by voxels ndarray - ISC for each subject or pair (or summary statistic) per voxel - - """ - - # Check response time series input format - data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - - # No summary statistic if only two subjects - if n_subjects == 2: - logger.info("Only two subjects! Simply computing Pearson correlation.") - summary_statistic = None - - # Loop over each voxel or ROI - voxel_iscs = [] - for v in np.arange(n_voxels): - voxel_data = data[:, v, :].T - if n_subjects == 2: - iscs = pearsonr(voxel_data[0, :], voxel_data[1, :])[0] - elif pairwise: - iscs = squareform(np.corrcoef(voxel_data), checks=False) - elif not pairwise: - iscs = np.array([pearsonr(subject, - np.mean(np.delete(voxel_data, - s, axis=0), - axis=0))[0] - for s, subject in enumerate(voxel_data)]) - voxel_iscs.append(iscs) - iscs = np.column_stack(voxel_iscs) - - # Summarize results (if requested) - if summary_statistic: - iscs = compute_summary_statistic(iscs, - summary_statistic=summary_statistic, - axis=0)[np.newaxis, :] - - return iscs - - -def isfc(data, pairwise=False, summary_statistic=None): - - """Intersubject functional correlation (ISFC) - - For each voxel or ROI, compute the Pearson correlation between each - subject's response time series and other subjects' response time series - for all voxels or ROIs. If pairwise is False (default), use the - leave-one-out approach, where correlation is computed between each - subject and the average of the other subjects. If pairwise is True, - compute correlations between all pairs of subjects. If summary_statistic - is None, return N ISFC values for N subjects (leave-one-out) or N(N-1)/2 - ISFC values for each pair of N subjects, corresponding to the upper - triangle of the correlation matrix (see scipy.spatial.distance.squareform). - Alternatively, use either 'mean' or 'median' to compute summary statistic - of ISFCs (Fisher Z is applied if using mean). Input should be n_TRs by - n_voxels by n_subjects array (e.g., brainiak.image.MaskedMultiSubjectData) - or a list where each item is a n_TRs by n_voxels ndarray per subject. - Multiple input ndarrays must be the same shape. If a 2D array is supplied, - the last dimension is assumed to correspond to subjects. If only two - subjects are supplied, simply compute ISFC between these two subjects - (precludes averaging in leave-one-out approach, and does not apply summary - statistic). Output is n_voxels by n_voxels array if summary_statistic is - supplied; otherwise output is n_voxels by n_voxels by n_subjects (or - n_pairs) array. - - The implementation is based on the following publication: - - .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. - - Parameters - ---------- - data : list or ndarray (n_TRs x n_voxels x n_subjects) - fMRI data for which to compute ISFC - - pairwise : bool, default: False - Whether to use pairwise (True) or leave-one-out (False) approach - - summary_statistic : None or str, default:None - Return all ISFCs or collapse using 'mean' or 'median' - - Returns - ------- - isfcs : subjects or pairs by voxels ndarray - ISFC for each subject or pair (or summary statistic) per voxel - - """ - - # Check response time series input format - data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - - # Handle just two subjects properly - if n_subjects == 2: - isfcs = compute_correlation(np.ascontiguousarray(data[..., 0].T), - np.ascontiguousarray(data[..., 1].T)) - isfcs = (isfcs + isfcs.T) / 2 - assert isfcs.shape == (n_voxels, n_voxels) - summary_statistic = None - logger.info("Only two subjects! Computing ISFC between them.") - - # Compute all pairwise ISFCs - elif pairwise: - isfcs = [] - for pair in it.combinations(np.arange(n_subjects), 2): - isfc_pair = compute_correlation(np.ascontiguousarray( - data[..., pair[0]].T), - np.ascontiguousarray( - data[..., pair[1]].T)) - isfc_pair = (isfc_pair + isfc_pair.T) / 2 - isfcs.append(isfc_pair) - isfcs = np.dstack(isfcs) - assert isfcs.shape == (n_voxels, n_voxels, - n_subjects * (n_subjects - 1) / 2) - - # Compute ISFCs using leave-one-out approach - elif not pairwise: - - # Roll subject axis for loop - data = np.rollaxis(data, 2, 0) - - # Compute leave-one-out ISFCs - isfcs = [compute_correlation(np.ascontiguousarray(subject.T), - np.ascontiguousarray(np.mean( - np.delete(data, s, axis=0), - axis=0).T)) - for s, subject in enumerate(data)] - - # Transpose and average ISFC matrices for both directions - isfcs = np.dstack([(isfc_matrix + isfc_matrix.T) / 2 - for isfc_matrix in isfcs]) - assert isfcs.shape == (n_voxels, n_voxels, n_subjects) - - # Summarize results (if requested) - if summary_statistic: - isfcs = compute_summary_statistic(isfcs, - summary_statistic=summary_statistic, - axis=2) - - return isfcs - - -def check_timeseries_input(data): - - """Checks response time series input data for ISC analysis - - Input data should be a n_TRs by n_voxels by n_subjects ndarray - (e.g., brainiak.image.MaskedMultiSubjectData) or a list where each - item is a n_TRs by n_voxels ndarray for a given subject. Multiple - input ndarrays must be the same shape. If a 2D array is supplied, - the last dimension is assumed to correspond to subjects. This - function is only intended to be used internally by other - functions in this module (e.g., isc, isfc). - - Parameters - ---------- - data : ndarray or list - Time series data - - Returns - ------- - iscs : ndarray - Array of ISC values - - n_TRs : int - Number of time points (TRs) - - n_voxels : int - Number of voxels (or ROIs) - - n_subjects : int - Number of subjects - - """ - - # Convert list input to 3d and check shapes - if type(data) == list: - data_shape = data[0].shape - for i, d in enumerate(data): - if d.shape != data_shape: - raise ValueError("All ndarrays in input list " - "must be the same shape!") - if d.ndim == 1: - data[i] = d[:, np.newaxis] - data = np.dstack(data) - - # Convert input ndarray to 3d and check shape - elif isinstance(data, np.ndarray): - if data.ndim == 2: - data = data[:, np.newaxis, :] - elif data.ndim == 3: - pass - else: - raise ValueError("Input ndarray should have 2 " - "or 3 dimensions (got {0})!".format(data.ndim)) - - # Infer subjects, TRs, voxels and log for user to check - n_TRs, n_voxels, n_subjects = data.shape - logger.info("Assuming {0} subjects with {1} time points " - "and {2} voxel(s) or ROI(s) for ISC analysis.".format( - n_subjects, n_TRs, n_voxels)) - - return data, n_TRs, n_voxels, n_subjects - - -def check_isc_input(iscs, pairwise=False): - - """Checks ISC inputs for statistical tests - - Input ISCs should be n_subjects (leave-one-out approach) or - n_pairs (pairwise approach) by n_voxels or n_ROIs array or a 1D - array (or list) of ISC values for a single voxel or ROI. This - function is only intended to be used internally by other - functions in this module (e.g., bootstrap_isc, permutation_isc). - - Parameters - ---------- - iscs : ndarray or list - ISC values - - Returns - ------- - iscs : ndarray - Array of ISC values - - n_subjects : int - Number of subjects - - n_voxels : int - Number of voxels (or ROIs) - """ - - # Standardize structure of input data - if type(iscs) == list: - iscs = np.array(iscs)[:, np.newaxis] - - elif isinstance(iscs, np.ndarray): - if iscs.ndim == 1: - iscs = iscs[:, np.newaxis] - - # Check if incoming pairwise matrix is vectorized triangle - if pairwise: - try: - test_square = squareform(iscs[:, 0]) - n_subjects = test_square.shape[0] - except ValueError: - raise ValueError("For pairwise input, ISCs must be the " - "vectorized triangle of a square matrix.") - elif not pairwise: - n_subjects = iscs.shape[0] - - # Infer subjects, voxels and print for user to check - n_voxels = iscs.shape[1] - logger.info("Assuming {0} subjects with and {1} " - "voxel(s) or ROI(s) in bootstrap ISC test.".format(n_subjects, - n_voxels)) - - return iscs, n_subjects, n_voxels - - -def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): - - """Computes summary statistics for ISCs - - Computes either the 'mean' or 'median' across a set of ISCs. In the - case of the mean, ISC values are first Fisher Z transformed (arctanh), - averaged, then inverse Fisher Z transformed (tanh). - - The implementation is based on the following publication: - - .. [SilverDunlap1987] "Averaging corrlelation coefficients: should - Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, - Journal of Applied Psychology, 72, 146-148. - - Parameters - ---------- - iscs : list or ndarray - ISC values - - summary_statistic : str, default:'mean' - Summary statistic, 'mean' or 'median' - - axis : None or int or tuple of ints, optional - Axis or axes along which the means are computed. The default is to - compute the mean of the flattened array. - - Returns - ------- - statistic : float or ndarray - Summary statistic of ISC values - - """ - - if summary_statistic not in ('mean', 'median'): - raise ValueError("Summary statistic must be 'mean' or 'median'") - - # Compute summary statistic - if summary_statistic == 'mean': - statistic = np.tanh(np.nanmean(np.arctanh(iscs), axis=axis)) - elif summary_statistic == 'median': - statistic = np.nanmedian(iscs, axis=axis) - - return statistic - - -def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', - n_bootstraps=1000, ci_percentile=95, random_state=None): - - """One-sample group-level bootstrap hypothesis test for ISCs - - For ISCs from one more voxels or ROIs, resample subjects with replacement - to construct a bootstrap distribution. Input is a list or ndarray of - ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. ISC values - should be either N ISC values for N subjects in the leave-one-out appraoch - (pairwise=False), N(N-1)/2 ISC values for N subjects in the pairwise - approach (pairwise=True). In the pairwise approach, ISC values should - correspond to the vectorized upper triangle of a square corrlation matrix - (see scipy.stats.distance.squareform). Shifts bootstrap distribution by - actual summary statistic (effectively to zero) for two-tailed null - hypothesis test (Hall & Wilson, 1991). Uses subject-wise (not pair-wise) - resampling in the pairwise approach. Returns the observed ISC, the - confidence interval, and a p-value for the bootstrap hypothesis test, as - well as the bootstrap distribution of summary statistics. According to - Chen et al., 2016, this is the preferred nonparametric approach for - controlling false positive rates (FPR) for one-sample tests in the pairwise - approach. - - The implementation is based on the following publications: - - .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. - - .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", - P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. - - Parameters - ---------- - iscs : list or ndarray, ISCs by voxels array - ISC values for one or more voxels - - pairwise : bool, default:False - Indicator of pairwise or leave-one-out, should match ISCs structure - - summary_statistic : str, default:'median' - Summary statistic, either 'median' (default) or 'mean' - - n_bootstraps : int, default:1000 - Number of bootstrap samples (subject-level with replacement) - - ci_percentile : int, default:95 - Percentile for computing confidence intervals - - random_state = int or None, default:None - Initial random seed - - Returns - ------- - observed : float, median (or mean) ISC value - Summary statistic for actual ISCs - - ci : tuple, bootstrap confidence intervals - Confidence intervals generated from bootstrap distribution - - p : float, p-value - p-value based on bootstrap hypothesis test - - distribution : ndarray, bootstraps by voxels (optional) - Bootstrap distribution if return_bootstrap=True - - """ - - # Standardize structure of input data - iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) - - # Check for valid summary statistic - if summary_statistic not in ('mean', 'median'): - raise ValueError("Summary statistic must be 'mean' or 'median'") - - # Compute summary statistic for observed ISCs - observed = compute_summary_statistic(iscs, - summary_statistic=summary_statistic, - axis=0)[np.newaxis, :] - - # Set up an empty list to build our bootstrap distribution - distribution = [] - - # Loop through n bootstrap iterations and populate distribution - for i in np.arange(n_bootstraps): - - # Random seed to be deterministically re-randomized at each iteration - if isinstance(random_state, np.random.RandomState): - prng = random_state - else: - prng = np.random.RandomState(random_state) - - # Randomly sample subject IDs with replacement - subject_sample = sorted(prng.choice(np.arange(n_subjects), - size=n_subjects)) - - # Squareform and shuffle rows/columns of pairwise ISC matrix to - # to retain correlation structure among ISCs, then get triangle - if pairwise: - - # Loop through voxels - isc_sample = [] - for voxel_iscs in iscs.T: - - # Square the triangle and fill diagonal - voxel_iscs = squareform(voxel_iscs) - np.fill_diagonal(voxel_iscs, 1) - - # Check that pairwise ISC matrix is square and symmetric - assert voxel_iscs.shape[0] == voxel_iscs.shape[1] - assert np.allclose(voxel_iscs, voxel_iscs.T) - - # Shuffle square correlation matrix and get triangle - voxel_sample = voxel_iscs[subject_sample, :][:, subject_sample] - voxel_sample = squareform(voxel_sample, checks=False) - - # Censor off-diagonal 1s for same-subject pairs - voxel_sample[voxel_sample == 1.] = np.NaN - - isc_sample.append(voxel_sample) - - isc_sample = np.column_stack(isc_sample) - - # Get simple bootstrap sample if not pairwise - elif not pairwise: - isc_sample = iscs[subject_sample, :] - - # Compute summary statistic for bootstrap ISCs per voxel - # (alternatively could construct distribution for all voxels - # then compute statistics, but larger memory footprint) - distribution.append(compute_summary_statistic( - isc_sample, - summary_statistic=summary_statistic, - axis=0)) - - # Update random state for next iteration - random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - - # Convert distribution to numpy array - distribution = np.array(distribution) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Compute CIs of median from bootstrap distribution (default: 95%) - ci = (np.percentile(distribution, (100 - ci_percentile)/2, axis=0), - np.percentile(distribution, ci_percentile + (100 - ci_percentile)/2, - axis=0)) - - # Shift bootstrap distribution to 0 for hypothesis test - shifted = distribution - observed - - # Get p-value for actual median from shifted distribution - p = compute_p_from_null_distribution(observed, shifted, - side='two-sided', exact=False, - axis=0) - - # Reshape p-values to fit with data shape - p = p[np.newaxis, :] - - return observed, ci, p, distribution - - -def check_group_assignment(group_assignment, n_subjects): - if type(group_assignment) == list: - pass - elif type(group_assignment) == np.ndarray: - group_assignment = group_assignment.tolist() - else: - logger.info("No group assignment provided, " - "performing one-sample test.") - - if group_assignment and len(group_assignment) != n_subjects: - raise ValueError("Group assignments ({0}) " - "do not match number of subjects ({1})!".format( - len(group_assignment), n_subjects)) - return group_assignment - - -def get_group_parameters(group_assignment, n_subjects, pairwise=False): - - # Set up dictionary to contain group info - group_parameters = {'group_assignment': group_assignment, - 'n_subjects': n_subjects, - 'group_labels': None, 'groups': None, - 'sorter': None, 'unsorter': None, - 'group_matrix': None, 'group_selector': None} - - # Set up group selectors for two-group scenario - if group_assignment and len(np.unique(group_assignment)) == 2: - group_parameters['n_groups'] = 2 - - # Get group labels and counts - group_labels = np.unique(group_assignment) - groups = {group_labels[0]: group_assignment.count(group_labels[0]), - group_labels[1]: group_assignment.count(group_labels[1])} - - # For two-sample pairwise approach set up selector from matrix - if pairwise: - # Sort the group_assignment variable if it came in shuffled - # so it's easier to build group assignment matrix - sorter = np.array(group_assignment).argsort() - unsorter = np.array(group_assignment).argsort().argsort() - - # Populate a matrix with group assignments - upper_left = np.full((groups[group_labels[0]], - groups[group_labels[0]]), - group_labels[0]) - upper_right = np.full((groups[group_labels[0]], - groups[group_labels[1]]), - np.nan) - lower_left = np.full((groups[group_labels[1]], - groups[group_labels[0]]), - np.nan) - lower_right = np.full((groups[group_labels[1]], - groups[group_labels[1]]), - group_labels[1]) - group_matrix = np.vstack((np.hstack((upper_left, upper_right)), - np.hstack((lower_left, lower_right)))) - np.fill_diagonal(group_matrix, np.nan) - group_parameters['group_matrix'] = group_matrix - - # Unsort matrix and squareform to create selector - group_parameters['group_selector'] = squareform( - group_matrix[unsorter, :][:, unsorter], - checks=False) - group_parameters['sorter'] = sorter - group_parameters['unsorter'] = unsorter - - # If leave-one-out approach, just user group assignment as selector - else: - group_parameters['group_selector'] = group_assignment - - # Save these parameters for later - group_parameters['groups'] = groups - group_parameters['group_labels'] = group_labels - - # Manage one-sample and incorrect group assignments - elif not group_assignment or len(np.unique(group_assignment)) == 1: - group_parameters['n_groups'] = 1 - - # If pairwise initialize matrix of ones for sign-flipping - if pairwise: - group_parameters['group_matrix'] = np.ones(( - group_parameters['n_subjects'], - group_parameters['n_subjects'])) - - elif len(np.unique(group_assignment)) > 2: - raise ValueError("This test is not valid for more than " - "2 groups! (got {0})".format( - len(np.unique(group_assignment)))) - else: - raise ValueError("Invalid group assignments!") - - return group_parameters - - -def permute_one_sample_iscs(iscs, group_parameters, i, pairwise=False, - summary_statistic='median', group_matrix=None, - exact_permutations=None, prng=None): - - """Applies one-sample permutations to ISC data - - Input ISCs should be n_subjects (leave-one-out approach) or - n_pairs (pairwise approach) by n_voxels or n_ROIs array. - This function is only intended to be used internally by the - permutation_isc function in this module. - - Parameters - ---------- - iscs : ndarray or list - ISC values - - group_parameters : dict - Dictionary of group parameters - - i : int - Permutation iteration - - pairwise : bool, default:False - Indicator of pairwise or leave-one-out, should match ISCs variable - - summary_statistic : str, default:'median' - Summary statistic, either 'median' (default) or 'mean' - - exact_permutations : list - List of permutations - - prng = None or np.random.RandomState, default:None - Initial random seed - - Returns - ------- - isc_sample : ndarray - Array of permuted ISC values - - """ - - # Randomized sign-flips - if exact_permutations: - sign_flipper = np.array(exact_permutations[i]) - else: - sign_flipper = prng.choice([-1, 1], - size=group_parameters['n_subjects'], - replace=True) - - # If pairwise, apply sign-flips by rows and columns - if pairwise: - matrix_flipped = (group_parameters['group_matrix'] * sign_flipper - * sign_flipper[ - :, np.newaxis]) - sign_flipper = squareform(matrix_flipped, checks=False) - - # Apply flips along ISC axis (same across voxels) - isc_flipped = iscs * sign_flipper[:, np.newaxis] - - # Get summary statistics on sign-flipped ISCs - isc_sample = compute_summary_statistic( - isc_flipped, - summary_statistic=summary_statistic, - axis=0) - - return isc_sample - - -def permute_two_sample_iscs(iscs, group_parameters, i, pairwise=False, - summary_statistic='median', - exact_permutations=None, prng=None): - - """Applies two-sample permutations to ISC data - - Input ISCs should be n_subjects (leave-one-out approach) or - n_pairs (pairwise approach) by n_voxels or n_ROIs array. - This function is only intended to be used internally by the - permutation_isc function in this module. - - Parameters - ---------- - iscs : ndarray or list - ISC values - - group_parameters : dict - Dictionary of group parameters - - i : int - Permutation iteration - - pairwise : bool, default:False - Indicator of pairwise or leave-one-out, should match ISCs variable - - summary_statistic : str, default:'median' - Summary statistic, either 'median' (default) or 'mean' - - exact_permutations : list - List of permutations - - prng = None or np.random.RandomState, default:None - Initial random seed - Indicator of pairwise or leave-one-out, should match ISCs variable - - Returns - ------- - isc_sample : ndarray - Array of permuted ISC values - - """ - - # Shuffle the group assignments - if exact_permutations: - group_shuffler = np.array(exact_permutations[i]) - elif not exact_permutations and pairwise: - group_shuffler = prng.permutation(np.arange( - len(np.array(group_parameters['group_assignment'])[ - group_parameters['sorter']]))) - elif not exact_permutations and not pairwise: - group_shuffler = prng.permutation(np.arange( - len(group_parameters['group_assignment']))) - - # If pairwise approach, convert group assignments to matrix - if pairwise: - - # Apply shuffler to group matrix rows/columns - group_shuffled = group_parameters['group_matrix'][ - group_shuffler, :][:, group_shuffler] - - # Unsort shuffled matrix and squareform to create selector - group_selector = squareform(group_shuffled[ - group_parameters['unsorter'], :] - [:, group_parameters['unsorter']], - checks=False) - - # Shuffle group assignments in leave-one-out two sample test - elif not pairwise: - - # Apply shuffler to group matrix rows/columns - group_selector = np.array( - group_parameters['group_assignment'])[group_shuffler] - - # Get difference of within-group summary statistics - # with group permutation - isc_sample = (compute_summary_statistic( - iscs[group_selector == group_parameters[ - 'group_labels'][0], :], - summary_statistic=summary_statistic, - axis=0) - - compute_summary_statistic( - iscs[group_selector == group_parameters[ - 'group_labels'][1], :], - summary_statistic=summary_statistic, - axis=0)) - - return isc_sample - - -def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 - summary_statistic='median', n_permutations=1000, - random_state=None): - - """Group-level permutation test for ISCs - - For ISCs from one or more voxels or ROIs, permute group assignments to - construct a permutation distribution. Input is a list or ndarray of - ISCs for a single voxel/ROI, or an ISCs-by-voxels ndarray. If two groups, - ISC values should stacked along first dimension (vertically), and a - group_assignment list (or 1d array) of same length as the number of - subjects should be provided to indicate groups. If no group_assignment - is provided, one-sample test is performed using a sign-flipping procedure. - Performs exact test if number of possible permutations (2**N for one-sample - sign-flipping, N! for two-sample shuffling) is less than or equal to number - of requested permutation; otherwise, performs approximate permutation test - using Monte Carlo resampling. ISC values should either be N ISC values for - N subjects in the leave-one-out approach (pairwise=False) or N(N-1)/2 ISC - values for N subjects in the pairwise approach (pairwise=True). In the - pairwise approach, ISC values should correspond to the vectorized upper - triangle of a square corrlation matrix (scipy.stats.distance.squareform). - Note that in the pairwise approach, group_assignment order should match the - row/column order of the subject-by-subject square ISC matrix even though - the input ISCs should be supplied as the vectorized upper triangle of the - square ISC matrix. Returns the observed ISC and permutation-based p-value - (two-tailed test), as well as the permutation distribution of summary - statistic. According to Chen et al., 2016, this is the preferred - nonparametric approach for controlling false positive rates (FPR) for - two-sample tests. This approach may yield inflated FPRs for one-sample - tests. - - The implementation is based on the following publication: - - .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. - - Parameters - ---------- - iscs : list or ndarray, correlation matrix of ISCs - ISC values for one or more voxels - - group_assignment : list or ndarray, group labels - Group labels matching order of ISC input - - pairwise : bool, default:False - Indicator of pairwise or leave-one-out, should match ISCs variable - - summary_statistic : str, default:'median' - Summary statistic, either 'median' (default) or 'mean' - - n_permutations : int, default:1000 - Number of permutation iteration (randomizing group assignment) - - random_state = int, None, or np.random.RandomState, default:None - Initial random seed - - Returns - ------- - observed : float, ISC summary statistic or difference - Actual ISC or group difference (excluding between-group ISCs) - - p : float, p-value - p-value based on permutation test - - distribution : ndarray, permutations by voxels (optional) - Permutation distribution if return_bootstrap=True - """ - - # Standardize structure of input data - iscs, n_subjects, n_voxels = check_isc_input(iscs, pairwise=pairwise) - - # Check for valid summary statistic - if summary_statistic not in ('mean', 'median'): - raise ValueError("Summary statistic must be 'mean' or 'median'") - - # Check match between group labels and ISCs - group_assignment = check_group_assignment(group_assignment, - n_subjects) - - # Get group parameters - group_parameters = get_group_parameters(group_assignment, n_subjects, - pairwise=pairwise) - - # Set up permutation type (exact or Monte Carlo) - if group_parameters['n_groups'] == 1: - if n_permutations < 2**n_subjects: - logger.info("One-sample approximate permutation test using " - "sign-flipping procedure with Monte Carlo resampling.") - exact_permutations = None - else: - logger.info("One-sample exact permutation test using " - "sign-flipping procedure with 2**{0} " - "({1}) iterations.".format(n_subjects, - 2**n_subjects)) - exact_permutations = list(it.product([-1, 1], repeat=n_subjects)) - n_permutations = 2**n_subjects - - # Check for exact test for two groups - else: - if n_permutations < np.math.factorial(n_subjects): - logger.info("Two-sample approximate permutation test using " - "group randomization with Monte Carlo resampling.") - exact_permutations = None - else: - logger.info("Two-sample exact permutation test using group " - "randomization with {0}! " - "({1}) iterations.".format( - n_subjects, - np.math.factorial(n_subjects))) - exact_permutations = list(it.permutations( - np.arange(len(group_assignment)))) - n_permutations = np.math.factorial(n_subjects) - - # If one group, just get observed summary statistic - if group_parameters['n_groups'] == 1: - observed = compute_summary_statistic( - iscs, - summary_statistic=summary_statistic, - axis=0)[np.newaxis, :] - - # If two groups, get the observed difference - else: - observed = (compute_summary_statistic( - iscs[group_parameters['group_selector'] == - group_parameters['group_labels'][0], :], - summary_statistic=summary_statistic, - axis=0) - - compute_summary_statistic( - iscs[group_parameters['group_selector'] == - group_parameters['group_labels'][1], :], - summary_statistic=summary_statistic, - axis=0)) - observed = np.array(observed)[np.newaxis, :] - - # Set up an empty list to build our permutation distribution - distribution = [] - - # Loop through n permutation iterations and populate distribution - for i in np.arange(n_permutations): - - # Random seed to be deterministically re-randomized at each iteration - if exact_permutations: - prng = None - elif isinstance(random_state, np.random.RandomState): - prng = random_state - else: - prng = np.random.RandomState(random_state) - - # If one group, apply sign-flipping procedure - if group_parameters['n_groups'] == 1: - isc_sample = permute_one_sample_iscs( - iscs, group_parameters, i, - pairwise=pairwise, - summary_statistic=summary_statistic, - exact_permutations=exact_permutations, - prng=prng) - - # If two groups, set up group matrix get the observed difference - else: - isc_sample = permute_two_sample_iscs( - iscs, group_parameters, i, - pairwise=pairwise, - summary_statistic=summary_statistic, - exact_permutations=exact_permutations, - prng=prng) - - # Tack our permuted ISCs onto the permutation distribution - distribution.append(isc_sample) - - # Update random state for next iteration - if not exact_permutations: - random_state = np.random.RandomState(prng.randint( - 0, MAX_RANDOM_SEED)) - - # Convert distribution to numpy array - distribution = np.array(distribution) - assert distribution.shape == (n_permutations, n_voxels) - - # Get p-value for actual median from shifted distribution - if exact_permutations: - p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=True, - axis=0) - else: - p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False, - axis=0) - - # Reshape p-values to fit with data shape - p = p[np.newaxis, :] - - return observed, p, distribution - - -def timeshift_isc(data, pairwise=False, summary_statistic='median', - n_shifts=1000, random_state=None): - - """Circular time-shift randomization for one-sample ISC test - - For each voxel or ROI, compute the actual ISC and p-values - from a null distribution of ISCs where response time series - are first circularly shifted by random intervals. If pairwise, - apply time-shift randomization to each subjects and compute pairwise - ISCs. If leave-one-out approach is used (pairwise=False), apply - the random time-shift to only the left-out subject in each iteration - of the leave-one-out procedure. Input data should be a list where - each item is a time-points by voxels ndarray for a given subject. - Multiple input ndarrays must be the same shape. If a single ndarray is - supplied, the last dimension is assumed to correspond to subjects. - Returns the observed ISC and p-values (two-tailed test), as well as - the null distribution of ISCs computed on randomly time-shifted data. - - This implementation is based on the following publications: - - .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic - responses during watching a movie: localization in space and - frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, - 2010, Frontiers in Neuroinformatics, 4, 5. - - .. [Kauppi2014] "A versatile software package for inter-subject - correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, - J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. - - Parameters - ---------- - data : list or ndarray (n_TRs x n_voxels x n_subjects) - fMRI data for which to compute ISFC - - pairwise : bool, default: False - Whether to use pairwise (True) or leave-one-out (False) approach - - summary_statistic : str, default:'median' - Summary statistic, either 'median' (default) or 'mean' - - n_shifts : int, default:1000 - Number of randomly shifted samples - - random_state = int, None, or np.random.RandomState, default:None - Initial random seed - - Returns - ------- - observed : float, observed ISC (without time-shifting) - Actual ISCs - - p : float, p-value - p-value based on time-shifting randomization test - - distribution : ndarray, time-shifts by voxels (optional) - Time-shifted null distribution if return_bootstrap=True - """ - - # Check response time series input format - data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - - # Get actual observed ISC - observed = isc(data, pairwise=pairwise, - summary_statistic=summary_statistic) - - # Roll axis to get subjects in first dimension for loop - if pairwise: - data = np.rollaxis(data, 2, 0) - - # Iterate through randomized shifts to create null distribution - distribution = [] - for i in np.arange(n_shifts): - - # Random seed to be deterministically re-randomized at each iteration - if isinstance(random_state, np.random.RandomState): - prng = random_state - else: - prng = np.random.RandomState(random_state) - - # Get a random set of shifts based on number of TRs, - shifts = prng.choice(np.arange(n_TRs), size=n_subjects, - replace=True) - - # In pairwise approach, apply all shifts then compute pairwise ISCs - if pairwise: - - # Apply circular shift to each subject's time series - shifted_data = [] - for subject, shift in zip(data, shifts): - shifted_data.append(np.concatenate( - (subject[-shift:, :], - subject[:-shift, :]))) - shifted_data = np.dstack(shifted_data) - - # Compute null ISC on shifted data for pairwise approach - shifted_isc = isc(shifted_data, pairwise=pairwise, - summary_statistic=summary_statistic) - - # In leave-one-out, apply shift only to each left-out participant - elif not pairwise: - - shifted_isc = [] - for s, shift in enumerate(shifts): - shifted_subject = np.concatenate((data[-shift:, :, s], - data[:-shift, :, s])) - nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) - loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), - pairwise=False, - summary_statistic=None) - shifted_isc.append(loo_isc) - - # Get summary statistics across left-out subjects - shifted_isc = compute_summary_statistic( - np.dstack(shifted_isc), - summary_statistic=summary_statistic, - axis=2) - - distribution.append(shifted_isc) - - # Update random state for next iteration - random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - - # Convert distribution to numpy array - distribution = np.vstack(distribution) - assert distribution.shape == (n_shifts, n_voxels) - - # Get p-value for actual median from shifted distribution - p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False, - axis=0) - - # Reshape p-values to fit with data shape - p = p[np.newaxis, :] - - return observed, p, distribution - - -def phaseshift_isc(data, pairwise=False, summary_statistic='median', - n_shifts=1000, random_state=None): - - """Phase randomization for one-sample ISC test - - For each voxel or ROI, compute the actual ISC and p-values - from a null distribution of ISCs where response time series - are phase randomized prior to computing ISC. If pairwise, - apply phase randomization to each subject and compute pairwise - ISCs. If leave-one-out approach is used (pairwise=False), only - apply phase randomization to the left-out subject in each iteration - of the leave-one-out procedure. Input data should be a list where - each item is a time-points by voxels ndarray for a given subject. - Multiple input ndarrays must be the same shape. If a single ndarray is - supplied, the last dimension is assumed to correspond to subjects. - Returns the observed ISC and p-values (two-tailed test), as well as - the null distribution of ISCs computed on phase-randomized data. - - This implementation is based on the following publications: - - .. [Lerner2011] "Topographic mapping of a hierarchy of temporal - receptive windows using a narrated story.", Y. Lerner, C. J. Honey, - L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. - - .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. - - Parameters - ---------- - data : list or ndarray (n_TRs x n_voxels x n_subjects) - fMRI data for which to compute ISFC - - pairwise : bool, default: False - Whether to use pairwise (True) or leave-one-out (False) approach - - summary_statistic : str, default:'median' - Summary statistic, either 'median' (default) or 'mean' - - n_shifts : int, default:1000 - Number of randomly shifted samples - - random_state = int, None, or np.random.RandomState, default:None - Initial random seed - - Returns - ------- - observed : float, observed ISC (without time-shifting) - Actual ISCs - - p : float, p-value - p-value based on time-shifting randomization test - - distribution : ndarray, time-shifts by voxels (optional) - Time-shifted null distribution if return_bootstrap=True - """ - - # Check response time series input format - data, n_TRs, n_voxels, n_subjects = check_timeseries_input(data) - - # Get actual observed ISC - observed = isc(data, pairwise=pairwise, - summary_statistic=summary_statistic) - - # Iterate through randomized shifts to create null distribution - distribution = [] - for i in np.arange(n_shifts): - - # Random seed to be deterministically re-randomized at each iteration - if isinstance(random_state, np.random.RandomState): - prng = random_state - else: - prng = np.random.RandomState(random_state) - - # Get randomized phase shifts - if n_TRs % 2 == 0: - # Why are we indexing from 1 not zero here? n_TRs / -1 long? - pos_freq = np.arange(1, data.shape[0] // 2) - neg_freq = np.arange(data.shape[0] - 1, data.shape[0] // 2, -1) - else: - pos_freq = np.arange(1, (data.shape[0] - 1) // 2 + 1) - neg_freq = np.arange(data.shape[0] - 1, - (data.shape[0] - 1) // 2, -1) - - phase_shifts = prng.rand(len(pos_freq), 1, n_subjects) * 2 * np.math.pi - - # In pairwise approach, apply all shifts then compute pairwise ISCs - if pairwise: - - # Fast Fourier transform along time dimension of data - fft_data = fft(data, axis=0) - - # Shift pos and neg frequencies symmetrically, to keep signal real - fft_data[pos_freq, :, :] *= np.exp(1j * phase_shifts) - fft_data[neg_freq, :, :] *= np.exp(-1j * phase_shifts) - - # Inverse FFT to put data back in time domain for ISC - shifted_data = np.real(ifft(fft_data, axis=0)) - - # Compute null ISC on shifted data for pairwise approach - shifted_isc = isc(shifted_data, pairwise=True, - summary_statistic=summary_statistic) - - # In leave-one-out, apply shift only to each left-out participant - elif not pairwise: - - # Roll subject axis in phaseshifts for loop - phase_shifts = np.rollaxis(phase_shifts, 2, 0) - - shifted_isc = [] - for s, shift in enumerate(phase_shifts): - - # Apply FFT to left-out subject - fft_subject = fft(data[:, :, s], axis=0) - - # Shift pos and neg frequencies symmetrically, keep signal real - fft_subject[pos_freq, :] *= np.exp(1j * shift) - fft_subject[neg_freq, :] *= np.exp(-1j * shift) - - # Inverse FFT to put data back in time domain for ISC - shifted_subject = np.real(ifft(fft_subject, axis=0)) - - # ISC of shifted left-out subject vs mean of N-1 subjects - nonshifted_mean = np.mean(np.delete(data, s, 2), axis=2) - loo_isc = isc(np.dstack((shifted_subject, nonshifted_mean)), - pairwise=False, summary_statistic=None) - shifted_isc.append(loo_isc) - - # Get summary statistics across left-out subjects - shifted_isc = compute_summary_statistic( - np.dstack(shifted_isc), - summary_statistic=summary_statistic, axis=2) - distribution.append(shifted_isc) - - # Update random state for next iteration - random_state = np.random.RandomState(prng.randint(0, MAX_RANDOM_SEED)) - - # Convert distribution to numpy array - distribution = np.vstack(distribution) - assert distribution.shape == (n_shifts, n_voxels) - - # Get p-value for actual median from shifted distribution - p = compute_p_from_null_distribution(observed, distribution, - side='two-sided', exact=False, - axis=0) - - # Reshape p-values to fit with data shape - p = p[np.newaxis, :] - - return observed, p, distribution diff --git a/tests/isfc/test_isfc.py b/tests/isfc/test_isfc.py deleted file mode 100644 index 2c5508446..000000000 --- a/tests/isfc/test_isfc.py +++ /dev/null @@ -1,556 +0,0 @@ -import numpy as np -import logging -from brainiak.isfc import (isc, isfc, bootstrap_isc, permutation_isc, - timeshift_isc, phaseshift_isc) -from scipy.spatial.distance import squareform - -logger = logging.getLogger(__name__) - - -# Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, - noise=1, data_type='array', - random_state=None): - prng = np.random.RandomState(random_state) - if n_voxels: - signal = prng.randn(n_TRs, n_voxels) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs, n_voxels) * noise - for subject in np.arange(n_subjects)] - elif not n_voxels: - signal = prng.randn(n_TRs) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs) * noise - for subject in np.arange(n_subjects)] - if data_type == 'array': - if n_voxels: - data = np.dstack(data) - elif not n_voxels: - data = np.column_stack(data) - return data - - -# Create 3 voxel simulated data with correlated time series -def correlated_timeseries(n_subjects, n_TRs, noise=0, - random_state=None): - prng = np.random.RandomState(random_state) - signal = prng.randn(n_TRs) - correlated = True - while correlated: - uncorrelated = np.random.randn(n_TRs, - n_subjects)[:, np.newaxis, :] - unc_max = np.amax(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - unc_mean = np.mean(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - if unc_max < .3 and np.abs(unc_mean) < .001: - correlated = False - data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], - 20, axis=2) - data = np.concatenate((data, uncorrelated), axis=1) - data = data + np.random.randn(n_TRs, 3, n_subjects) * noise - return data - - -# Compute ISCs using different input types -# List of subjects with one voxel/ROI -def test_isc_input(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC inputs") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with one voxel/ROI - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - # List of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - logger.info("Finished testing ISC inputs") - - -# Check pairwise and leave-one-out, and summary statistics for ISC -def test_isc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs_loo = isc(data, pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_subjects, n_voxels) - - iscs_pw = isc(data, pairwise=True, summary_statistic=None) - assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - - # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic='mean') - assert isc_mean.shape == (1, n_voxels) - - isc_median = isc(data, pairwise=False, summary_statistic='median') - assert isc_median.shape == (1, n_voxels) - - try: - isc(data, pairwise=False, summary_statistic='min') - except ValueError: - logger.info("Correctly caught unexpected summary statistic") - - logger.info("Finished testing ISC options") - - -# Make sure ISC recovers correlations of 1 and less than 1 -def test_isc_output(): - - logger.info("Testing ISC outputs") - - data = correlated_timeseries(20, 60, noise=0, - random_state=42) - iscs = isc(data, pairwise=False) - assert np.all(iscs[:, :2] == 1.) - assert np.all(iscs[:, -1] < 1.) - - iscs = isc(data, pairwise=True) - assert np.all(iscs[:, :2] == 1.) - assert np.all(iscs[:, -1] < 1.) - - logger.info("Finished testing ISC outputs") - - -# Test one-sample bootstrap test -def test_bootstrap_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - n_bootstraps = 10 - - logger.info("Testing bootstrap hypothesis test") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs = isc(data, pairwise=False, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Test one-sample bootstrap test with pairwise approach - n_bootstraps = 10 - - iscs = isc(data, pairwise=True, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Check random seeds - iscs = isc(data, pairwise=False, summary_statistic=None) - distributions = [] - for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc( - iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95, - random_state=random_state) - distributions.append(distribution) - assert np.array_equal(distributions[0], distributions[1]) - assert not np.array_equal(distributions[1], distributions[2]) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='median')) - - logger.info("Finished testing bootstrap hypothesis test") - - -# Test permutation test with group assignments -def test_permutation_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - group_assignment = [1] * 10 + [2] * 10 - - logger.info("Testing permutation test") - - # Create dataset with two groups in pairwise approach - data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=5, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=200) - - # Create data with two groups in leave-one-out approach - data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=10, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=200) - - # One-sample leave-one-out permutation test - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=False, - summary_statistic='median', - n_permutations=200) - - # One-sample pairwise permutation test - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=True, - summary_statistic='median', - n_permutations=200) - - # Small one-sample pairwise exact test - data = simulated_timeseries(12, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median', - n_permutations=10000) - - # Small two-sample pairwise exact test (and unequal groups) - data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=10000) - - # Small two-sample leave-one-out exact test (and unequal groups) - data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=10000) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True, - summary_statistic='mean') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='mean')) - - logger.info("Finished testing permutaton test") - - -def test_timeshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing circular time-shift") - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, pairwise - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='mean')) - - logger.info("Finished testing circular time-shift") - - -# Phase randomization test -def test_phaseshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing phase randomization") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Phase randomization one-sample test, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0, 0] < .05 and p[0, 1] < .05 - assert p[0, 2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='mean')) - - logger.info("Finished testing phase randomization") - - -# Test ISFC -def test_isfc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing ISFC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - isfcs = isfc(data, pairwise=False, summary_statistic=None) - - # Just two subjects - isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None) - - # ISFC with pairwise approach - isfcs = isfc(data, pairwise=True, summary_statistic=None) - - # ISFC with summary statistics - isfcs = isfc(data, pairwise=True, summary_statistic='mean') - isfcs = isfc(data, pairwise=True, summary_statistic='median') - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - isfcs = isfc(data, pairwise=False) - assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) - assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - - isfcs = isfc(data, pairwise=True) - assert np.all(isfcs[0, 1, :] > .5) and np.all(isfcs[1, 0, :] > .5) - assert np.all(isfcs[:2, 2, :] < .5) and np.all(isfcs[2, :2, :] < .5) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=False) - isfcs = isfc(data, pairwise=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :], rtol=1e-03) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=True) - isfcs = isfc(data, pairwise=True) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[..., s].diagonal(), iscs[s, :], rtol=1e-03) - - logger.info("Finished testing ISFC options") - - -if __name__ == '__main__': - test_isc_input() - test_isc_options() - test_isc_output() - test_bootstrap_isc() - test_permutation_isc() - test_timeshift_isc() - test_phaseshift_isc() - test_isfc_options() - logger.info("Finished all ISC tests") From 04c7734320ab9f6e8fbe835f56c7a7d5f03da93b Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 6 Dec 2018 16:31:41 -0500 Subject: [PATCH 40/43] Removed duplicate Simony reference in phaseshift_isc --- brainiak/isc.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/brainiak/isc.py b/brainiak/isc.py index 09f8062cb..fea81a45b 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -1149,11 +1149,6 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', receptive windows using a narrated story.", Y. Lerner, C. J. Honey, L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. - .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. - Parameters ---------- data : list or ndarray (n_TRs x n_voxels x n_subjects) From 3a5a0d86170f05c342d01c45d937e059fb464da7 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 6 Dec 2018 18:15:12 -0500 Subject: [PATCH 41/43] Fixed references and added doi URLs --- brainiak/isc.py | 78 ++++++++++++++++++++++++----------------- brainiak/utils/utils.py | 9 ++--- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/brainiak/isc.py b/brainiak/isc.py index fea81a45b..c1bd3684f 100644 --- a/brainiak/isc.py +++ b/brainiak/isc.py @@ -18,6 +18,29 @@ analyses (e.g., intersubject funtional correlations; ISFC), as well as statistical tests designed specifically for ISC analyses. +The implementation is based on the work in [Hasson2004]_, [Kauppi2014]_, +[Simony2016]_, and [Chen2016]_. + +.. [Chen2016] "Untangling the relatedness among correlations, part I: + nonparametric approaches to inter-subject correlation analysis at the + group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. + Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + https://doi.org/10.1016/j.neuroimage.2016.05.023 + +.. [Hasson2004] "Intersubject synchronization of cortical activity + during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, + R. Malach, 2004, Science, 303, 1634-1640. + https://doi.org/10.1126/science.1089506 + +.. [Kauppi2014] "A versatile software package for inter-subject + correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, + J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. + https://doi.org/10.3389/fninf.2014.00002 + +.. [Simony2016] "Dynamic reconfiguration of the default mode network + during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. + Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, + 7, 12141. https://doi.org/10.1038/ncomms12141 """ # Authors: Sam Nastase, Christopher Baldassano, Qihong Lu, @@ -61,11 +84,7 @@ def isc(data, pairwise=False, summary_statistic=None): Output is an ndarray where the first dimension is the number of subjects or pairs and the second dimension is the number of voxels (or ROIs). - The implementation is based on the following publication: - - .. [Hasson2004] "Intersubject synchronization of cortical activity - during natural vision.", U. Hasson, Y. Nir, I. Levy, G. Fuhrmann, - R. Malach, 2004, Science, 303, 1634-1640. + The implementation is based on the work in [Hasson2004]_. Parameters ---------- @@ -144,12 +163,7 @@ def isfc(data, pairwise=False, summary_statistic=None): supplied; otherwise output is n_voxels by n_voxels by n_subjects (or n_pairs) array. - The implementation is based on the following publication: - - .. [Simony2016] "Dynamic reconfiguration of the default mode network - during narrative comprehension.", E. Simony, C. J. Honey, J. Chen, O. - Lositsky, Y. Yeshurun, A. Wiesel, U. Hasson, 2016, Nature Communications, - 7, 12141. + The implementation is based on the work in [Simony2016]_. Parameters ---------- @@ -348,11 +362,12 @@ def compute_summary_statistic(iscs, summary_statistic='mean', axis=None): case of the mean, ISC values are first Fisher Z transformed (arctanh), averaged, then inverse Fisher Z transformed (tanh). - The implementation is based on the following publication: + The implementation is based on the work in [SilverDunlap1987]_. .. [SilverDunlap1987] "Averaging corrlelation coefficients: should - Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, - Journal of Applied Psychology, 72, 146-148. + Fisher's z transformation be used?", N. C. Silver, W. P. Dunlap, 1987, + Journal of Applied Psychology, 72, 146-148. + https://doi.org/10.1037/0021-9010.72.1.146 Parameters ---------- @@ -407,15 +422,12 @@ def bootstrap_isc(iscs, pairwise=False, summary_statistic='median', controlling false positive rates (FPR) for one-sample tests in the pairwise approach. - The implementation is based on the following publications: - - .. [Chen2016] "Untangling the relatedness among correlations, part I: - nonparametric approaches to inter-subject correlation analysis at the - group level.", G. Chen, Y. W. Shin, P. A. Taylor, D. R. Glen, R. C. - Reynolds, R. B. Israel, R. W. Cox, 2016, NeuroImage, 142, 248-259. + The implementation is based on the work in [Chen2016]_ and + [HallWilson1991]_. .. [HallWilson1991] "Two guidelines for bootstrap hypothesis testing.", - P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. + P. Hall, S. R., Wilson, 1991, Biometrics, 757-762. + https://doi.org/10.2307/2532163 Parameters ---------- @@ -828,7 +840,7 @@ def permutation_isc(iscs, group_assignment=None, pairwise=False, # noqa: C901 two-sample tests. This approach may yield inflated FPRs for one-sample tests. - The implementation is based on the following publication: [Chen2016]_ + The implementation is based on the work in [Chen2016]_. Parameters ---------- @@ -1006,16 +1018,14 @@ def timeshift_isc(data, pairwise=False, summary_statistic='median', Returns the observed ISC and p-values (two-tailed test), as well as the null distribution of ISCs computed on randomly time-shifted data. - This implementation is based on the following publications: + The implementation is based on the work in [Kauppi2010]_ and + [Kauppi2014]_. .. [Kauppi2010] "Inter-subject correlation of brain hemodynamic - responses during watching a movie: localization in space and - frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, - 2010, Frontiers in Neuroinformatics, 4, 5. - - .. [Kauppi2014] "A versatile software package for inter-subject - correlation based analyses of fMRI.", J. P. Kauppi, J. Pajula, - J. Tohka, 2014, Frontiers in Neuroinformatics, 8, 2. + responses during watching a movie: localization in space and + frequency.", J. P. Kauppi, I. P. Jääskeläinen, M. Sams, J. Tohka, + 2010, Frontiers in Neuroinformatics, 4, 5. + https://doi.org/10.3389/fninf.2010.00005 Parameters ---------- @@ -1143,11 +1153,13 @@ def phaseshift_isc(data, pairwise=False, summary_statistic='median', Returns the observed ISC and p-values (two-tailed test), as well as the null distribution of ISCs computed on phase-randomized data. - This implementation is based on the following publications: + The implementation is based on the work in [Lerner2011]_ and + [Simony2016]_. .. [Lerner2011] "Topographic mapping of a hierarchy of temporal - receptive windows using a narrated story.", Y. Lerner, C. J. Honey, - L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. + receptive windows using a narrated story.", Y. Lerner, C. J. Honey, + L. J. Silbert, U. Hasson, 2011, Journal of Neuroscience, 31, 2906-2915. + https://doi.org/10.1523/jneurosci.3684-10.2011 Parameters ---------- diff --git a/brainiak/utils/utils.py b/brainiak/utils/utils.py index c0dc36c07..74b625a4b 100644 --- a/brainiak/utils/utils.py +++ b/brainiak/utils/utils.py @@ -850,12 +850,13 @@ def compute_p_from_null_distribution(observed, distribution, distribution is provided, use axis argument to specify which axis indexes resampling iterations. - The implementation is based on the following publication: + The implementation is based on the work in [PhipsonSmyth2010]_. .. [PhipsonSmyth2010] "Permutation p-values should never be zero: - calculating exact p-values when permutations are randomly drawn.", - B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics - and Molecular Biology, 9, 1544-6115. + calculating exact p-values when permutations are randomly drawn.", + B. Phipson, G. K., Smyth, 2010, Statistical Applications in Genetics + and Molecular Biology, 9, 1544-6115. + https://doi.org/10.2202/1544-6115.1585 Parameters ---------- From a39f0b81359e85344303d5d2edfb923028da46c4 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 6 Dec 2018 20:15:48 -0500 Subject: [PATCH 42/43] Updated example with current ISC code --- examples/isc/download_data.sh | 4 ++ examples/isc/isfc.py | 76 +++++++++++++++++++++++++ examples/{isfc => isc}/requirements.txt | 0 examples/isfc/download_data.sh | 4 -- examples/isfc/isfc.py | 69 ---------------------- 5 files changed, 80 insertions(+), 73 deletions(-) create mode 100755 examples/isc/download_data.sh create mode 100644 examples/isc/isfc.py rename examples/{isfc => isc}/requirements.txt (100%) delete mode 100755 examples/isfc/download_data.sh delete mode 100644 examples/isfc/isfc.py diff --git a/examples/isc/download_data.sh b/examples/isc/download_data.sh new file mode 100755 index 000000000..40d810da6 --- /dev/null +++ b/examples/isc/download_data.sh @@ -0,0 +1,4 @@ +#!/bin/sh +wget https://ndownloader.figshare.com/files/9342556 -O isc.zip +unzip -qo isc.zip +rm -f isc.zip diff --git a/examples/isc/isfc.py b/examples/isc/isfc.py new file mode 100644 index 000000000..bc1fd743a --- /dev/null +++ b/examples/isc/isfc.py @@ -0,0 +1,76 @@ +# Copyright 2018 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. +"""Example of intersubject correlation (ISC) analysis + +Computes ISC for all voxels within a brain mask, and computes +ISFC for voxels with high ISC. + +First download the example dataset by running the download_data.sh +script locally (e.g., ./download_data.sh). This download includes +functional data for 5 subjects and a gray-matter anatomical mask. +""" + +# Authors: Christopher Baldassano, Sam Nastase, and Mor Regev +# Princeton University, 2018 + +from os.path import abspath, dirname, join +from brainiak.isc import isc, isfc +import numpy as np +import nibabel as nib +from brainiak import image, io +import matplotlib.pyplot as plt +from scipy.cluster.hierarchy import fcluster, linkage + + +curr_dir = dirname(abspath("__file__")) + +mask_fn = join(curr_dir,'avg152T1_gray_3mm.nii.gz') +func_fns = [join(curr_dir, + 'sub-{0:03d}-task-intact1.nii.gz'.format(sub)) + for sub in np.arange(1, 6)] + +print('Loading data from {0} subjects...'.format(len(func_fns))) + +mask_image = io.load_boolean_mask(mask_fn, lambda x: x > 50) +masked_images = image.mask_images(io.load_images(func_fns), + mask_image) +coords = np.where(mask_image) +data = image.MaskedMultiSubjectData.from_masked_images(masked_images, + len(func_fns)) + +print('Calculating mean ISC on {0} voxels'.format(data.shape[1])) +iscs = isc(data, pairwise=False, summary_statistic='mean') +iscs = np.nan_to_num(iscs) + +print('Writing ISC map to file...') +nii_template = nib.load(mask_fn) +isc_vol = np.zeros(nii_template.shape) +isc_vol[coords] = iscs +isc_image = nib.Nifti1Image(isc_vol, nii_template.affine, + nii_template.header) +nib.save(isc_image, 'example_isc.nii.gz') + +isc_mask = (iscs > 0.2)[0, :] +print('Calculating mean ISFC on {0} voxels...'.format(np.sum(isc_mask))) +data_masked = data[:, isc_mask, :] +isfcs = isfc(data_masked, pairwise=False, summary_statistic='mean') + +print('Clustering ISFC...') +Z = linkage(isfcs, 'ward') +z = fcluster(Z, 2, criterion='maxclust') +clust_inds = np.argsort(z) + +# Show the ISFC matrix, sorted to show the two main clusters +plt.imshow(isfcs[np.ix_(clust_inds, clust_inds)]) +plt.show() diff --git a/examples/isfc/requirements.txt b/examples/isc/requirements.txt similarity index 100% rename from examples/isfc/requirements.txt rename to examples/isc/requirements.txt diff --git a/examples/isfc/download_data.sh b/examples/isfc/download_data.sh deleted file mode 100755 index 3831f3482..000000000 --- a/examples/isfc/download_data.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -wget https://ndownloader.figshare.com/files/9342556 -O isfc.zip -unzip -qo isfc.zip -rm -f isfc.zip diff --git a/examples/isfc/isfc.py b/examples/isfc/isfc.py deleted file mode 100644 index 373755623..000000000 --- a/examples/isfc/isfc.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2017 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. -"""Example of intersubject analyses (ISC/ISFC) - -Computes ISC for all voxels within a brain mask, and -computes ISFC for voxels with high ISC -""" - -# Authors: Christopher Baldassano and Mor Regev -# Princeton University, 2017 - -import brainiak.isfc -from brainiak import image, io -import nibabel as nib -import numpy as np -from matplotlib import pyplot as plt -from scipy.cluster.hierarchy import fcluster, linkage -import sys -import os - -curr_dir = os.path.dirname(__file__) - -brain_fname = os.path.join(curr_dir,'avg152T1_gray_3mm.nii.gz') -fnames = [os.path.join(curr_dir, - 'sub-0' + format(subj, '02') + '-task-intact1.nii.gz') for - subj in np.arange(1, 5)] - -print('Loading data from ', len(fnames), ' subjects...') - -brain_mask = io.load_boolean_mask(brain_fname, lambda x: x > 50) -masked_images = image.mask_images(io.load_images(fnames), brain_mask) -coords = np.where(brain_mask) -D = image.MaskedMultiSubjectData.from_masked_images(masked_images, len(fnames)) - -print('Calculating ISC on ', D.shape[0], ' voxels') -ISC = brainiak.isfc.isc(D) -ISC[np.isnan(ISC)] = 0 - -print('Writing ISC map to file...') -brain_nii = nib.load(brain_fname) -ISC_vol = np.zeros(brain_nii.shape) -ISC_vol[coords] = ISC -ISC_nifti = nib.Nifti1Image(ISC_vol, brain_nii.affine, brain_nii.header) -nib.save(ISC_nifti, 'ISC.nii.gz') - -ISC_mask = ISC > 0.2 -print('Calculating ISFC on ', np.sum(ISC_mask), ' voxels...') -D_masked = D[ISC_mask, :, :] -ISFC = brainiak.isfc.isfc(D_masked) - -print('Clustering ISFC...') -Z = linkage(ISFC, 'ward') -z = fcluster(Z, 2, criterion='maxclust') -clust_inds = np.argsort(z) - -# Show the ISFC matrix, sorted to show the two main clusters -plt.imshow(ISFC[np.ix_(clust_inds,clust_inds)]) -plt.show() From bae11b648016fb4efea84b21c3d1361ab052f654 Mon Sep 17 00:00:00 2001 From: Sam Nastase Date: Thu, 6 Dec 2018 20:29:07 -0500 Subject: [PATCH 43/43] Changed test_isc_output to allclose with some tolerance --- tests/isc/test_isc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py index 12c49bf81..6bd681cc3 100644 --- a/tests/isc/test_isc.py +++ b/tests/isc/test_isc.py @@ -140,11 +140,11 @@ def test_isc_output(): data = correlated_timeseries(20, 60, noise=0, random_state=42) iscs = isc(data, pairwise=False) - assert np.all(iscs[:, :2] == 1.) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) assert np.all(iscs[:, -1] < 1.) iscs = isc(data, pairwise=True) - assert np.all(iscs[:, :2] == 1.) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) assert np.all(iscs[:, -1] < 1.) logger.info("Finished testing ISC outputs")