def fit(self, pha, amp, method='circular', smooth=None, n_jobs=-1, n_perm=None, p=.05, mcp='fdr', verbose=None): """Compute the Event-Related Phase-Amplitude Coupling (ERPAC). The ERPAC :cite:`voytek2013method` is used to measure PAC across trials and is interesting for real-time estimation. Parameters ---------- pha, amp : array_like Respectively the phase of slower oscillations of shape (n_pha, n_epochs, n_times) and the amplitude of faster oscillations of shape (n_pha, n_epochs, n_times). method : {'circular', 'gc'} Name of the method for computing erpac. Use 'circular' for reproducing :cite:`voytek2013method` or 'gc' for a Gaussian-Copula based erpac :cite:`ince2017statistical`. smooth : int | None Half number of time-points to use to produce a smoothing. Only active with the Gaussian-Copula ('gc') method. n_perm : int | None Number of permutations to compute for assessing p-values for the gaussian-copula ('gc') method. Statistics are performed by randomly swapping phase trials p : float | 0.05 Statistical threshold for the gaussian-copula ('gc') method mcp : {'fdr', 'bonferroni'} Correct the p-values for multiple comparisons. This is needed when using the circular ERPAC (:cite:`voytek2013method`). Note that the correction is performed using MNE-Python. Returns ------- erpac : array_like The ERPAC estimation of shape (n_amp, n_pha, n_times) """ set_log_level(verbose) pha, amp = self._phampcheck(pha, amp) self.method = method self._pvalues = None # move the trial axis to the end (n_freqs, n_times, n_epochs) pha, amp = np.moveaxis(pha, 1, -1), np.moveaxis(amp, 1, -1) # method switch if method == 'circular': self.method = "ERPAC (Voytek et al. 2013)" logger.info(f" Compute {self.method}") self._erpac, self._pvalues = erpac(pha, amp) self.infer_pvalues(p=p, mcp=mcp) elif method == 'gc': self.method = "Gaussian-Copula ERPAC" logger.info(f" Compute {self.method}") # copnorm phases and amplitudes then compute erpac sco = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2)) amp = copnorm(amp)[..., np.newaxis, :] self._erpac = ergcpac(sco, amp, smooth=smooth, n_jobs=n_jobs) # compute permutations (if needed) if isinstance(n_perm, int) and (n_perm > 0): logger.info(f" Compute {n_perm} permutations") self._surrogates = _ergcpac_perm(sco, amp, smooth=smooth, n_jobs=n_jobs, n_perm=n_perm) self.infer_pvalues(p=p, mcp=mcp) return self.erpac
def fit(self, pha, amp, n_perm=200, p=.05, mcp='maxstat', n_jobs=-1, random_state=None, verbose=None): """Compute PAC on filtered data. Parameters ---------- pha : array_like Array of phases of shape (n_pha, n_epochs, n_times). Angles should be in rad. amp : array_like Array of amplitudes of shape (n_amp, n_epochs, n_times). n_perm : int | 200 Number of surrogates to compute. p : float | 0.05 Statistical threshold mcp : {'fdr', 'bonferroni'} Correct the p-values for multiple comparisons. Use either : * 'maxstat' : maximum statistics * 'fdr' : FDR correction (need MNE-Python) * 'bonferroni' : Bonferroni correction (need MNE-Python) n_jobs : int | -1 Number of jobs to compute PAC in parallel. For very large data, set this parameter to 1 in order to prevent large memory usage. random_state : int | None Fix the random state of the machine for reproducible results. Returns ------- pac : array_like Phase-Amplitude Coupling measure of shape (n_amp, n_pha, n_epochs) Attributes ---------- pac : array_like Unormalized Phase-Amplitude Coupling measure of shape (n_amp, n_pha, n_epochs) pvalues : array_like Array of p-values of shape (n_amp, n_pha) surrogates : array_like Array of surrogates of shape (n_perm, n_amp, n_pha, n_epochs) """ set_log_level(verbose) # --------------------------------------------------------------------- # input checking pha, amp = self._phampcheck(pha, amp) self._pvalues, self._surrogates = None, None # for the plv, extract the phase of the amplitude if self._idpac[0] == 5: amp = np.angle(hilbertm(amp)) # --------------------------------------------------------------------- # check if permutations should be computed if self._idpac[1] == 0: n_perm = None if not isinstance(n_perm, int) or not (n_perm > 0): self._idpac = (self._idpac[0], 0, 0) compute_surro = False else: compute_surro = True # --------------------------------------------------------------------- # copnorm if gaussian copula is used if self._idpac[0] == 6: logger.debug(f" copnorm the phase and the amplitude") pha = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2)) amp = copnorm(amp[..., np.newaxis, :]) # --------------------------------------------------------------------- # true pac estimation logger.info(f' true PAC estimation using {self.method}') fcn = get_pac_fcn(self.idpac[0], self.n_bins, p) pac = fcn(pha, amp) self._pac = pac.copy() # --------------------------------------------------------------------- # compute surrogates (if needed) if compute_surro: if random_state is None: random_state = int(np.random.randint(0, 10000, size=1)) logger.info(f" compute surrogates ({self.str_surro}, {n_perm} " f"permutations, random_state={random_state})") surro = compute_surrogates(pha, amp, self.idpac[1], fcn, n_perm, n_jobs, random_state) self._surrogates = surro # infer pvalues self.infer_pvalues(p, mcp=mcp) # --------------------------------------------------------------------- # normalize (if needed) if self._idpac[2] != 0: # Get the mean / deviation of surrogates logger.info(" normalize true PAC estimation by surrogates " f"({self.str_norm})") normalize(self.idpac[2], pac, surro) return pac
def fit(self, pha, amp, method='circular', smooth=None, n_jobs=-1, n_perm=None, p=.05, verbose=None): """Compute the Event-Related Phase-Amplitude Coupling (ERPAC). The ERPAC [#f6]_ is used to measure PAC across trials and is interesting for real-time estimation. Parameters ---------- pha, amp : array_like Respectively the phase of slower oscillations of shape (n_pha, n_epochs, n_times) and the amplitude of faster oscillations of shape (n_pha, n_epochs, n_times). method : {'circular', 'gc'} Name of the method for computing erpac. Use 'circular' for reproducing [#f6]_ or 'gc' for a Gaussian-Copula based erpac. smooth : int | None Half number of time-points to use to produce a smoothing. Only active with the Gaussian-Copula ('gc') method. n_perm : int | None Number of permutations to compute for assessing p-values for the gaussian-copula ('gc') method. Statistics are performed by randomly swapping phase trials p : float | 0.05 Statistical threshold for the gaussian-copula ('gc') method Returns ------- erpac : array_like The ERPAC estimation of shape (n_amp, n_pha, n_times) References ---------- .. [#f6] `Voytek et al, 2013 <https://www.ncbi.nlm.nih.gov/pubmed/ 22986076>`_ .. [#f7] `Ince et al, 2017 <https://onlinelibrary.wiley.com/doi/full/10 .1002/hbm.23471>`_ """ set_log_level(verbose) pha, amp = self._phampcheck(pha, amp) self.method = method self._pvalues = None # move the trial axis to the end (n_freqs, n_times, n_epochs) pha, amp = np.moveaxis(pha, 1, -1), np.moveaxis(amp, 1, -1) # method switch if method == 'circular': self.method = "ERPAC (Voytek et al. 2013)" logger.info(f" Compute {self.method}") self._erpac, self._pvalues = erpac(pha, amp) elif method == 'gc': self.method = "Gaussian-Copula ERPAC (Ince et al. 2017)" logger.info(f" Compute {self.method}") # copnorm phases and amplitudes then compute erpac sco = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2)) amp = copnorm(amp)[..., np.newaxis, :] self._erpac = ergcpac(sco, amp, smooth=smooth, n_jobs=n_jobs) # compute permutations (if needed) if isinstance(n_perm, int) and (n_perm > 0): logger.info(f" Compute {n_perm} permutations") self._surrogates = _ergcpac_perm(sco, amp, smooth=smooth, n_jobs=n_jobs, n_perm=n_perm) self.infer_pvalues(p=p) return self.erpac
def fit(self, pha, amp, n_perm=200, p=.05, n_jobs=-1, verbose=None): """Compute PAC on filtered data. Parameters ---------- pha : array_like Array of phases of shape (n_pha, n_epochs, n_times). Angles should be in rad. amp : array_like Array of amplitudes of shape (n_amp, n_epochs, n_times). n_perm : int | 200 Number of surrogates to compute. p : float | 0.05 Statistical threhold n_jobs : int | -1 Number of jobs to compute PAC in parallel. For very large data, set this parameter to 1 in order to prevent large memory usage. Returns ------- pac : array_like Phase-Amplitude Coupling measure of shape (n_amp, n_pha, n_epochs) Attributes ---------- pac : array_like Unormalized Phase-Amplitude Coupling measure of shape (n_amp, n_pha, n_epochs) pvalues : array_like Array of p-values of shape (n_amp, n_pha) surrogates : array_like Array of surrogates of shape (n_perm, n_amp, n_pha, n_epochs) """ set_log_level(verbose) # --------------------------------------------------------------------- # input checking pha, amp = self._phampcheck(pha, amp) self._pvalues, self._surrogates = None, None # for the phase synchrony, extract the phase of the amplitude if self._idpac[0] == 5: amp = np.angle(hilbertm(amp)) # --------------------------------------------------------------------- # check if permutations should be computed if self._idpac[1] == 0: n_perm = None if not isinstance(n_perm, int) or not (n_perm > 0): self._idpac = (self._idpac[0], 0, 0) compute_surro = False else: compute_surro = True # --------------------------------------------------------------------- # copnorm if gaussian copula is used if self._idpac[0] == 6: logger.info(f" copnorm the phase and the amplitude") pha = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2)) amp = copnorm(amp[..., np.newaxis, :]) # --------------------------------------------------------------------- # true pac estimation logger.info(f' true PAC estimation using {self.method}') fcn = get_pac_fcn(self.idpac[0], self.n_bins, p) pac = fcn(pha, amp) self._pac = pac # --------------------------------------------------------------------- # compute surrogates (if needed) if compute_surro: logger.info(f" compute surrogates ({self.str_surro}, {n_perm} " "permutations)") surro = compute_surrogates(pha, amp, self.idpac[1], fcn, n_perm, n_jobs) self._surrogates = surro # infer pvalues self.infer_pvalues(p) # --------------------------------------------------------------------- # normalize (if needed) if self._idpac[2] != 0: # Get the mean / deviation of surrogates logger.info(" normalize true PAC estimation by surrogates " f"({self.str_norm})") normalize(self.idpac[2], pac, surro) return pac
def ergcpac(pha, amp, smooth=None, n_jobs=-1): """Event Related PAC computed using the Gaussian Copula Mutual Information. Parameters ---------- pha, amp : array_like Respectively the arrays of phases of shape (n_pha, n_times, n_epochs) and the array of amplitudes of shape (n_amp, n_times, n_epochs). Returns ------- rho : array_like Array of correlation coefficients of shape (n_amp, n_pha, n_times) References ---------- Ince RAA, Giordano BL, Kayser C, Rousselet GA, Gross J, Schyns PG (2017) A statistical framework for neuroimaging data analysis based on mutual information estimated via a gaussian copula: Gaussian Copula Mutual Information. Human Brain Mapping 38:1541–1573. """ # Move the trial axis to the end : pha = np.moveaxis(pha, 1, -1) amp = np.moveaxis(amp, 1, -1) # get shapes n_pha, n_times, n_epochs = pha.shape n_amp = amp.shape[0] # conversion for computing mi sco = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2)) amp = copnorm(amp)[..., np.newaxis, :] # 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 sco = np.moveaxis(sco, 1, -2) amp = np.moveaxis(amp, 1, -2) # function to run in parallel across times def _fcn(xp, xa): # noqa _erpac = np.zeros((n_amp, n_pha), dtype=float) 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, **JOBLIB_CFG)(delayed(_fcn)(sco[..., t, :], amp[..., t, :]) for t in times) # reconstruct the smoothed ERGCPAC 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(sco[p, ...], amp[a, ...]) return ergcpac