raphaelvallat / yasa

YASA (Yet Another Spindle Algorithm): a Python package to analyze polysomnographic sleep recordings.
https://raphaelvallat.com/yasa/
BSD 3-Clause "New" or "Revised" License
417 stars 113 forks source link

Entropy analysis on all channels #10

Closed apavlo89 closed 4 years ago

apavlo89 commented 4 years ago

Hello,

I'm trying to use your entropy analysis algorithm on 2-minute resting-state EEG with 64 channels. Is there a way to create a df_feat that includes in columns or rows all the channels?

This is the code I have thus far.

P1

Load data as a eeglab file

raw = mne.io.read_raw_eeglab('E:/study/EEGLAB/restingstate/1_restingstate.set', montage=None, eog=(), preload=True, uint16_codec=None, verbose=None)

Load data

data = raw._data * 1e6

sf = raw.info['sfreq'] chan = raw.ch_names

ch_names = chan

times = np.arange(data.size) / sf

times = np.arange(data.shape[1]) / sf print(data.shape, chan, times)

data = data[0, :] # 0 indicates first electrode location, change number to change electrode - to see which number is each electrode look at variable 'chan' print(data.shape, np.round(data[0:5], 3))

Convert the EEG data to 120-sec data

times, data_win = yasa.sliding_window(data, sf, window=120)

Convert times to minutes

times /= 60

data_win.shape

from numpy import apply_along_axis as apply

df_feat = {

Entropy

'perm_entropy': apply(ent.perm_entropy, axis=1, arr=data_win, normalize=True),
'svd_entropy': apply(ent.svd_entropy, 1, data_win, normalize=True),
'spec_entropy': apply(ent.spectral_entropy, 1, data_win, sf=sf, nperseg=data_win.shape[1], normalize=True),
'sample_entropy': apply(ent.sample_entropy, 1, data_win),
# Fractal dimension
'dfa': apply(ent.detrended_fluctuation, 1, data_win),
'petrosian': apply(ent.petrosian_fd, 1, data_win),
'katz': apply(ent.katz_fd, 1, data_win),
'higuchi': apply(ent.higuchi_fd, 1, data_win),

}

df_feat = pd.DataFrame(df_feat) df_feat.head()

def lziv(x): """Binarize the EEG signal and calculate the Lempel-Ziv complexity. """ return ent.lziv_complexity(x > x.mean(), normalize=True)

df_feat['lziv'] = apply(lziv, 1, data_win) # Slow

Any help will be greatly appreciated!

apavlo89 commented 4 years ago

Have you labeled this as a bug?Not sure how the github system works. I think it is more like my programming incompetence! :P My understanding is that in order for df_feat to display entropy for each channel, something has to be changed in data = data[0, :] to make it include all channels but I am terrible at programming so everything I've tried doesn't work! Is including all channels in df_feat possible currently or will there need to be major changes in the YASA source code to do this?

raphaelvallat commented 4 years ago

Hi @apavlo89! Just removed the "Bug" label :-) So the issue here is not with YASA but rather with Entropy, which is designed to work only with one-dimensional array. In other words, if you want to create a df_feat dataframe with all the channels, you need to create a for loop in which you loop across all the channels

df_all = []
for i, c in enumerate(chan):
    data = data[i, :]

    # Calculate entropy for this channel
    df_chan = {'perm_entropy': apply(...), }
    df_chan['Channel'] = f

    # Append to a larger dataframe
    df_all.append(df_chan)

df_all = pd.DataFrame(df_all, ignore_index=True)

Something like that should work, let me know! Thanks

apavlo89 commented 4 years ago

It is giving me an error: too many indices for array

import yasa import mne import numpy as np import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import entropy as ent sns.set(font_scale=1.2)

P1

Load data as a eeglab file

f = mne.io.read_raw_eeglab('E:\REEG MU HIGHLOW\EEGLAB\P1_postICA.set', montage=None, eog=(), preload=True, uint16_codec=None, verbose=None)

Load data

data = f._data * 1e6

sf = f.info['sfreq'] chan = f.ch_names times = np.arange(data.shape[1]) / sf print(data.shape, chan, times)

data = data[0, :] # 0 indicates first electrode location, change number to change electrode - to see which number is each electrode look at variable 'chan' print(data.shape, np.round(data[0:5], 3))

Convert the EEG data to 120-sec data

times, data_win = yasa.sliding_window(data, sf, window=120)

Convert times to minutes

times /= 60

data_win.shape

from numpy import apply_along_axis as apply

df_feat = {

Entropy

'perm_entropy': apply(ent.perm_entropy, axis=1, arr=data_win, normalize=True),
'svd_entropy': apply(ent.svd_entropy, 1, data_win, normalize=True),
'spec_entropy': apply(ent.spectral_entropy, 1, data_win, sf=sf, nperseg=data_win.shape[1], normalize=True),
'sample_entropy': apply(ent.sample_entropy, 1, data_win),
# Fractal dimension
'dfa': apply(ent.detrended_fluctuation, 1, data_win),
'petrosian': apply(ent.petrosian_fd, 1, data_win),
'katz': apply(ent.katz_fd, 1, data_win),
'higuchi': apply(ent.higuchi_fd, 1, data_win),

}

df_feat = pd.DataFrame(df_feat) df_feat.head()

def lziv(x): """Binarize the EEG signal and calculate the Lempel-Ziv complexity. """ return ent.lziv_complexity(x > x.mean(), normalize=True)

df_feat['lziv'] = apply(lziv, 1, data_win) # Slow

df_all = [] for i, c in enumerate(chan): data = data[i, :]

# Calculate entropy for this channel
df_chan = {'perm_entropy': apply(...), }
df_chan['Channel'] = f

# Append to a larger dataframe
df_all.append(df_chan)

df_all = pd.DataFrame(df_all, ignore_index=True)

I wasn't sure if the three dots in this line df_chan = {'perm_entropy': apply(...), } meant to just copy paste from df_feat all the values so I also tried like this

df_all = [] for i, c in enumerate(chan): data = data[i, :]

# Calculate entropy for this channel
df_chan = {
# Entropy
'perm_entropy': apply(ent.perm_entropy, axis=1, arr=data_win, normalize=True),
'svd_entropy': apply(ent.svd_entropy, 1, data_win, normalize=True),
'spec_entropy': apply(ent.spectral_entropy, 1, data_win, sf=sf, nperseg=data_win.shape[1], normalize=True),
'sample_entropy': apply(ent.sample_entropy, 1, data_win),
# Fractal dimension
'dfa': apply(ent.detrended_fluctuation, 1, data_win),
'petrosian': apply(ent.petrosian_fd, 1, data_win),
'katz': apply(ent.katz_fd, 1, data_win),
'higuchi': apply(ent.higuchi_fd, 1, data_win),

} df_chan['Channel'] = f

# Append to a larger dataframe
df_all.append(df_chan)

df_all = pd.DataFrame(df_all, ignore_index=True)

Again I got same error :( Also how do I get lziv complexity analysis into this loop?

raphaelvallat commented 4 years ago

You need to remove this line from your script;

data = data[0, :]

Yes the three dots meant copy-paste everything.

For LZiv just define this function at the beginning of the script:

def lziv(x):
"""Binarize the EEG signal and calculate the Lempel-Ziv complexity.
"""
return ent.lziv_complexity(x > x.mean(), normalize=True)

and then add

df_chan = {...,
'lziv': apply(lziv, 1, data_win),
}
apavlo89 commented 4 years ago

I've done as you have explained but now I get

Error: order*delay should be lower than x.size

This is my code now

import yasa import mne import numpy as np import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import entropy as ent sns.set(font_scale=1.2)

P1

Load data as a eeglab file

f = mne.io.read_raw_eeglab('E:\REEG MU HIGHLOW\EEGLAB\P1_postICA.set', montage=None, eog=(), preload=True, uint16_codec=None, verbose=None)

Load data

data = f._data * 1e6

sf = f.info['sfreq'] chan = f.ch_names times = np.arange(data.shape[1]) / sf

print(data.shape, np.round(data[0:5], 3))

Convert the EEG data to 120-sec data

times, data_win = yasa.sliding_window(data, sf, window=120)

Convert times to minutes

times /= 60

data_win.shape

from numpy import apply_along_axis as apply

def lziv(x): """Binarize the EEG signal and calculate the Lempel-Ziv complexity. """ return ent.lziv_complexity(x > x.mean(), normalize=True)

df_all = [] for i, c in enumerate(chan): data = data[i, :]

# Calculate entropy for this channel
df_chan = {
# Entropy
'perm_entropy': apply(ent.perm_entropy, axis=1, arr=data_win, normalize=True),
'svd_entropy': apply(ent.svd_entropy, 1, data_win, normalize=True),
'spec_entropy': apply(ent.spectral_entropy, 1, data_win, sf=sf, nperseg=data_win.shape[1], normalize=True),
'sample_entropy': apply(ent.sample_entropy, 1, data_win),
# Fractal dimension
'dfa': apply(ent.detrended_fluctuation, 1, data_win),
'petrosian': apply(ent.petrosian_fd, 1, data_win),
'katz': apply(ent.katz_fd, 1, data_win),
'higuchi': apply(ent.higuchi_fd, 1, data_win),
'lziv': apply(lziv, 1, data_win),
}

df_chan['Channel'] = f
# Append to a larger dataframe
df_all.append(df_chan)

df_all = pd.DataFrame(df_all, ignore_index=True)

raphaelvallat commented 4 years ago

Ok it's hard for me to debug without having the data and code but the issue is that data_win is now a three-dimension array, so you first need to create something like:

data_win_ep = data_win[i, :, :]

and then use this data_win_ep in the df_chan = {...} code, possibily changing the axis argument of the numpy.apply_along_axis.

df_chan = {
# Entropy
'perm_entropy': apply(ent.perm_entropy, axis=1, arr=data_win_ep, normalize=True),
}

As this is not directly related to YASA or Entropy I will close this issue now. I recommend that you read on Numpy axis and shape.

Thanks

apavlo89 commented 4 years ago

Merci beaucoup, you are a rockstar! It worked perfectly! Thank you for your time. The only thing I had to change from your code was to remove ignore_index=True as it was giving me an error: unexpected keyword argument 'ignore index'. Not sure what it does but removing seemed to have fixed all errors.

For anyone wanting to analyze more than 1 channel in one go for their analysis this is an example of code they could use

import yasa import mne import numpy as np import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import entropy as ent sns.set(font_scale=1.2)

Participant 1

Load data as a eeglab file

f = mne.io.read_raw_eeglab('E:\study\P1_nonlinear.set', montage=None, eog=(), preload=True, uint16_codec=None, verbose=None)

Load data

data = f._data * 1e6

sf = f.info['sfreq'] chan = f.ch_names times = np.arange(data.shape[1]) / sf

print(data.shape, np.round(data[0:5], 3))

Convert the EEG data to 120-sec data

times, data_win = yasa.sliding_window(data, sf, window=120)

Convert times to minutes

times /= 60

from numpy import apply_along_axis as apply

def lziv(x): """Binarize the EEG signal and calculate the Lempel-Ziv complexity. """ return ent.lziv_complexity(x > x.mean(), normalize=True)

df_all = []

for i, c in enumerate(chan): data_win_ep = data_win[i, :, :]

Calculate entropy for this channel

df_chan = {
# Entropy
'perm_entropy': apply(ent.perm_entropy, axis=1, arr=data_win_ep, normalize=True),
'svd_entropy': apply(ent.svd_entropy, 1, data_win_ep, normalize=True),
'spec_entropy': apply(ent.spectral_entropy, 1, data_win_ep, sf=sf, nperseg=data_win_ep.shape[1], normalize=True),
'sample_entropy': apply(ent.sample_entropy, 1, data_win_ep),
# Fractal dimension
'dfa': apply(ent.detrended_fluctuation, 1, data_win_ep),
'petrosian': apply(ent.petrosian_fd, 1, data_win_ep),
'katz': apply(ent.katz_fd, 1, data_win_ep),
'higuchi': apply(ent.higuchi_fd, 1, data_win_ep),
'lziv': apply(lziv, 1, data_win_ep),
}
df_chan['Channel'] = f
# Append to a larger dataframe
df_all.append(df_chan)

df_all = pd.DataFrame(df_all) df_all.to_csv(r'E:\study\1_nonlinear.csv')