EEG Microstates#

In continually recorded human EEG, the spatial distribution of electric potentials across the cortex changes over time. Interestingly, for brief time periods (60 - 120ms), quasi-stable states of activity (i.e., microstates), characterized by unique spatial configurations of electrical activity distribution, have been observed. NeuroKit can be used to easily analyze them.

# Load the NeuroKit package and other useful packages
import neurokit2 as nk
import mne

EEG Preprocessing#

First, let’s download a raw eeg data in an MNE format and select teh EEG channels.

raw = nk.data("eeg_1min_200hz")
raw = raw.pick(["eeg"], verbose=False)

sampling_rate = raw.info["sfreq"]  # Store the sampling rate

EEG recordings measure the difference in electric potential between each electrode and a reference electrode. This means that the ideal reference electrode is one which records all the interfering noise from the environment but doesn’t pick up any fluctuating signals related to brain activity. The idea behind re-referencing is to express the voltage at the EEG scalp channels with respect to another, new reference. This “virtual reference” can be any recorded channel, or the average of all the channels, as we will use in the current analysis.

Below, we will apply a band-pass filter and re-reference the signals to remove power line noise, slow drifts and other large artifacts from the raw input.

# Apply band-pass filter (1-35Hz) and re-reference the raw signal
eeg = raw.copy().filter(1, 35, verbose = False)
eeg = nk.eeg_rereference(eeg, 'average')

nk.signal_plot([raw.get_data()[0, 0:500], eeg.get_data()[0, 0:500]], 
               labels =["Raw", "Preprocessed"], 
               sampling_rate = eeg.info["sfreq"])
../../_images/34ef9f864622fe221ce14e9e15b82cb0582ca96ffdad60fcf6da314df07de4b4.png

Microstates Analysis#

Minimal Example#

Microstates can be extracted and analyzed like that:

# Extract microstates
microstates = nk.microstates_segment(eeg, n_microstates=4)

# Visualize the extracted microstates
nk.microstates_plot(microstates, epoch = (0, 500))
../../_images/6dab9bdd62e76abb1c5602b5093a04e989fbce4b57c4c839eecee9e706a5b544.png

This shows the aspect of the microstates and their sequence in the Global Field Power (GFP; see below). We can then proceed to a statistical analysis.

nk.microstates_static(microstates, sampling_rate=sampling_rate, show=True)
Microstate_0_Proportion Microstate_1_Proportion Microstate_2_Proportion Microstate_3_Proportion Microstate_0_LifetimeDistribution Microstate_1_LifetimeDistribution Microstate_2_LifetimeDistribution Microstate_3_LifetimeDistribution Microstate_0_DurationMean Microstate_0_DurationMedian Microstate_1_DurationMean Microstate_1_DurationMedian Microstate_2_DurationMean Microstate_2_DurationMedian Microstate_3_DurationMean Microstate_3_DurationMedian Microstate_Average_DurationMean Microstate_Average_DurationMedian
0 0.260083 0.272667 0.281167 0.186083 717.0 702.0 764.5 516.0 0.019194 0.015 0.021082 0.015 0.019824 0.015 0.018485 0.01 0.019711 0.015
../../_images/a34dfdeef41edae6d43dfd2b4202e99f07868c48438d89487ccf589502b1d986.png

This shows computes static statistics, such as the prevalence of each microstates and the median duration time (also shown in the graph).

nk.microstates_dynamic(microstates, show=True)
Microstate_0_to_0 Microstate_0_to_1 Microstate_0_to_2 Microstate_0_to_3 Microstate_1_to_0 Microstate_1_to_1 Microstate_1_to_2 Microstate_1_to_3 Microstate_2_to_0 Microstate_2_to_1 Microstate_2_to_2 Microstate_2_to_3 Microstate_3_to_0 Microstate_3_to_1 Microstate_3_to_2 Microstate_3_to_3
0 0.739507 0.093239 0.107658 0.059596 0.088936 0.762836 0.091993 0.056235 0.098696 0.084173 0.747777 0.069354 0.084677 0.089606 0.095878 0.729839
../../_images/6ab2c9d6663ec1d18d456a114e46a3aa725a2c4e2e61b9c884eee8add908a9d2.png

Finally, one can compute complexity features of the sequence of microstates, such as the entropy.

nk.microstates_complexity(microstates, show=True)
Microstates_Entropy_Shannon
0 1.982633
../../_images/b02349f121610b3d0501250975072da18fbd382a16c8100e2d310e792c16c5da.png

Options and features#

Global Field Power (GFP)#

Under the hood, microstates_segment() starts by computing the GFP.

The Global Field Power (GFP) is a reference-independent measure of potential field strength. It is thought to quantify the integrated electrical activity of the brain and is mathematically defined as the standard deviation of all electrodes at a given time.

The GFP time series periodically shows peaks, where the EEG topographies are most clearly defined (i.e., signal-to-noise ratio is maximized at GFP peaks). As such, GFP peak samples are often used to extract microstates.

This can be visualized manually:

gfp = nk.eeg_gfp(eeg)

peaks = nk.microstates_peaks(eeg, gfp=gfp)

# Plot the peaks in the first 200 data points
nk.events_plot(events = peaks[peaks < 200], signal = gfp[0:200])
../../_images/6ae3f5d75761cd2873bbdf98b31b5793d2594c6718bbbeaaaa6891d9a874bcf0.png

By default, the microstates clustering algorithm is trained on the EEG activity at these peaks, and then applied to back-predict the state at all data points. However, this behaviour can be changed. For instance, one can decide to train the algorithm directly on all data points.

microstates_all = nk.microstates_segment(eeg, n_microstates=4, train="all")
nk.microstates_plot(microstates_all, epoch = (0, 500))
../../_images/4cc7c4d9bf4001ae44b362c142e995e0ec7742a1db6d5b365afdb93d49afd7fe.png

How many microstates?#

Most of the clustering algorithms used in microstates analysis require the number of clusters to extract to be specified beforehand.

However, one can attempt at statistically estimating the optimal number of microstates. A variety of indices of fit can be used.

# Note: we cropped the data as this function takes some time to compute 
n_optimal, scores = nk.microstates_findnumber(eeg.crop(0, 5), n_max=8, show=True)  
print("Optimal number of microstates: ", n_optimal)
[........................................] 0/7
[█████...................................] 1/7
[███████████.............................] 2/7
[█████████████████.......................] 3/7
[██████████████████████..................] 4/7

[████████████████████████████............] 5/7
[██████████████████████████████████......] 6/7

[████████████████████████████████████████] 7/7

Optimal number of microstates:  5.0
../../_images/e197104c7530728549a2d6503c66505e38176119619e9767c269b8698439a5b5.png

Microstates clustering algorithms#

Several different clustering algorithms can be used to segment your EEG recordings into microstates. These algorithms mainly differ in how they define cluster membership and the cost functionals to be optimized (Xu & Tian, 2015). The method to use hence depends on your data and the underlying assumptions of the methods (e.g., some methods ignore polarity). There is no one true method that gives the best results but you can refer to Poulsen et al., 2018 if you would like a more detailed review of the different clustering methods.

In the example below, we will compare the two most commonly applied clustering algorithms - the K-means and modified K-means. Other methods that can be applied using nk.microstates_segment include kmedoids, pca, ica, aahc.

# Extract microstates
microstates_kmeans = nk.microstates_segment(eeg, n_microstates=4, method="kmeans")
microstates_kmod = nk.microstates_segment(eeg, n_microstates=4, method="kmod")  

# Global Explained Variance
gev_kmeans = microstates_kmeans['GEV']
gev_kmod = microstates_kmod['GEV']
print( f' Using conventional Kmeans,  GEV = {gev_kmeans*100:.2f}%')
print( f' Using modified Kmeans,  GEV = {gev_kmod*100:.2f}%')

# Visualize the extracted microstates
nk.microstates_plot(microstates_kmeans, epoch = (150, 450))
nk.microstates_plot(microstates_kmod, epoch = (150, 450))
 Using conventional Kmeans,  GEV = 61.46%
 Using modified Kmeans,  GEV = 71.92%
../../_images/3229627b67552ac1327ef251810ccfbbd00d1fbb2bf6fa91bbdd31fcfcfb9ce6.png ../../_images/38816b8e1e08c6234c389f4683b53bc769600d55c2adace68717f51935548d11.png