Source code for tensorpac.methods.meth_erpac

"""Individual methods for assessing ERPAC."""
import numpy as np
from scipy.stats import chi2

from joblib import Parallel, delayed

from tensorpac.gcmi import nd_mi_gg
from tensorpac.config import CONFIG


def pearson(x, y, st='i...j, k...j->ik...'):
    """Pearson correlation for multi-dimensional arrays.

    Parameters
    ----------
    x, y : array_like
        Compute pearson correlation between the multi-dimensional arrays
        x and y.
    st : string | 'i..j, k..j->ik...'
        The string to pass to the np.einsum function.

    Returns
    -------
    cov: array_like
        The pearson correlation array.
    """
    n = x.shape[-1]
    # Distribution center :
    mu_x = x.mean(-1, keepdims=True)
    mu_y = y.mean(-1, keepdims=True)
    # Distribution deviation :
    s_x = x.std(-1, ddof=n - 1, keepdims=True)
    s_y = y.std(-1, ddof=n - 1, keepdims=True)
    # Compute correlation coefficient :
    cov = np.einsum(st, x, y)
    mu_xy = np.einsum(st, mu_x, mu_y)
    cov -= n * mu_xy
    cov /= np.einsum(st, s_x, s_y)
    return cov


[docs]def erpac(pha, amp): """Event-Related Phase Amplitude Coupling. This function computed the correlation coefficient between a circular and a linear random variable at each time point and across trials. Adapted from the function circ_corrcc Circular Statistics Toolbox for Matlab By Philipp Berens, 2009 :cite:`berens2009circstat`. This function is an adaptation of Voytek, 2013 :cite:`voytek2013method` for tensors. Parameters ---------- pha, amp : array_like Respectively the arrays of phases of shape (n_pha, ..., n_epochs) and the array of amplitudes of shape (n_amp, ..., n_epochs). Returns ------- rho : array_like Array of correlation coefficients of shape (n_amp, n_pha, ...) pval : array_like Array of p-values of shape (n_amp, n_pha, ...). References ---------- Voytek et al. 2013 :cite:`voytek2013method` """ # Compute correlation coefficient for sin and cos independently n = pha.shape[-1] sa, ca = np.sin(pha), np.cos(pha) rxs = pearson(amp, sa) rxc = pearson(amp, ca) rcs = pearson(sa, ca, st='i...j, k...j->i...') rcs = rcs[np.newaxis, ...] # Compute angular-linear correlation (equ. 27.47) rho = np.sqrt((rxc**2 + rxs**2 - 2 * rxc * rxs * rcs) / (1 - rcs**2)) # Compute pvalue : pval = 1. - chi2.cdf(n * rho**2, 2) return rho, pval
[docs]def ergcpac(pha, amp, smooth=None, n_jobs=-1): """Event Related PAC using the Gaussian Copula Mutual Information. This function assumes that phases and amplitudes have already been prepared i.e. phases should be represented in a unit circle (np.c_[np.sin(pha), np.cos(pha)]) and both inputs should also have been copnormed. Parameters ---------- pha, amp : array_like Respectively arrays of phases of shape (n_pha, n_times, 2, n_epochs) and the array of amplitudes of shape (n_amp, n_times, 1, n_epochs). Returns ------- erpac : array_like Array of correlation coefficients of shape (n_amp, n_pha, n_times) References ---------- Ince et al. 2017 :cite:`ince2017statistical` """ # get shapes (n_pha, n_times, _, n_epochs), n_amp = pha.shape, amp.shape[0] # noqa # compute mutual information across trials ergcpac = np.zeros((n_amp, n_pha, n_times)) if isinstance(smooth, int): # define the temporal smoothing vector vec = np.arange(smooth, n_times - smooth, 1) times = [slice(k - smooth, k + smooth + 1) for k in vec] # move time axis to avoid to do it inside parallel pha, amp = np.moveaxis(pha, 1, -2), np.moveaxis(amp, 1, -2) # function to run in parallel across times def _fcn(t): # noqa _erpac = np.zeros((n_amp, n_pha), dtype=float) xp, xa = pha[..., t, :], amp[..., t, :] for a in range(n_amp): _xa = xa.reshape(n_amp, 1, -1) for p in range(n_pha): _xp = xp.reshape(n_pha, 2, -1) _erpac[a, p] = nd_mi_gg(_xp[p, ...], _xa[a, ...]) return _erpac # run the function across time points _ergcpac = Parallel(n_jobs=n_jobs, **CONFIG['JOBLIB_CFG'])(delayed( _fcn)(t) for t in times) # reconstruct the smoothed array for a in range(n_amp): for p in range(n_pha): mean_vec = np.zeros((n_times,), dtype=float) for t, _gc in zip(times, _ergcpac): ergcpac[a, p, t] += _gc[a, p] mean_vec[t] += 1 ergcpac[a, p, :] /= mean_vec else: for a in range(n_amp): for p in range(n_pha): ergcpac[a, p, ...] = nd_mi_gg(pha[p, ...], amp[a, ...]) return ergcpac
def swap_erpac_trials(pha): """Swap trials across the last dimension.""" tr_ = np.random.permutation(pha.shape[-1]) return pha[..., tr_] def _ergcpac_perm(pha, amp, smooth=None, n_jobs=-1, n_perm=200): def _ergcpac_single_perm(): p = swap_erpac_trials(pha) return ergcpac(p, amp, smooth=smooth, n_jobs=1) out = Parallel(n_jobs=n_jobs, **CONFIG['JOBLIB_CFG'])(delayed( _ergcpac_single_perm)() for _ in range(n_perm)) return np.stack(out)