import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import detrend
from scipy.fft import fft
from scipy.io import loadmat
Chapter 15
Chapter 15
Analyzing Neural Time Series Data
Python code for Chapter 15 – 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 15.1
# Load sample EEG data
= loadmat('../data/sampleEEGdata.mat')['EEG'][0, 0]
EEG
# Define time window in ms
= 500
timewin
# Convert ms to index
= round(timewin / (1000 / EEG['srate'][0][0]))
timewinidx
# Create Hann taper function
= 0.5 * (1 - np.cos(2 * np.pi * np.arange(timewinidx) / (timewinidx - 1)))
hann_win
# Detrend data
= detrend(EEG['data'][19, :, 15])
d
# Plot one trial of data
=(12, 8))
plt.figure(figsize311)
plt.subplot('times'][0], d)
plt.plot(EEG[-1000, 1500])
plt.xlim(['One trial of data')
plt.title(
# Find the sample time closest to -50 ms
= np.argmin(np.abs(EEG['times'][0] - (-50)))
stime
# Plot one short-time window of data, windowed
323)
plt.subplot('times'][0][stime:stime + timewinidx], d[stime:stime + timewinidx], label='Original')
plt.plot(EEG['times'][0][stime:stime + timewinidx], d[stime:stime + timewinidx] * hann_win, 'r', label='Windowed')
plt.plot(EEG[-50, -50 + timewin])
plt.xlim(['One short-time window of data, windowed')
plt.title(
plt.legend()
# Compute power spectrum from that time window
= fft(d[stime:stime + timewinidx] * hann_win)
dfft = np.linspace(0, EEG['srate'][0][0] / 2, int(np.floor(len(hann_win) / 2)) + 1)
f 313)
plt.subplot(1:], np.abs(dfft[1:int(np.floor(len(hann_win) / 2)) + 1]) ** 2, '.-')
plt.plot(f['Power spectrum from that time window')
plt.title(1, 128])
plt.xlim([-1000, 25000])
plt.ylim([0, EEG['srate'][0][0] / 2 + 1, 10))
plt.xticks(np.arange(
plt.tight_layout()
plt.show()
# Create TF matrix and input column of data at selected time point
= np.zeros((int(np.floor(len(hann_win) / 2)), EEG['pnts'][0][0]))
tf + int(timewinidx / 2) - 11:stime + int(timewinidx / 2) + 10] = np.tile(np.abs(dfft[1:int(np.floor(len(hann_win) / 2)) + 1]) * 2, (21, 1)).T
tf[:, stime
# Plot TF matrix
=(12, 6))
plt.figure(figsize+ 1), aspect='auto', extent=[EEG['times'][0][0], EEG['times'][0][-1], f[0], f[-1]], origin='lower')
plt.imshow(np.log10(tf
plt.gca().invert_yaxis()-4, 4])
plt.clim(['TF matrix')
plt.title( plt.show()
Figure 15.2
# Define parameters
= 400 # in ms, for stFFT
timewin = np.arange(-300, 1050, 50) # in ms
times2save = 'P7'
channel2plot = 15 # in Hz
frequency2plot = 200 # ms
timepoint2plot
# Convert from ms to index
= [np.argmin(np.abs(EEG['times'][0] - t)) for t in times2save]
times2saveidx = round(timewin / (1000 / EEG['srate'][0][0]))
timewinidx = EEG['chanlocs'][0]['labels']==channel2plot
chan2useidx
# Create Hann taper
= 0.5 * (1 - np.cos(2 * np.pi * np.arange(timewinidx) / (timewinidx - 1)))
hann_win
# Define frequencies
= np.linspace(0, EEG['srate'][0][0] / 2, int(np.floor(timewinidx / 2)) + 1)
frex
# Initialize power output matrix
= np.zeros((len(frex), len(times2save)))
tf
# Loop over time points and perform FFT
for timepointi, tidx in enumerate(times2saveidx):
# Extract time series data for this center time point
= np.squeeze(EEG['data'][chan2useidx, tidx - int(np.floor(timewinidx / 2)) - 1:tidx + int(np.floor(timewinidx / 2)) - (timewinidx + 1) % 2, :])
tempdat
# Taper data
= tempdat * hann_win[:, None]
taperdat
# Perform FFT
= fft(taperdat, axis=0) / timewinidx
fdat = np.mean(np.abs(fdat[0:int(np.floor(timewinidx / 2)) + 1, :]) ** 2, axis=1) # Average over trials
tf[:, timepointi]
# Plot
=(8, 6))
plt.figure(figsize121)
plt.subplot(= np.argmin(np.abs(frex - frequency2plot))
freq2plotidx - 2:freq2plotidx + 3, :]), axis=0))
plt.plot(times2save, np.mean(np.log10(tf[freq2plotidx f'Sensor {channel2plot}, {frequency2plot} Hz')
plt.title(0], times2save[-1]])
plt.xlim([times2save[
122)
plt.subplot(= np.argmin(np.abs(times2save - timepoint2plot))
time2plotidx
plt.plot(frex, np.log10(tf[:, time2plotidx]))f'Sensor {channel2plot}, {timepoint2plot} ms')
plt.title(0], 40])
plt.xlim([frex[
plt.tight_layout()
plt.show()
=(8, 6))
plt.figure(figsize40, cmap='viridis', linestyles='None')
plt.contourf(times2save, frex, np.log10(tf), -2, 1])
plt.clim([f'Sensor {channel2plot}, power plot (no baseline correction)')
plt.title(
= 100 * (1 - np.mean(np.diff(times2save)) / timewin)
overlap print(f'Overlap of {overlap}%')
plt.show()
Overlap of 87.5%
Figure 15.3
# Create Hamming and Hann windows
= 0.54 - 0.46 * np.cos(2 * np.pi * np.arange(timewinidx) / (timewinidx - 1))
hamming_win = 0.5 * (1 - np.cos(2 * np.pi * np.arange(timewinidx) / (timewinidx - 1)))
hann_win
# Create Gaussian window
= np.exp(-0.5 * ((2.5 * np.arange(-timewinidx / 2, timewinidx / 2)) / (timewinidx / 2)) ** 2)
gaus_win
# Plot together
=(8, 4))
plt.figure(figsize='Hann')
plt.plot(hann_win, label'r', label='Hamming')
plt.plot(hamming_win, 'k', label='Gaussian')
plt.plot(gaus_win,
plt.legend()-5, timewinidx + 5])
plt.xlim([-0.1, 1.1])
plt.ylim([0, 1.1, 0.2))
plt.yticks(np.arange('Window functions')
plt.title( plt.show()
Figure 15.6
# Define parameters
= 'O1'
chan2use = 10 # in Hz
frequency2plot
# Find the index of the frequency to plot
= np.argmin(np.abs(frex - frequency2plot))
freq2plotidx
# Initialize ITPC output matrix
= np.zeros((len(frex), len(times2save)))
itpc
# Loop over time points and perform FFT
for timepointi, tidx in enumerate(times2saveidx):
# Extract time series data for this center time point
= np.squeeze(EEG['data'][EEG['chanlocs'][0]['labels']==chan2use, tidx - int(np.floor(timewinidx / 2)) - 1:tidx + int(np.floor(timewinidx / 2)) - (timewinidx + 1) % 2, :])
tempdat
# Taper data
= tempdat * hann_win[:, None]
taperdat
# Perform FFT
= fft(taperdat, axis=0) / timewinidx
fdat # Compute ITPC
= np.abs(np.mean(np.exp(1j * np.angle(fdat[0:int(np.floor(timewinidx / 2)) + 1, :])), axis=1)) # Average over trials
itpc[:, timepointi]
# Plot ITPC
=(8, 6))
plt.figure(figsize40, cmap='viridis', linestyles='None')
plt.contourf(times2save, frex, itpc, 0, 0.5])
plt.clim([-200, 1000])
plt.xlim([f'ITPC at sensor {chan2use}')
plt.title(
=(8, 6))
plt.figure(figsize- 2:freq2plotidx + 3, :], axis=0))
plt.plot(times2save, np.mean(itpc[freq2plotidx f'ITPC at sensor {chan2use}, {frequency2plot} Hz')
plt.title(-200, 1000])
plt.xlim([ plt.show()