Source code for tensorpac.methods.meth_pac

"""Individual methods for assessing PAC."""
import numpy as np
from scipy.special import erfinv

from tensorpac.gcmi import nd_mi_gg


[docs]def mean_vector_length(pha, amp): """Tensor-based Mean Vector Length (MVL). Parameters ---------- pha, amp : array_like Respectively the arrays of phases of shape (n_pha, ..., n_times) and the array of amplitudes of shape (n_amp, ..., n_times). Returns ------- pac : array_like Array of phase amplitude coupling of shape (n_amp, n_pha, ...) References ---------- Canolty et al. 2006 :cite:`canolty2006high` """ return np.abs(np.einsum('i...j, k...j->ik...', amp, np.exp(1j * pha))) / pha.shape[-1]
[docs]def modulation_index(pha, amp, n_bins=18): """Tensor-based Modulation index (MI). The modulation index is obtained using the Kullback Leibler Distance which measures how much the distribution of binned amplitude differs from a uniform distribution. Parameters ---------- pha, amp : array_like Respectively the arrays of phases of shape (n_pha, ..., n_times) and the array of amplitudes of shape (n_amp, ..., n_times). n_bins : int | 18 Number of bins to binarize the amplitude according to phase intervals Returns ------- pac : array_like Array of phase amplitude coupling of shape (n_amp, n_pha, ...) References ---------- Tort et al. 2010 :cite:`tort2010measuring` """ # Get the phase locked binarized amplitude : p_j = _kl_hr(pha, amp, n_bins) # Divide the binned amplitude by the mean over the bins : p_j /= p_j.sum(axis=0, keepdims=True) # Take the log of non-zero values : p_j = p_j * np.ma.log(p_j).filled(-np.inf) # Compute the PAC : pac = 1 + p_j.sum(axis=0) / np.log(n_bins) # Set distribution distances that are really closed to zero : pac[np.isinf(pac)] = 0. return pac
[docs]def heights_ratio(pha, amp, n_bins=18): """Tensor-based Heights ratio (HR). Parameters ---------- pha, amp : array_like Respectively the arrays of phases of shape (n_pha, ..., n_times) and the array of amplitudes of shape (n_amp, ..., n_times). n_bins : int | 18 Number of bins to binarize the amplitude according to phase intervals Returns ------- pac : array_like Array of phase amplitude coupling of shape (n_amp, n_pha, ...) References ---------- Lakatos et al. 2005 :cite:`lakatos2005oscillatory` """ # Get the phase locked binarized amplitude : p_j = _kl_hr(pha, amp, n_bins) # Divide the binned amplitude by the mean over the bins : p_j /= p_j.sum(axis=0, keepdims=True) # Find (maxximum, minimum) of the binned distribution : h_max, h_min = p_j.max(axis=0), p_j.min(axis=0) # Compute pac : pac = (h_max - h_min) / h_max return pac
def _kl_hr(pha, amp, n_bins, mean_bins=True): """Binarize the amplitude according to phase values. This function is shared by the Kullback-Leibler Distance and the Height Ratio. """ vecbin = np.linspace(-np.pi, np.pi, n_bins + 1) phad = np.digitize(pha, vecbin) - 1 abin = [] for i in np.unique(phad): # Find where phase take vecbin values : idx = phad == i m = idx.sum() if mean_bins else 1. # Take the sum of amplitude inside the bin : abin_pha = np.einsum('i...j, k...j->ik...', amp, idx) / m abin.append(abin_pha) return np.array(abin)
[docs]def norm_direct_pac(pha, amp, p=.05): """Tensor-based Normalized direct Pac (ndPAC). Parameters ---------- pha, amp : array_like Respectively the arrays of phases of shape (n_pha, ..., n_times) and the array of amplitudes of shape (n_amp, ..., n_times). p : float | .05 P-value to use for thresholding. Sub-threshold PAC values will be set to 0. To disable this behavior (no masking), use ``p=1`` or ``p=None``. Returns ------- pac : array_like Array of phase amplitude coupling of shape (n_amp, n_pha, ...) References ---------- Ozkurt et al. :cite:`ozkurt2012statistically` """ n_times = amp.shape[-1] # normalize amplitude # use the sample standard deviation, as in original matlab code from author amp = np.subtract(amp, np.mean(amp, axis=-1, keepdims=True)) amp = np.divide(amp, np.std(amp, ddof=1, axis=-1, keepdims=True)) # compute pac pac = np.abs(np.einsum('i...j, k...j->ik...', amp, np.exp(1j * pha))) # no thresholding if p == 1. or p is None: return pac / n_times s = pac ** 2 pac /= n_times # set to zero non-significant values xlim = n_times * erfinv(1 - p) ** 2 pac[s <= 2 * xlim] = 0. return pac
[docs]def phase_locking_value(pha, pha_amp): """Tensor-based Phase Locking-Value (PLV). In order to measure the phase locking value, the phase of the amplitude of the higher-frequency signal must be provided, and not the amplitude as in most other PAC functions. Parameters ---------- pha, pha_amp : array_like Respectively the arrays of phases of shape (n_pha, ..., n_times) for the lower frequency and the array of phase of the amplitude signal of shape (n_pha_amp, ..., n_times) for the higher frequency. Returns ------- pac : array_like Array of phase amplitude coupling of shape (n_pha_amp, n_pha, ...) References ---------- Penny et al. 2008 :cite:`penny2008testing`, Lachaux et al. 1999 :cite:`lachaux1999measuring` """ pac = np.einsum('i...j, k...j->ik...', np.exp(-1j * pha_amp), np.exp(1j * pha)) return np.abs(pac) / pha.shape[-1]
[docs]def gauss_cop_pac(pha, amp): """Tensor-based Gaussian Copula PAC (gcPac). 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 the arrays of phases of shape (n_pha, ..., n_times) and the array of amplitudes of shape (n_amp, ..., n_times). Returns ------- pac : array_like Array of phase amplitude coupling of shape (n_amp, n_pha, ...) References ---------- Ince et al 2017. :cite:`ince2017statistical` """ # prepare the shape of gcpac n_pha, n_amp = pha.shape[0], amp.shape[0] pha_sh = list(pha.shape[:-2]) gc = np.zeros([n_amp] + pha_sh, dtype=float) # compute mutual information for p in range(n_pha): for a in range(n_amp): gc[a, p, ...] = nd_mi_gg(pha[p, ...], amp[a, ...]) return gc