Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

EEG Analysis with MNE

Run this notebook

Authors: Angela Renton, Monika Doerig

Date: 11 Aug 2025

License:

MIT License

Note: If this notebook uses neuroimaging tools from Neurocontainers, those tools retain their original licenses. Please see Neurodesk citation guidelines for details.

Citation:

Tools included in this workflow

MNE-Python:

  • Larson, E., Gramfort, A., Engemann, D. A., Leppakangas, J., Brodbeck, C., Jas, M., Brooks, T. L., Sassenhagen, J., McCloy, D., Luessi, M., King, J.-R., Höchenberger, R., Brunner, C., Goj, R., Favelier, G., van Vliet, M., Wronkiewicz, M., Rockhill, A., Appelhoff, S., … user27182. (2025). MNE-Python (v1.10.0). Zenodo. Larson et al. (2025)

  • Alexandre Gramfort, Martin Luessi, Eric Larson, Denis A. Engemann, Daniel Strohmeier, Christian Brodbeck, Roman Goj, Mainak Jas, Teon Brooks, Lauri Parkkonen, and Matti S. Hämäläinen. MEG and EEG data analysis with MNE-Python. Frontiers in Neuroscience, 7(267):1–13, 2013. Gramfort (2013)

Tutorial this work is based on

Dataset

  • Renton, A. (2022, April 11). Optimising the classification of feature-based attention in frequency-tagged electroencephalography data. Angela Renton (2021)

Install and import python libraries

Introduction

In this tutorial, we analyze EEG data from a visual attention task designed to evoke steady-state visual evoked potentials (SSVEPs). The dataset comes from an experiment in which participants viewed flickering fields of black and white dots, each assigned a different flicker frequency (6 Hz or 7.5 Hz, counterbalanced). Before each 15-second trial, participants were cued to attend to one color and detect brief bursts of coherent motion. On half the trials, both colors were presented concurrently (requiring selective attention), while on the other half, only the cued color was shown (mimicking complete attentional suppression). In our analysis, we focus on one participant and examine how attention modulates the frequency-tagged brain responses. After epoching and averaging trials by attended frequency, we use Fourier analysis to visualize how attention enhances SSVEP amplitude at the attended frequency.

Data download from OSF

Archive:  Data_sample.zip
   creating: Data_sample/
  inflating: Data_sample/helperdata.asv  
  inflating: Data_sample/helperdata.m  
 extracting: Data_sample/helperdata.mat  
  inflating: Data_sample/participants.json  
  inflating: Data_sample/participants.tsv  
   creating: Data_sample/sourcedata/
   creating: Data_sample/sourcedata/sub-01/
   creating: Data_sample/sourcedata/sub-01/behave/
  inflating: Data_sample/sourcedata/sub-01/behave/sub-01_task-FeatAttnDec_behav.mat  
   creating: Data_sample/sourcedata/sub-01/eeg/
  inflating: Data_sample/sourcedata/sub-01/eeg/sub-01_task-FeatAttnDec_eeg.mat  
   creating: Data_sample/sub-01/
   creating: Data_sample/sub-01/eeg/
  inflating: Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_channels.tsv  
  inflating: Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_eeg.eeg  
  inflating: Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_eeg.json  
  inflating: Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_eeg.vhdr  
  inflating: Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_eeg.vmrk  
  inflating: Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_events.tsv  
Data_sample
├── helperdata.asv
├── helperdata.m
├── helperdata.mat
├── participants.json
├── participants.tsv
├── sourcedata
│   └── sub-01
│       ├── behave
│       │   └── sub-01_task-FeatAttnDec_behav.mat
│       └── eeg
│           └── sub-01_task-FeatAttnDec_eeg.mat
└── sub-01
    └── eeg
        ├── sub-01_task-FeatAttnDec_channels.tsv
        ├── sub-01_task-FeatAttnDec_eeg.eeg
        ├── sub-01_task-FeatAttnDec_eeg.json
        ├── sub-01_task-FeatAttnDec_eeg.vhdr
        ├── sub-01_task-FeatAttnDec_eeg.vmrk
        └── sub-01_task-FeatAttnDec_events.tsv

7 directories, 13 files

Loading and preparing data

Begin by creating a pointer to the data:

Extracting parameters from ./Data_sample/sub-01/eeg/sub-01_task-FeatAttnDec_eeg.vhdr...
Setting channel info structure...
Reading 0 ... 3706303  =      0.000 ...  3088.586 secs...

The raw.info structure contains information about the dataset:

<RawBrainVision | sub-01_task-FeatAttnDec_eeg.eeg, 6 x 3706304 (3088.6 s), ~169.7 MiB, data loaded>
<Info | 7 non-empty values
 bads: []
 ch_names: Iz, Oz, POz, O1, O2, TRIG
 chs: 6 EEG
 custom_ref_applied: False
 highpass: 0.0 Hz
 lowpass: 600.0 Hz
 meas_date: unspecified
 nchan: 6
 projs: []
 sfreq: 1200.0 Hz
>

This dataset does not include a montage. We’ll use MNE’s built-in standard 10-20 montage to assign approximate electrode positions:

<Figure size 640x640 with 1 Axes>

Next, let’s visualize the data.

In this notebook, the plot will be rendered as a static image (using Matplotlib as the 2D backend), so interactive features like scrolling through the time series are not available.

Using matplotlib as 2D backend.
<MNEBrowseFigure size 800x800 with 4 Axes>

If, upon visual inspection, you decide to exclude one or more channels (e.g., due to noise or signal dropout), you can mark them as “bad” by adding them to raw.info['bads'].

For example, to mark channel ‘POz’ as bad:

This will ensure that the specified channel is ignored during subsequent preprocessing and analysis steps.

Loading...

To visualize the frequency content of the continuous EEG data, use the compute_psd() method on the Raw object. The resulting Spectrum object’s plot() method will display the power spectral density (PSD). Since the data contains only EEG channels, the PSD will be shown for those channels.

To plot the average PSD across all EEG channels, use average=True and exclude any channels marked as bad.

Effective window size : 1.707 (s)
Plotting power spectral density (dB=True).
<MNELineFigure size 1000x350 with 1 Axes>

It is also possible to plot each EEG channel individually, with options for how the spectrum should be computed, color-coding the channels by location, and more.

For example, the following plot shows the PSD of the four selected sensors (specified via the picks parameter), with color-coding by spatial location enabled using the spatial_colors parameter. For full details, see the plot method documentation.

Plotting power spectral density (dB=True).
<MNELineFigure size 1000x350 with 2 Axes>

It is also possible to visualize spectral power estimates across sensors as scalp topographies using the Spectrum object’s plot_topomap() method. By default, this plots power in five frequency bands (δ, θ, α, β, γ), computes power based on magnetometer channels if available, and displays the estimates on a dB-like logarithmic scale.

<Figure size 1000x150 with 10 Axes>

Extract Events

Next, we’ll extract events from the trigger channel. Since the trigger channel in this file is incorrectly scaled, we first correct the scaling before extracting the events.

Finding events on: TRIG
1725 events found on stim channel TRIG
Event IDs: [  1   2   3   4   5   6   7   8  21  22  23  24  26  27  28 101 102 104
 106 109 113 117 118 121 122 125 126 129 130]
Found 1725 events, first five:
[[24627     0     4]
 [27040     4    24]
 [29508    24   106]
 [29958   106   126]
 [31968   126   106]]
<Figure size 1280x960 with 1 Axes>

Extract EEG Channels and Preprocess

Now that we’ve extracted events, we proceed with selecting the EEG channels and applying some simple preprocessing steps.

NOTE: pick_types() is a legacy function. New code should use inst.pick(...).
Setting channel interpolation method to {'eeg': 'spline'}.
Interpolating bad channels.
Computing interpolation matrix from 4 sensor positions
Interpolating 1 sensors
Filtering raw data in 1 contiguous segment
Setting up band-pass filter from 1 - 45 Hz

FIR filter parameters
---------------------
Designing a one-pass, zero-phase, non-causal bandpass filter:
- Windowed time-domain design (firwin) method
- Hamming window with 0.0194 passband ripple and 53 dB stopband attenuation
- Lower passband edge: 1.00
- Lower transition bandwidth: 1.00 Hz (-6 dB cutoff frequency: 0.50 Hz)
- Upper passband edge: 45.00 Hz
- Upper transition bandwidth: 0.10 Hz (-6 dB cutoff frequency: 45.05 Hz)
- Filter length: 39601 samples (33.001 s)

Loading...
<MNEBrowseFigure size 800x800 with 4 Axes>

Epoching the Data

That’s looking good! We can even see hints of the frequency tagging. It’s about time to epoch our data.

Not setting metadata
40 matching events found
Applying baseline correction (mode: mean)
0 projection items activated
Using data from preloaded Raw for 40 events and 18001 original time points ...
0 bad epochs dropped
Loading...

We can average these epochs to form Event Related Potentials (ERPs):

combining channels using "mean"
combining channels using "mean"
<Figure size 800x600 with 1 Axes>

In this plot, we can see that the data are frequency tagged. While these data were collected, the participant was performing an attention task in which two visual stimuli were flickering at 6 Hz and 7.5 Hz respectively. On each trial the participant was cued to monitor one of these two stimuli for brief bursts of motion. From previous research, we expect that the steady-state visual evoked potential (SSVEP) should be larger at the attended frequency than the unattended frequency. First, we’ll examine how these frequency-tagged responses evolve over time, then quantify their overall strength. Let’s check if this is true.

FFT

Now let’s run a fast-fourier transform to evaluate how strongly our two tagged frequencies are represented in each condition.

Finally, let’s visualise the results.

<Figure size 640x480 with 1 Axes>

Exporting the data

Depending on what analyses you have planned, you may wish to extract the data from the mne format to a pandas or numpy array. Here’s an example of how we could run the same analysis using numpy and scipy. We’ll begin by exporting the averaged ERP data for each condition to a NumPy array, collapsing across EEG channels:

Next, we can use a Fast Fourier Transform (FFT) to transform the data from the time domain to the frequency domain. For this, we’ll need to import the FFT packages from scipy:

Now that we have our frequency transformed data, we can plot our two conditions to assess whether attention altered the SSVEP amplitudes:

<Figure size 640x480 with 1 Axes>

This plot shows that the SSVEPs were indeed modulated by attention in the direction we would expect!

Dependencies in Jupyter/Python

  • Using the package watermark to document system environment and software versions used in this notebook, alongside the Neurodesktop version extracted from the JUPYTER_IMAGE or NEURODESKTOP_VERSION environment variables.

Last updated: 2026-05-14T06:45:57.964960+00:00

Python implementation: CPython
Python version       : 3.13.13
IPython version      : 9.12.0

Compiler    : GCC 14.3.0
OS          : Linux
Release     : 6.8.0-106-generic
Machine     : x86_64
Processor   : x86_64
CPU cores   : 16
Architecture: 64bit

matplotlib: 3.10.9
mne       : 1.10.0
numpy     : 2.4.4
scipy     : 1.17.1

Neurodesktop version: 2026-04-28
References
  1. Larson, E., Gramfort, A., Engemann, D. A., Leppakangas, J., Brodbeck, C., Jas, M., Brooks, T. L., Sassenhagen, J., McCloy, D., Luessi, M., King, J.-R., Höchenberger, R., Brunner, C., Goj, R., Favelier, G., van Vliet, M., Wronkiewicz, M., Rockhill, A., Appelhoff, S., … user27182. (2025). MNE-Python. Zenodo. 10.5281/ZENODO.15928841
  2. Gramfort, A. (2013). MEG and EEG data analysis with MNE-Python. Frontiers in Neuroscience, 7. 10.3389/fnins.2013.00267
  3. Angela Renton. (2021). Optimising the classification of feature-based attention in frequency-tagged electroencephalography data. 10.17605/OSF.IO/C689U