import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat
from scipy.fft import fft, ifft
from scipy.stats import norm
Chapter 30
Chapter 30
Analyzing Neural Time Series Data
Python code for Chapter 30 – converted from original Matlab by AE Studio (and ChatGPT)
Original Matlab code by Mike X Cohen
This code accompanies the book, titled “Analyzing Neural Time Series Data” (MIT Press).
Using the code without following the book may lead to confusion, incorrect data analyses, and misinterpretations of results.
Mike X Cohen and AE Studio assume no responsibility for inappropriate or incorrect use of this code.
Import necessary libraries
Figure 30.1
# Load data
= loadmat('../data/sampleEEGdata.mat')['EEG'][0, 0]
EEG
# Define channel to plot
= 'O1'
channel2plot
# Wavelet parameters
= 25
freq2plot
# Other wavelet parameters
= np.arange(-1, 1 + 1/EEG['srate'][0][0], 1/EEG['srate'][0][0])
time = (len(time)-1)//2
half_of_wavelet_size = len(time)
n_wavelet = EEG['pnts'][0][0]*EEG['trials'][0][0]
n_data = n_wavelet + n_data - 1
n_convolution
# Find sensor index
= EEG['chanlocs'][0]['labels']==channel2plot
sensoridx
# FFT of data
= fft(EEG['data'][sensoridx, :, :].flatten('F'), n_convolution)
fft_EEG
# Create wavelet and get its FFT
= np.exp(2*1j*np.pi*freq2plot*time) * np.exp(-time**2 / (2*(4/(2*np.pi*freq2plot))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet
= ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result = np.reshape(convolution_result, (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
convolution_result
# Plotting
=(10, 6))
plt.figure(figsize
211)
plt.subplot('times'][0], np.real(convolution_result[:, 0])) # Filtered signal from the first trial
plt.plot(EEG['Time (ms)'), plt.ylabel('Filtered signal amplitude')
plt.xlabel(-200, 1000])
plt.xlim([-50, 50])
plt.ylim([
212)
plt.subplot('times'][0], np.abs(convolution_result[:, 0])**2) # Power from the first trial
plt.plot(EEG['Time (ms)'), plt.ylabel('Power')
plt.xlabel(-200, 1000])
plt.xlim([0, 3000])
plt.ylim([
plt.tight_layout() plt.show()
Figure 30.2
# Load data
= loadmat('../data/accumbens_eeg.mat')['eeg'][0]
eeg = 1000
srate
# Wavelet parameters
= 70
freq2plot
# Other wavelet parameters
= np.arange(-1, 1 + 1/srate, 1/srate)
time = (len(time)-1)//2
half_of_wavelet_size = len(time)
n_wavelet = len(eeg)
n_data = n_wavelet + n_data - 1
n_convolution
# FFT of data
= fft(eeg, n_convolution)
fft_EEG
# Create wavelet and get its FFT
= np.exp(2*1j*np.pi*freq2plot*time) * np.exp(-time**2 / (2*(4/(2*np.pi*freq2plot))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet
= ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result
= np.arange(0, len(eeg)) / srate
eegtime
# Plotting
=(10, 8))
plt.figure(figsize
311)
plt.subplot(
plt.plot(eegtime, eeg)'Time (ms)'), plt.ylabel('Broadband signal amplitude')
plt.xlabel(0, 1])
plt.xlim([
312)
plt.subplot(# Filtered signal from the first trial
plt.plot(eegtime, np.real(convolution_result)) 'Time (ms)'), plt.ylabel('Filtered signal amplitude')
plt.xlabel(0, 1])
plt.xlim([
313)
plt.subplot(abs(convolution_result)**2) # Power from the first trial
plt.plot(eegtime, np.'Time (ms)'), plt.ylabel('Power')
plt.xlabel(0, 1])
plt.xlim([
plt.tight_layout() plt.show()
Figure 30.3
# We will first test for cross-frequency coupling between two specific frequency bands
= 10 # in Hz
freq4phase = 70
freq4power
# Wavelet and FFT parameters
= 1000
srate = np.arange(-1, 1 + 1/srate, 1/srate)
time = (len(time)-1)//2
half_of_wavelet_size = len(time)
n_wavelet = len(eeg)
n_data = n_wavelet + n_data - 1
n_convolution = fft(eeg, n_convolution)
fft_data
# Wavelet for phase and its FFT
= np.exp(2*1j*np.pi*freq4phase*time) * np.exp(-time**2 / (2*(4/(2*np.pi*freq4phase))**2))
wavelet4phase = fft(wavelet4phase, n_convolution)
fft_wavelet4phase
# Wavelet for power and its FFT
= np.exp(2*1j*np.pi*freq4power*time) * np.exp(-time**2 / (2*(4/(2*np.pi*freq4power))**2))
wavelet4power = fft(wavelet4power, n_convolution)
fft_wavelet4power
# Get phase values
= ifft(fft_wavelet4phase * fft_data, n_convolution)
convolution_result_fft = np.angle(convolution_result_fft[half_of_wavelet_size:-half_of_wavelet_size])
phase
# Get power values (note: 'power' is a built-in function so we'll name this variable 'amp')
= ifft(fft_wavelet4power * fft_data, n_convolution)
convolution_result_fft = np.abs(convolution_result_fft[half_of_wavelet_size:-half_of_wavelet_size])**2
pwr
# Plot power and phase
=(12, 4))
plt.figure(figsize
131)
plt.subplot(
plt.plot(eegtime, phase)- np.mean(pwr)) / np.std(pwr), 'r')
plt.plot(eegtime, (pwr '10 Hz phase', '70 Hz power'])
plt.legend([0, 1])
plt.xlim([-4, 6])
plt.ylim([
# Plot power as a function of phase in polar space
132, projection='polar')
plt.subplot('.')
plt.polar(phase, pwr,
# Plot histogram of power over phase
= 30
n_hist_bins = np.linspace(np.min(phase), np.max(phase), n_hist_bins + 1)
phase_edges = np.zeros(n_hist_bins)
amp_by_phases
for i in range(n_hist_bins - 1):
= np.mean(pwr[(phase > phase_edges[i]) & (phase < phase_edges[i + 1])])
amp_by_phases[i]
133)
plt.subplot(-1], amp_by_phases, align='edge', width=np.diff(phase_edges)[0])
plt.bar(phase_edges[:-np.pi, np.pi])
plt.xlim([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
plt.xticks([f'Phase at {freq4phase} Hz (rad.)')
plt.xlabel(f'Power at {freq4power} Hz')
plt.ylabel(
plt.tight_layout() plt.show()
Figure 30.4
# Phase bias is the phase time series without the negative half of the cycle
= phase[phase >= -np.pi/2]
phase_bias
# Power bias is the corresponding power time series
= pwr[phase >= -np.pi/2]
power_bias
# Plot power as a function of phase in polar space
=(12, 4))
plt.figure(figsize
131, projection='polar')
plt.subplot('.')
plt.polar(phase, pwr, f'PAC = {np.round(np.abs(np.mean(pwr * np.exp(1j * phase)))):.0f}')
plt.title(
132, projection='polar')
plt.subplot(* 10, '.')
plt.polar(phase, pwr f'PAC = {np.round(np.abs(np.mean(pwr * 10 * np.exp(1j * phase)))):.0f}')
plt.title(
133, projection='polar')
plt.subplot('.')
plt.polar(phase_bias, power_bias, f'PAC = {np.round(np.abs(np.mean(power_bias * np.exp(1j * phase_bias)))):.0f}')
plt.title(
plt.show()
Figure 30.5
# Observed cross-frequency coupling (note the similarity to Euler's formula)
= np.abs(np.mean(pwr * np.exp(1j * phase)))
obsPAC = np.abs(np.mean(power_bias * np.exp(1j * phase_bias)))
obsPAC_bias
= 1000
num_iter = np.zeros((2, num_iter))
permutedPAC
# Permutation test
for i in range(num_iter):
# Select random time point
= np.random.choice(np.round(len(eeg) * 0.8).astype(int), 1) + np.round(len(eeg) * 0.1).astype(int)
random_timepoint = np.random.choice(np.round(len(power_bias) * 0.8).astype(int), 1) + np.round(len(power_bias) * 0.1).astype(int)
random_timepoint_bias
# Shuffle power
= np.concatenate((pwr[random_timepoint[0]:], pwr[:random_timepoint[0]]))
timeshiftedpwr = np.concatenate((power_bias[random_timepoint_bias[0]:], power_bias[:random_timepoint_bias[0]]))
timeshiftedpwr_bias
# Compute PAC
0, i] = np.abs(np.mean(timeshiftedpwr * np.exp(1j * phase)))
permutedPAC[1, i] = np.abs(np.mean(timeshiftedpwr_bias * np.exp(1j * phase_bias)))
permutedPAC[
# Compute PACz
= np.zeros(2)
pacz 0] = (obsPAC - np.mean(permutedPAC[0, :])) / np.std(permutedPAC[0, :])
pacz[1] = (obsPAC_bias - np.mean(permutedPAC[1, :])) / np.std(permutedPAC[1, :])
pacz[
# Plotting
=(12, 8))
plt.figure(figsize
221)
plt.subplot(0, :], bins=50)
plt.hist(permutedPAC[='m', linewidth=3)
plt.axvline(obsPAC, color'Observed value', 'Permuted values'])
plt.legend(['Modulation strength'), plt.ylabel('Number of observations')
plt.xlabel(f'PAC_z = {pacz[0]}')
plt.title(
222)
plt.subplot(1, :], bins=50)
plt.hist(permutedPAC[='m', linewidth=3)
plt.axvline(obsPAC_bias, color'Observed value', 'Permuted values'])
plt.legend(['Modulation strength'), plt.ylabel('Number of observations')
plt.xlabel(f'PAC_z = {pacz[1]}')
plt.title(
# Plot histogram of power over phase
= 30
n_hist_bins = np.linspace(np.min(phase), np.max(phase), n_hist_bins + 1)
phase_edges = np.zeros(n_hist_bins)
amp_by_phases
for i in range(n_hist_bins - 1):
= np.mean(pwr[(phase > phase_edges[i]) & (phase < phase_edges[i + 1])])
amp_by_phases[i]
223)
plt.subplot(-1], amp_by_phases, align='edge', width=np.diff(phase_edges)[0])
plt.bar(phase_edges[:-np.pi, np.pi])
plt.xlim([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
plt.xticks([f'Phase at {freq4phase} Hz (rad.)')
plt.xlabel(f'Power at {freq4power} Hz')
plt.ylabel(
# Plot histogram of power over phase bias
= np.linspace(np.min(phase_bias), np.max(phase_bias), n_hist_bins + 1)
phase_edges_bias = np.zeros(n_hist_bins)
amp_by_phases_bias
for i in range(n_hist_bins - 1):
= np.mean(power_bias[(phase_bias > phase_edges_bias[i]) & (phase_bias < phase_edges_bias[i + 1])])
amp_by_phases_bias[i]
224)
plt.subplot(-1], amp_by_phases_bias, align='edge', width=np.diff(phase_edges_bias)[0])
plt.bar(phase_edges_bias[:-np.pi, np.pi])
plt.xlim([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
plt.xticks([f'Phase at {freq4phase} Hz (rad.)')
plt.xlabel(f'Power at {freq4power} Hz')
plt.ylabel(
plt.show()
Figure 30.6
# Initialize the array to store permuted PAC values for three different permutation methods
= np.zeros((3, num_iter))
permutedPAC
# Permutation tests
for i in range(num_iter):
# Permutation method 1: select random time point
= np.random.choice(np.round(len(eeg) * 0.8).astype(int), 1) + np.round(len(eeg) * 0.1).astype(int)
random_timepoint = np.concatenate((pwr[random_timepoint[0]:], pwr[:random_timepoint[0]]))
timeshiftedpwr 0, i] = np.abs(np.mean(timeshiftedpwr * np.exp(1j * phase)))
permutedPAC[
# Permutation method 2: totally randomize power time series
1, i] = np.abs(np.mean(pwr[np.random.permutation(len(pwr))] * np.exp(1j * phase)))
permutedPAC[
# Permutation method 3: FFT-based power time series randomization
= fft(pwr) # compute FFT
f = np.abs(f) # extract amplitudes
A = np.cos(np.angle(f)) + 1j * np.sin(np.angle(f)) # extract phases
zphs = np.real(ifft(A * zphs[np.random.permutation(len(zphs))])) # recombine using randomized phases
powernew = powernew - np.min(powernew)
powernew
2, i] = np.abs(np.mean(powernew * np.exp(1j * phase)))
permutedPAC[
# Compute PACz and plot
=(15, 8))
plt.figure(figsize
for i in range(3):
2, 3, i + 1)
plt.subplot(
# Plot example power time series
if i == 0:
plt.plot(eegtime, timeshiftedpwr)'H_0: Time-shifted')
plt.title(elif i == 1:
len(pwr))])
plt.plot(eegtime, pwr[np.random.permutation('H_0: Randomized')
plt.title(elif i == 2:
plt.plot(eegtime, powernew)'H_0: FFT-derived randomization')
plt.title(
0, eegtime[-1]])
plt.xlim([min(pwr), np.max(pwr)])
plt.ylim([np.
# Plot null-hypothesis distribution
2, 3, i + 4)
plt.subplot(= (obsPAC - np.mean(permutedPAC[i, :])) / np.std(permutedPAC[i, :])
pacz = np.histogram(permutedPAC[i, :], bins=50)
y, x -1], y, width=np.diff(x), align='edge')
plt.bar(x[:='m', linewidth=3)
plt.axvline(obsPAC, color'Observed value', 'Permuted values'])
plt.legend(['Modulation strength'), plt.ylabel('Number of observations')
plt.xlabel(f'PAC_z = {pacz}')
plt.title(
plt.show()
Figure 30.7
# Define the time points to plot
= np.arange(-200, 1201, 100)
times2plot = 10 # Hz for phase
freq4phase = 25 # Hz for power
freq4power
= 3 # number of cycles at phase-frequency
cfc_numcycles
= np.zeros(len(times2plot))
pacz = np.zeros(len(times2plot))
itpc
# Convert cfc times to indices
= cfc_numcycles * (1000 / freq4phase)
cfc_time_window = np.round(cfc_time_window / (1000 / EEG['srate'][0][0])).astype(int)
cfc_time_window_idx
# Other wavelet parameters
= np.arange(-1, 1 + 1/EEG['srate'][0][0], 1/EEG['srate'][0][0])
time = (len(time)-1)//2
half_of_wavelet_size = len(time)
n_wavelet = EEG['pnts'][0][0]*EEG['trials'][0][0]
n_data = n_wavelet + n_data - 1
n_convolution
# FFT of scalp EEG data
= fft(EEG['data'][sensoridx, :, :].flatten('F'), n_convolution)
fft_EEG
for timei, timepoint in enumerate(times2plot):
= np.argmin(np.abs(EEG['times'][0] - timepoint))
cfc_centertime_idx
# Convolution for lower frequency phase
= np.exp(2 * 1j * np.pi * freq4phase * time) * np.exp(-time**2 / (2 * (4 / (2 * np.pi * freq4phase))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet = ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result = np.reshape(convolution_result, (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
lower_freq_phase
# Convolution for upper frequency power
= np.exp(2 * 1j * np.pi * freq4power * time) * np.exp(-time**2 / (2 * (4 / (2 * np.pi * freq4power))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet = ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result = np.reshape(convolution_result, (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
upper_freq_power
# Extract temporally localized power and phase
= np.abs(upper_freq_power[cfc_centertime_idx - np.floor(cfc_time_window_idx/2+0.5).astype(int):cfc_centertime_idx + np.floor(cfc_time_window_idx/2+0.5).astype(int) + 1, :])**2
power_ts = np.angle(lower_freq_phase[cfc_centertime_idx - np.floor(cfc_time_window_idx/2+0.5).astype(int):cfc_centertime_idx + np.floor(cfc_time_window_idx/2+0.5).astype(int) + 1, :])
phase_ts
# Compute observed PAC
= np.abs(np.mean(power_ts.flatten('F') * np.exp(1j * phase_ts.flatten('F'))))
obsPAC
# Compute lower frequency ITPC
= np.mean(np.abs(np.mean(np.exp(1j * phase_ts), axis=1)))
itpc[timei]
# Permutation test
= np.zeros(num_iter)
permutedPAC for i in range(num_iter):
= np.random.choice(np.round(cfc_time_window_idx * 0.8).astype(int), EEG['trials'][0][0]) + np.round(cfc_time_window_idx * 0.1).astype(int)
random_timepoint for triali in range(EEG['trials'][0][0]):
= np.roll(power_ts[:, triali], random_timepoint[triali])
power_ts[:, triali]
= np.abs(np.mean(power_ts.flatten('F') * np.exp(1j * phase_ts.flatten('F'))))
permutedPAC[i]
= (obsPAC - np.mean(permutedPAC)) / np.std(permutedPAC)
pacz[timei]
# Plotting
=(10, 6))
plt.figure(figsize
211)
plt.subplot('-o', markerfacecolor='w')
plt.plot(times2plot, pacz,
# Calculate the z-value threshold for p=0.05, correcting for multiple comparisons
= norm.ppf(1 - (0.05 / len(times2plot)))
zval
='k', linestyle=':')
plt.axhline(zval, color0, color='k')
plt.axhline('Time (ms)'), plt.ylabel('PAC_z')
plt.xlabel(f'PAC_z at electrode {channel2plot} between {freq4power} Hz power and {freq4phase} Hz phase')
plt.title(
# Also plot ITPC for comparison
212)
plt.subplot('-o', markerfacecolor='w')
plt.plot(times2plot, itpc, 'Time (ms)'), plt.ylabel('ITPC')
plt.xlabel(f'ITPC at electrode {channel2plot} at {freq4phase} Hz')
plt.title(
plt.tight_layout() plt.show()
Figure 30.8a
# Define the phase frequencies to explore
= np.arange(2, 21) # Hz
phase_freqs = 300 # ms post-stimulus
cfc_centertime = np.zeros(len(phase_freqs))
pacz = np.argmin(np.abs(EEG['times'][0] - cfc_centertime))
cfc_centertime_idx
# Loop over phase frequencies
for fi, pfreq in enumerate(phase_freqs):
# Convolution for lower frequency phase
= np.exp(2 * 1j * np.pi * pfreq * time) * np.exp(-time**2 / (2 * (4 / (2 * np.pi * pfreq))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet = ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result = np.reshape(convolution_result, (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
lower_freq_phase
# Extract temporally localized power and phase
= np.abs(upper_freq_power[cfc_centertime_idx - np.floor(cfc_time_window_idx/2+0.5).astype(int):cfc_centertime_idx + np.floor(cfc_time_window_idx/2+0.5).astype(int) + 1, :])**2
power_ts = np.angle(lower_freq_phase[cfc_centertime_idx - np.floor(cfc_time_window_idx/2+0.5).astype(int):cfc_centertime_idx + np.floor(cfc_time_window_idx/2+0.5).astype(int) + 1, :])
phase_ts
# Compute observed PAC
= np.abs(np.mean(power_ts.flatten('F') * np.exp(1j * phase_ts.flatten('F'))))
obsPAC
# Permutation test
= 2000
num_iter = np.zeros(num_iter)
permutedPAC for i in range(num_iter):
= np.random.choice(np.round(cfc_time_window_idx * 0.8).astype(int), EEG['trials'][0][0]) + np.round(cfc_time_window_idx * 0.1).astype(int)
random_timepoint for triali in range(EEG['trials'][0][0]):
= np.roll(power_ts[:, triali], random_timepoint[triali])
power_ts[:, triali]
= np.abs(np.mean(power_ts.flatten('F') * np.exp(1j * phase_ts.flatten('F'))))
permutedPAC[i]
= (obsPAC - np.mean(permutedPAC)) / np.std(permutedPAC)
pacz[fi]
# Plotting
=(10, 6))
plt.figure(figsize'-o')
plt.plot(phase_freqs, pacz, 0] - 0.5, phase_freqs[-1] + 0.5])
plt.xlim([phase_freqs['Lower frequency for phase (Hz)'), plt.ylabel('PAC_z')
plt.xlabel(= phase_freqs[np.argmax(pacz)]
max_phase_freq f'Best lower frequency phase coupled with {freq4power} Hz is {max_phase_freq} Hz')
plt.title( plt.show()
Figure 30.8b
# Define the power frequencies to explore
= np.arange(20, EEG['srate'][0][0]/2 + 1, 5) # Hz
power_freqs = np.zeros(len(power_freqs))
pacz
# convolution for lower frequency phase
= np.exp(2 * 1j * np.pi * freq4phase * time) * np.exp(-time**2 / (2 * (4 / (2 * np.pi * freq4phase))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet = ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result = np.reshape(convolution_result, (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
lower_freq_phase
# Loop over power frequencies
for fi, pwr_freq in enumerate(power_freqs):
# Convolution for upper frequency power
= np.exp(2 * 1j * np.pi * pwr_freq * time) * np.exp(-time**2 / (2 * (4 / (2 * np.pi * pwr_freq))**2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet = ifft(fft_wavelet * fft_EEG, n_convolution)
convolution_result = convolution_result[half_of_wavelet_size:-half_of_wavelet_size]
convolution_result = np.reshape(convolution_result, (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
upper_freq_power
# Extract temporally localized power and phase
= np.abs(upper_freq_power[cfc_centertime_idx - np.floor(cfc_time_window_idx/2+0.5).astype(int):cfc_centertime_idx + np.floor(cfc_time_window_idx/2+0.5).astype(int) + 1, :])**2
power_ts = np.angle(lower_freq_phase[cfc_centertime_idx - np.floor(cfc_time_window_idx/2+0.5).astype(int):cfc_centertime_idx + np.floor(cfc_time_window_idx/2+0.5).astype(int) + 1, :])
phase_ts
# Compute observed PAC
= np.abs(np.mean(power_ts.flatten('F') * np.exp(1j * phase_ts.flatten('F'))))
obsPAC
# Permutation test
= 2000
num_iter = np.zeros(num_iter)
permutedPAC for i in range(num_iter):
= np.random.choice(np.round(cfc_time_window_idx * 0.8).astype(int), EEG['trials'][0][0]) + np.round(cfc_time_window_idx * 0.1).astype(int)
random_timepoint for triali in range(EEG['trials'][0][0]):
= np.roll(power_ts[:, triali], random_timepoint[triali])
power_ts[:, triali]
= np.abs(np.mean(power_ts.flatten('F') * np.exp(1j * phase_ts.flatten('F'))))
permutedPAC[i]
= (obsPAC - np.mean(permutedPAC)) / np.std(permutedPAC)
pacz[fi]
# Plotting
=(10, 6))
plt.figure(figsize'-o')
plt.plot(power_freqs, pacz, 0] - 3, power_freqs[-1] + 3])
plt.xlim([power_freqs['Upper frequency for power (Hz)'), plt.ylabel('PAC_z')
plt.xlabel(= power_freqs[np.argmax(pacz)]
max_power_freq f'Best upper frequency power coupled with {freq4phase} Hz is {max_power_freq} Hz')
plt.title( plt.show()
Figure 30.9
You have to figure this one out on your own!
Figure 30.10
This figure is created by combining the code for figure 30.8. You need two loops, one for lower-frequency phase and one for upper-frequency power. Compute PAC at each phase-power pair, and then make an image of the resulting (z-scored) PAC values.
Figure 30.11
# Define the frequencies for phase and power
= np.arange(1, 21)
freqs4phase = np.arange(25, int(EEG['srate'][0][0] / 2) + 1)
freqs4power
# Initialize the matrix to store the number of power cycles per phase cycle
= np.zeros((len(freqs4power), len(freqs4phase)))
powcycles_per_phscycles
# Loop over phase and power frequencies
for phsi, pfreq in enumerate(freqs4phase):
for powi, pwr_freq in enumerate(freqs4power):
# Number of power cycles per phase cycle, scaled by sampling rate
= (pwr_freq / pfreq) / (1000 / EEG['srate'][0][0])
powcycles_per_phscycles[powi, phsi]
# Plotting
=(10, 6))
plt.figure(figsize=[freqs4phase[0], freqs4phase[-1], freqs4power[0], freqs4power[-1]], aspect='auto', origin='lower', cmap='gray')
plt.imshow(np.log10(powcycles_per_phscycles), extent
plt.colorbar() plt.show()
Figure 30.12
# Define wavelet parameters
= 70
upperfreq = 12
lowerfreq
# Other wavelet parameters
= np.arange(-1, 1 + 1/srate, 1/srate)
time = (len(time)-1)//2
half_of_wavelet_size = len(time)
n_wavelet = len(eeg)
n_data = n_wavelet + n_data - 1
n_convolution
# FFT of data
= fft(eeg, n_convolution)
fft_EEG
# Convolution for lower frequency phase (with 4 cycles)
= np.exp(2*1j*np.pi*lowerfreq*time) * np.exp(-time**2 / (2*(4/(2*np.pi*lowerfreq))**2))
waveletL = fft(waveletL, n_convolution)
fft_waveletL = ifft(fft_waveletL * fft_EEG, n_convolution)
convolution_resultL = np.angle(convolution_resultL[half_of_wavelet_size:-half_of_wavelet_size])
lowerfreq_phase
# Convolution for upper frequency (with 4 cycles)
= np.exp(2*1j*np.pi*upperfreq*time) * np.exp(-time**2 / (2*(4/(2*np.pi*upperfreq))**2))
waveletH = fft(waveletH, n_convolution)
fft_waveletH = ifft(fft_waveletH * fft_EEG, n_convolution)
convolution_resultH = np.abs(convolution_resultH[half_of_wavelet_size:-half_of_wavelet_size])
upperfreq_amp
# Filter the upper frequency power in the lower frequency range
= ifft(fft_waveletL * fft(upperfreq_amp, n_convolution), n_convolution)
convolution_result_filtered = np.angle(convolution_result_filtered[half_of_wavelet_size:-half_of_wavelet_size])
upperfreq_amp_phase
# Plotting
=(12, 10))
plt.figure(figsize
411)
plt.subplot(
plt.plot(eegtime, lowerfreq_phase)0, 2])
plt.xlim([f'{lowerfreq} Hz phase')
plt.title(
412)
plt.subplot(
plt.plot(eegtime, upperfreq_amp)0, 2])
plt.xlim([f'{upperfreq} Hz power')
plt.title(
413)
plt.subplot(-half_of_wavelet_size]))
plt.plot(eegtime, np.real(convolution_result_filtered[half_of_wavelet_size:0, 2])
plt.xlim([f'{upperfreq} Hz power filtered at {lowerfreq} Hz')
plt.title(
414)
plt.subplot(
plt.plot(eegtime, upperfreq_amp_phase)'r')
plt.plot(eegtime, lowerfreq_phase, 'Upper', 'Lower'])
plt.legend([0, 2])
plt.xlim([f'Phase of {lowerfreq} Hz component in {upperfreq} Hz power')
plt.title(
plt.tight_layout()
plt.show()
# Compute synchronization
= np.abs(np.mean(np.exp(1j * (lowerfreq_phase - upperfreq_amp_phase))))
phasephase_synch print(f'Phase-phase coupling between {lowerfreq} Hz and {upperfreq} Hz is {phasephase_synch:.5f}')
Phase-phase coupling between 12 Hz and 70 Hz is 0.27535