import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import iqr, rankdata
from scipy.io import loadmat
from scipy.fftpack import fft, ifft
from mne import create_info, EvokedArray
from mne.channels import make_dig_montage
from mutualinformationx import mutualinformationx
Chapter 29
Chapter 29
Analyzing Neural Time Series Data
Python code for Chapter 29 – 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 29.2
# create two signals
= np.arange(0, 1.0001, 0.0001)
time = np.sin(2 * np.pi * 10 * time)
signal1 = np.random.rand(len(signal1)) * 2 - 1 # uniform random numbers in the same scale as the sine wave
signal2
# plot signals
=(10, 8))
plt.figure(figsize
221)
plt.subplot(
plt.plot(time, signal1)0], time[-1]])
plt.xlim([time[-1, 1])
plt.ylim([
222)
plt.subplot('r')
plt.plot(time, signal2, 0], time[-1]])
plt.xlim([time[-1, 1])
plt.ylim([
# bin data
= 50
nbins = np.histogram(signal1, bins=nbins)
hdat1, x1 = np.histogram(signal2, bins=nbins)
hdat2, x2
# convert to probability
= hdat1 / np.sum(hdat1)
hdat1 = hdat2 / np.sum(hdat2)
hdat2
# plot histograms
223)
plt.subplot(-1], hdat1, label='Sine wave')
plt.plot(x1[:-1], hdat2, 'r', label='Random data')
plt.plot(x2[:
plt.legend()-1, 1])
plt.xlim(['Value bins')
plt.xlabel('Probability')
plt.ylabel(
224)
plt.subplot(
plt.plot(np.sort(signal1))'r')
plt.plot(np.sort(signal2), 0, len(signal1)])
plt.xlim([-1, 1])
plt.ylim([
# compute entropy
= np.zeros(2)
entro 0] = -np.sum(hdat1 * np.log2(hdat1 + np.finfo(float).eps))
entro[1] = -np.sum(hdat2 * np.log2(hdat2 + np.finfo(float).eps))
entro[
print(f'Entropies of sine wave and random noise are {entro[0]} and {entro[1]}.')
plt.tight_layout() plt.show()
Entropies of sine wave and random noise are 5.371662108209934 and 5.641046372916797.
Figure 29.3
# range of bin numbers
= np.arange(10, 2001)
nbins
= np.zeros(len(nbins))
entropyByBinSize
for nbini in range(len(nbins)):
# bin data, transform to probability, and eliminate zeros
= np.histogram(signal1, bins=nbins[nbini])
hdat, _ = hdat / np.sum(hdat)
hdat
# compute entropy
= -np.sum(hdat * np.log2(hdat + np.finfo(float).eps))
entropyByBinSize[nbini]
plt.figure()
plt.plot(nbins, entropyByBinSize)0], nbins[-1]])
plt.xlim([nbins['Number of bins')
plt.xlabel('Entropy')
plt.ylabel( plt.show()
Figure 29.4
# optimal number of bins for histogram based on a few different guidelines
= len(signal1)
n = np.max(signal1) - np.min(signal1)
maxmin_range
# Freedman-Diaconis, Scott, and Sturges rules for bin width
= np.ceil(maxmin_range / (2.0 * iqr(signal1) * n ** (-1/3)))
fd_bins = np.ceil(maxmin_range / (3.5 * np.std(signal1) * n ** (-1/3)))
scott_bins = np.ceil(1 + np.log2(n))
sturges_bins
=(10, 8))
plt.figure(figsize
211)
plt.subplot(# plot up to 50 bins
= np.argmin(np.abs(nbins - 50)) # index of nbins that most closely matches 50
maxNbins ='Entropy')
plt.plot(nbins[:maxNbins], entropyByBinSize[:maxNbins], label
='m', linewidth=2, label='Freedman-Diaconis')
plt.axvline(fd_bins, color='k', linewidth=2, label='Scott')
plt.axvline(scott_bins, color='r', linewidth=2, label='Sturges')
plt.axvline(sturges_bins, color
plt.legend()0], nbins[maxNbins]])
plt.xlim([nbins['Number of bins')
plt.xlabel('Entropy')
plt.ylabel(
223)
plt.subplot(= np.histogram(signal1, bins=int(fd_bins))
y, x -1], y, width=np.diff(x), align='edge', edgecolor='none')
plt.bar(x[:min(signal1) * 1.1, np.max(signal1) * 1.1])
plt.xlim([np.'Value')
plt.xlabel('Count')
plt.ylabel('Optimal number of bins (FD rule)')
plt.title(
224)
plt.subplot(= np.histogram(signal1, bins=2000)
y, x -1], y, width=np.diff(x), align='edge', edgecolor='none')
plt.bar(x[:min(signal1) * 1.05, np.max(signal1) * 1.05])
plt.xlim([np.'Value')
plt.xlabel('Count')
plt.ylabel('Too many bins (2000)')
plt.title(
plt.tight_layout() plt.show()
Figure 29.5
# Load sample EEG data
= loadmat('../data/sampleEEGdata.mat')['EEG'][0, 0]
EEG
# Define time windows for entropy calculation
= [100, 400] # in ms
time4entropy = [-400, -100] # in ms
base4entropy = np.zeros(EEG['nbchan'][0][0])
topo_entropy
# Convert time windows to indices
= [np.argmin(np.abs(EEG['times'][0] - t)) for t in time4entropy]
timeidx = [np.argmin(np.abs(EEG['times'][0] - t)) for t in base4entropy]
baseidx
# Calculate entropy for each channel
for chani in range(EEG['nbchan'][0][0]):
# Entropy during task
= EEG['data'][chani, timeidx[0]:timeidx[1]+1, :].flatten('F')
tempdat = np.histogram(tempdat, bins=25)
hdat, _ = hdat / np.sum(hdat)
hdat = -np.sum(hdat * np.log2(hdat + np.finfo(float).eps))
task_entropy
# Entropy during pre-stim baseline
= EEG['data'][chani, baseidx[0]:baseidx[1]+1, :].flatten('F')
tempdat = np.histogram(tempdat, bins=25)
hdat, _ = hdat / np.sum(hdat)
hdat = -np.sum(hdat * np.log2(hdat + np.finfo(float).eps))
base_entropy
# Compute difference in entropy
= task_entropy - base_entropy
topo_entropy[chani]
# create channel montage
= [chan['labels'][0] for chan in EEG['chanlocs'][0]]
chan_labels = np.vstack([-1*EEG['chanlocs']['Y'], EEG['chanlocs']['X'], EEG['chanlocs']['Z']]).T
coords # remove channels outside of head
= np.where(np.array([chan['radius'][0][0] for chan in EEG['chanlocs'][0]]) > 0.54)
exclude_chans = np.delete(coords, exclude_chans, axis=0)
coords = np.delete(chan_labels, exclude_chans)
chan_labels = np.delete(topo_entropy, exclude_chans)
topo_entropy = make_dig_montage(ch_pos=dict(zip(chan_labels, coords)), coord_frame='head')
montage
# create MNE Info and Evoked object
= create_info(list(chan_labels), EEG['srate'], ch_types='eeg')
info
# Plot the topographic map of entropy differences
=(10, 8))
plt.figure(figsize= plt.subplot(111)
ax1 = EvokedArray(topo_entropy[:, np.newaxis], info, tmin=EEG['xmin'][0][0])
evoked
evoked.set_montage(montage)=False, contours=0, sphere=EEG['chanlocs'][0][0]['sph_radius'][0][0], cmap='jet', axes=ax1, show=False, times=-1, time_format='', colorbar=False)
evoked.plot_topomap(sensors
# Define sensor for entropy over time and parameters
= 'FCz'
sensor4entropy = np.arange(-300, 1250, 50) # in ms
times2save = 400 # ms
timewindow
# Convert ms to indices
= round(timewindow / (1000 / EEG['srate'][0][0]) / 2)
timewindowidx = [np.argmin(np.abs(EEG['times'][0] - t)) for t in times2save]
times2saveidx
# Find the index of the selected sensor
= EEG['chanlocs'][0]['labels']==sensor4entropy
electrodeidx
# Initialize array to store entropy values
= np.zeros((2, len(times2save)))
timeEntropy
# Calculate entropy over time for the selected sensor
for timei, tidx in enumerate(times2saveidx):
# Data for first 30 trials
= EEG['data'][electrodeidx, tidx - timewindowidx:tidx + timewindowidx + 1, :30].flatten('F')
tempdata = np.histogram(tempdata, bins=25)
hdat, _ = hdat / np.sum(hdat)
hdat 0, timei] = -np.sum(hdat * np.log2(hdat + np.finfo(float).eps))
timeEntropy[
# Data for last 30 trials
= EEG['data'][electrodeidx, tidx - timewindowidx:tidx + timewindowidx + 1, -30:].flatten('F')
tempdata = np.histogram(tempdata, bins=25)
hdat, _ = hdat / np.sum(hdat)
hdat 1, timei] = -np.sum(hdat * np.log2(hdat + np.finfo(float).eps))
timeEntropy[
# Plot the entropy over time
plt.figure()0, :], label='First 30 trials')
plt.plot(times2save, timeEntropy[1, :], label='Last 30 trials')
plt.plot(times2save, timeEntropy['Time (ms)')
plt.xlabel('Entropy (bits)')
plt.ylabel(f'Entropy over time from electrode {sensor4entropy}')
plt.title(
plt.legend() plt.show()
Figure 29.6
# right panel: random noise
= np.random.rand(len(signal1)) * 2 - 1
signal1 = np.random.rand(len(signal1)) * 2 - 1
signal2
# center panel: one pure sine wave and one sine wave plus random noise
# Uncomment the following lines to use these signals instead
= np.sin(2 * np.pi * 10 * time)
signal1 = signal1 + np.random.randn(len(signal1)) / 2
signal2
# left panel: one pure sine wave and its inverse
# Uncomment the following lines to use these signals instead
= np.sin(2 * np.pi * 10 * time)
signal1 = -signal1
signal2
# determine the optimal number of bins for each variable
= len(signal1)
n = np.max(signal1) - np.min(signal1)
maxmin_range = np.ceil(maxmin_range / (2.0 * iqr(signal1) * n ** (-1/3))) # Freedman-Diaconis
fd_bins1
= len(signal2)
n = np.max(signal2) - np.min(signal2)
maxmin_range = np.ceil(maxmin_range / (2.0 * iqr(signal2) * n ** (-1/3))) # Freedman-Diaconis
fd_bins2
# and use the average...
= int(np.ceil((fd_bins1 + fd_bins2) / 2))
fd_bins
# bin data (using np.histogram)
= np.linspace(min(signal1), max(signal1), fd_bins + 1)
edges1 = np.linspace(min(signal2), max(signal2), fd_bins + 1)
edges2 = np.histogram(signal1, edges1)
nPerBin1, _ = np.histogram(signal2, edges2)
nPerBin2, _
# Get the bin indices for each value in signal1 and signal2
= np.digitize(signal1, edges1) - 1 # -1 to convert to 0-based indexing
bin_indices1 = np.digitize(signal2, edges2) - 1 # -1 to convert to 0-based indexing
bin_indices2
# compute joint frequency table
= np.zeros((fd_bins, fd_bins))
jointprobs for i1 in range(fd_bins):
for i2 in range(fd_bins):
= np.sum((bin_indices1 == i1) & (bin_indices2 == i2))
jointprobs[i1, i2] /= np.sum(jointprobs)
jointprobs
# Plot the signals
=(12, 6))
plt.figure(figsize211)
plt.subplot(
plt.plot(time, signal1)0], time[-1]])
plt.xlim([time[-1, 1])
plt.ylim([212)
plt.subplot(
plt.plot(time, signal2)0], time[-1]])
plt.xlim([time[-1, 1])
plt.ylim([
plt.tight_layout()
plt.show()
# Plot the joint probability matrix
plt.figure()='gray', aspect='auto')
plt.imshow(jointprobs, cmap
plt.colorbar()'Signal 2 bin')
plt.xlabel('Signal 1 bin')
plt.ylabel(0, 0.01)
plt.clim(
plt.gca().invert_yaxis() plt.show()
Figure 29.7
# Plot the signals and their mutual information
=(10, 10))
plt.figure(figsize
221)
plt.subplot(= np.arange(0, 1.001, 0.001)
x = x
y '.')
plt.plot(x, y, 0, 1])
plt.xlim([0, 1])
plt.ylim([f'MI={mutualinformationx(x, y)[0]:.4f}, r_s={np.corrcoef(x, y)[0, 1]:.4f}')
plt.title(
222)
plt.subplot(= np.arange(0, 1.001, 0.001)
x = -x**3
y '.')
plt.plot(x, y, 0, 1])
plt.xlim([-1, 0])
plt.ylim([f'MI={mutualinformationx(x, y)[0]:.4f}, r_s={np.corrcoef(x, y)[0, 1]:.4f}')
plt.title(
223)
plt.subplot(= np.cos(np.arange(0, 2 * np.pi, 0.01))
x = np.sin(np.arange(0, 2 * np.pi, 0.01))
y '.')
plt.plot(x, y, -1, 1])
plt.xlim([-1, 1])
plt.ylim([f'MI={mutualinformationx(x, y)[0]:.4f}, r_s={np.corrcoef(x, y)[0, 1]:.4f}')
plt.title(
224)
plt.subplot(= np.concatenate([np.cos(np.arange(0, 2 * np.pi, 0.01)), np.cos(np.arange(0, 2 * np.pi, 0.01)) + 1])
x = np.concatenate([np.sin(np.arange(0, 2 * np.pi, 0.01)), np.sin(np.arange(0, 2 * np.pi, 0.01)) - 1])
y '.')
plt.plot(x, y, -1, 2])
plt.xlim([-2, 1])
plt.ylim([f'MI={mutualinformationx(x, y)[0]:.4f}, r_s={np.corrcoef(x, y)[0, 1]:.4f}')
plt.title(
plt.tight_layout() plt.show()
Figure 29.8
# Define error estimation functions
def entropy_error(b, n):
return (b - 1) / (2.0 * n * np.log(2))
def mutinfo_error(b, n):
return (b - 1)**2 / (2.0 * n * np.log(2))
# Define range of data points and fixed number of bins
= np.arange(20, 301)
n = 15
nfixed
# Calculate errors
= entropy_error(nfixed, n)
entropy_errors_fixed = entropy_error(np.ceil(1 + np.log2(n)), n)
entropy_errors_sturges = mutinfo_error(nfixed, n)
mutinfo_errors_fixed = mutinfo_error(np.ceil(1 + np.log2(n)), n)
mutinfo_errors_sturges
# Plot entropy error
=(12, 6))
plt.figure(figsize
211)
plt.subplot(=f'{nfixed} bins')
plt.plot(n, entropy_errors_fixed, label'r', label="Sturges' rule")
plt.plot(n, entropy_errors_sturges,
plt.legend()'Number of data points')
plt.xlabel('Entropy error (bits)')
plt.ylabel(0], n[-1]])
plt.xlim([n[
# Plot mutual information error
212)
plt.subplot(=f'{nfixed} bins')
plt.plot(n, mutinfo_errors_fixed, label'r', label="Sturges' rule")
plt.plot(n, mutinfo_errors_sturges,
plt.legend()'Number of data points')
plt.xlabel('Mutual information error')
plt.ylabel(0], n[-1]])
plt.xlim([n[
plt.tight_layout() plt.show()
Figure 29.9
# Define the signals
= np.concatenate([np.cos(np.arange(0, 2 * np.pi, 0.01)), np.cos(np.arange(0, 2 * np.pi, 0.01)) + 1])
x = np.concatenate([np.sin(np.arange(0, 2 * np.pi, 0.01)), np.sin(np.arange(0, 2 * np.pi, 0.01)) - 1])
y
# Define noise levels and initialize mutual information array
= np.arange(0, 1.01, 0.01)
noiselevels = np.zeros(len(noiselevels))
mi
# Calculate mutual information for different levels of noise
for ni in range(len(noiselevels)):
= y + np.random.randn(len(y)) * noiselevels[ni]
noisy_y = mutualinformationx(x, noisy_y)[0]
mi[ni]
# Plot the original signal without noise
=(12, 8))
plt.figure(figsize221)
plt.subplot('.')
plt.plot(x, y, -3, 3, -3, 3])
plt.axis([
# Plot the mutual information as a function of noise level
212)
plt.subplot(
plt.plot(noiselevels, mi)0], noiselevels[-1]])
plt.xlim([noiselevels['Noise level')
plt.xlabel('Mutual information')
plt.ylabel(
# Plot the signal with an intermediate level of noise
222)
plt.subplot(= round(len(noiselevels) / 2)
mid_noise_idx + np.random.randn(len(y)) * noiselevels[mid_noise_idx], '.')
plt.plot(x, y -3, 3, -3, 3])
plt.axis([
plt.tight_layout() plt.show()
Figure 29.9d
(takes a while to run)
# Define the range of data lengths and noise levels
= np.arange(300, 3000, 205)
nrange = np.arange(0, 1.01, 0.01)
noiselevels
# Initialize the mutual information matrix and bin number array
= np.zeros((len(nrange), len(noiselevels)))
mi = np.zeros(len(nrange)).astype(int) # number of histogram bins
b
# Loop over different data lengths
for ni, n in enumerate(nrange):
# Define time and signals
= np.linspace(0, 2 * np.pi, n)
t = np.concatenate([np.cos(t), np.cos(t) + 1])
x = np.concatenate([np.sin(t), np.sin(t) - 1])
y
# Loop over different noise levels
for noi, noise in enumerate(noiselevels):
if noi == 0: # keep number of bins constant across noise levels within each number of points
= mutualinformationx(x, y + np.random.randn(len(y)) * noise)
mi[ni, noi], _, b[ni] else:
= mutualinformationx(x, y + np.random.randn(len(y)) * noise, fd_bins=b[ni])[0]
mi[ni, noi]
# Convert to percent change from best-case scenario (no noise, large N)
= 100 * (mi - mi[-1, 0]) / mi[-1, 0]
mip
# Plot the percent decrease in MI due to noise
plt.figure()40, cmap='viridis', vmin=-100, vmax=0)
plt.contourf(noiselevels, nrange, mip,
plt.colorbar()'Noise level')
plt.xlabel('N (data length)')
plt.ylabel('Percent decrease in MI due to noise')
plt.title( plt.show()
Figure 29.10
# Define electrodes for mutual information analysis and other parameters
= ['Fz', 'O1']
electrodes4mi = 400 # in ms
timewindow = np.arange(-400, 1201, 100)
times2save
# Convert ms to indices
= round(timewindow / (1000 / EEG['srate'][0][0]) / 2)
timewindowidx = [np.argmin(np.abs(EEG['times'][0] - t)) for t in times2save]
times2saveidx
# Find the indices of the selected electrodes
= [EEG['chanlocs'][0]['labels']==electrodes4mi[0], EEG['chanlocs'][0]['labels']==electrodes4mi[1]]
electrodesidx
# Initialize outputs
= np.zeros((3, len(times2save)))
entropy = np.zeros((2, len(times2save)))
mi = np.zeros(len(times2save))
nbins
# Calculate mutual information over time
for timei, tidx in enumerate(times2saveidx):
= EEG['data'][electrodesidx[0], tidx - timewindowidx:tidx + timewindowidx + 1, :].flatten('F')
datax = EEG['data'][electrodesidx[1], tidx - timewindowidx:tidx + timewindowidx + 1, :].flatten('F')
datay
# Determine the number of bins for each variable using the Freedman-Diaconis rule
= int(np.ceil((np.max(datax) - np.min(datax)) / (2 * iqr(datax) / len(datax) ** (1 / 3))))
bins_x = int(np.ceil((np.max(datay) - np.min(datay)) / (2 * iqr(datay) / len(datay) ** (1 / 3))))
bins_y = max(bins_x, bins_y) # Use the larger number of bins
nbins[timei]
# Calculate mutual information with variable bin length
0, timei] = mutualinformationx(datax, datay, fd_bins=int(nbins[timei]))[0]
mi[
# Calculate mutual information with fixed bin length (e.g., 70)
1, timei] = mutualinformationx(datax, datay, fd_bins=70)[0]
mi[
# Plot the mutual information over time
=(12, 8))
plt.figure(figsize
221)
plt.subplot(0, :])
plt.plot(times2save, mi['Time (ms)')
plt.xlabel('MI (bits)')
plt.ylabel('Variable bin length')
plt.title(0] - 50, times2save[-1] + 50])
plt.xlim([times2save[min(mi) - 0.01, np.max(mi) + 0.01])
plt.ylim([np.
222)
plt.subplot(0, :], '.')
plt.plot(nbins, mi['Bin length')
plt.xlabel('MI (bits)')
plt.ylabel('Bin length vs. MI')
plt.title(min(mi) - 0.01, np.max(mi) + 0.01])
plt.ylim([np.
223)
plt.subplot(1, :])
plt.plot(times2save, mi['Time (ms)')
plt.xlabel('MI (bits)')
plt.ylabel('Constant bin length')
plt.title(0] - 50, times2save[-1] + 50])
plt.xlim([times2save[min(mi) - 0.01, np.max(mi) + 0.01])
plt.ylim([np.
plt.tight_layout() plt.show()
Figure 29.11
# Define frequency range and baseline time window
= np.logspace(np.log10(4), np.log10(40), 20)
frex = [-500, -200]
baselinetime
# Convert baseline time window to indices
= [np.argmin(np.abs(times2save - t)) for t in baselinetime]
baseidx
# Specify convolution parameters
= np.arange(-1, 1 + 1/EEG['srate'][0][0], 1/EEG['srate'][0][0])
time = (len(time) - 1) // 2
half_wavelet = len(time)
n_wavelet = EEG['pnts'][0][0] * EEG['trials'][0][0]
n_data = n_wavelet + n_data - 1
n_convolution
# FFT of data for both electrodes
= fft(EEG['data'][electrodesidx[0], :, :].flatten('F'), n_convolution)
fft_EEG1 = fft(EEG['data'][electrodesidx[1], :, :].flatten('F'), n_convolution)
fft_EEG2
# Initialize outputs
= np.zeros((2, len(frex), len(times2save)))
mi = np.zeros((len(frex), len(times2save)))
ispc = np.zeros((len(frex), len(times2save)))
powc
# Loop over frequencies
for fi, f in enumerate(frex):
# Create wavelet and get its FFT
= np.exp(2 * 1j * np.pi * f * time) * np.exp(-time ** 2 / (2 * (4 / (2 * np.pi * f)) ** 2))
wavelet = fft(wavelet, n_convolution)
fft_wavelet
# Convolution for each electrode
= ifft(fft_wavelet * fft_EEG1, n_convolution)
convres = np.reshape(convres[half_wavelet:-half_wavelet], (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
analytic1
= ifft(fft_wavelet * fft_EEG2, n_convolution)
convres = np.reshape(convres[half_wavelet:-half_wavelet], (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')
analytic2
# Loop over time points
for ti, tidx in enumerate(times2saveidx):
# Get analytic signal in the time window around the time point
= analytic1[tidx - timewindowidx:tidx + timewindowidx + 1, :]
datax = analytic2[tidx - timewindowidx:tidx + timewindowidx + 1, :]
datay
# Compute mutual information for power and phase
0, fi, ti] = mutualinformationx(np.log10(np.abs(datax)**2), np.log10(np.abs(datay)**2), fd_bins=50)[0]
mi[1, fi, ti] = mutualinformationx(np.angle(datax), np.angle(datay), fd_bins=20)[0]
mi[
# Compute inter-site phase clustering (ISPC)
= np.mean(np.abs(np.mean(np.exp(1j * (np.angle(datay) - np.angle(datax))), axis=1)), axis=0)
ispc[fi, ti]
# Compute power correlation (Spearman's rank correlation)
= rankdata(np.abs(datax), axis=0)
dataxr = rankdata(np.abs(datay), axis=0)
datayr = timewindowidx*2+1
n = np.mean(1 - 6 * np.sum((dataxr - datayr) ** 2, axis=0) / (n * (n ** 2 - 1)))
powc[fi, ti]
# Plot the results
=(12, 10))
plt.figure(figsize
for i in range(2):
# Plot mutual information
2, 2, i+1)
plt.subplot(- np.mean(mi[i, :, baseidx[0]:baseidx[1]+1], axis=1)[..., np.newaxis], 40, cmap='viridis', vmin=-0.075, vmax=0.075)
plt.contourf(times2save, frex, mi[i, :, :] 'log')
plt.yscale(round(np.logspace(np.log10(frex[0]), np.log10(frex[-1]), 8)))
plt.yticks(np.round(np.logspace(np.log10(frex[0]), np.log10(frex[-1]), 8)))
plt.gca().set_yticklabels(np.
# Plot power correlation
2, 2, 3)
plt.subplot(- np.mean(powc[:, baseidx[0]:baseidx[1]+1], axis=1)[..., np.newaxis], 40, cmap='viridis', vmin=-0.2, vmax=0.2)
plt.contourf(times2save, frex, powc 'log')
plt.yscale(round(np.logspace(np.log10(frex[0]), np.log10(frex[-1]), 8)))
plt.yticks(np.round(np.logspace(np.log10(frex[0]), np.log10(frex[-1]), 8)))
plt.gca().set_yticklabels(np.
# Plot inter-site phase clustering
2, 2, 4)
plt.subplot(- np.mean(ispc[:, baseidx[0]:baseidx[1]+1], axis=1)[..., np.newaxis], 40, cmap='viridis', vmin=-0.1, vmax=0.1)
plt.contourf(times2save, frex, ispc 'log')
plt.yscale(round(np.logspace(np.log10(frex[0]), np.log10(frex[-1]), 8)))
plt.yticks(np.round(np.logspace(np.log10(frex[0]), np.log10(frex[-1]), 8)))
plt.gca().set_yticklabels(np.
plt.tight_layout() plt.show()
Figure 29.12
# Define the signals
= np.arange(0, 1.0001, 0.0001)
time = np.sin(2 * np.pi * 10 * time)
signal1 = -signal1
signal2
# Define lags
= np.arange(1, 1501, 10)
lagz = np.zeros(len(lagz))
milags
# Calculate mutual information for different lags
for li, lag in enumerate(lagz):
# Compute mutual information
= mutualinformationx(signal1, np.roll(signal2, lag), fd_bins=15)[0]
milags[li]
# Plot mutual information as a function of lag
plt.figure()/ (1 / np.mean(np.diff(time))), milags)
plt.plot(lagz 'Lag (seconds)')
plt.xlabel('Mutual information (bits)')
plt.ylabel(
plt.show()
# Now on real data (6 Hz power MI from figure 11)
# Create wavelet and get its FFT
= np.arange(-1, 1 + 1/EEG['srate'][0][0], 1/EEG['srate'][0][0])
time = fft(np.exp(2 * 1j * np.pi * frex[4] * time) * np.exp(-time ** 2 / (2 * (4 / (2 * np.pi * frex[4])) ** 2)), n_convolution)
fft_wavelet
# Convolution for each electrode with wavelet
= ifft(fft_wavelet * fft_EEG1, n_convolution)
convres = np.log10(np.abs(np.reshape(convres[half_wavelet:-half_wavelet], (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')) ** 2)
pow1
= ifft(fft_wavelet * fft_EEG2, n_convolution)
convres = np.log10(np.abs(np.reshape(convres[half_wavelet:-half_wavelet], (EEG['pnts'][0][0], EEG['trials'][0][0]), 'F')) ** 2)
pow2
# Define lags for real data
= round(1000 / frex[4])
onecycle = int(onecycle / (1000 / EEG['srate'][0][0]))
onecycleidx
= np.arange(-onecycleidx, onecycleidx + 1)
lagz = np.zeros(len(lagz))
milags
# Calculate mutual information for different lags on real data
for li, lag in enumerate(lagz):
if lag < 0:
= mutualinformationx(pow1[:lag, :], pow2[-lag:, :], fd_bins=30)[0]
milags[li] elif lag == 0:
= mutualinformationx(pow1, pow2, fd_bins=30)[0]
milags[li] else:
= mutualinformationx(pow1[lag:, :], pow2[:-lag, :], fd_bins=30)[0]
milags[li]
# Plot mutual information as a function of lag for real data
plt.figure()1000 * lagz / EEG['srate'][0][0], milags)
plt.plot(f"{electrodes4mi[0]} leads {electrodes4mi[1]} ... Lag (ms) ... {electrodes4mi[1]} leads {electrodes4mi[0]}")
plt.xlabel('Mutual information (bits)')
plt.ylabel(-onecycle, onecycle])
plt.xlim([ plt.show()