Skip to content

Chapter 2: MNE-Python Core Concepts

MNE-Python is the standard open-source Python library for MEG/EEG analysis — most published EEG papers using Python use it. Its official documentation and tutorials live at mne.tools; everything in this tutorial series is grounded in that documentation, adapted for a first pass.

Almost everything you'll do in MNE revolves around four object types. Learn these and the rest of the library reads naturally, because every function's job is to create, transform, or extract information from one of them.

The four core objects

Object Shape of underlying data What it represents
Info (metadata only) Channel names/types, sampling rate, filter history, bad channels, montage — the "header" describing a recording
Raw (n_channels, n_times) The full continuous recording
Epochs (n_epochs, n_channels, n_times) Many short snippets cut around events
Evoked (n_channels, n_times) The average across epochs — this is your ERP

Every Raw, Epochs, and Evoked object carries an .info attribute — the same kind of metadata container, describing whatever data that object holds. Once you've inspected .info once, you know how to inspect it on any of the three.

Let's load real data (the public sample dataset from the previous chapters) and look at each.

Raw, Epochs, and Evoked data shapes

import mne
from pathlib import Path

mne.set_log_level("WARNING")

sample_folder = Path(mne.datasets.sample.data_path()) / "MEG" / "sample"
raw = mne.io.read_raw_fif(sample_folder / "sample_audvis_raw.fif", preload=True)
raw.pick(["eeg", "eog"])  # keep this tutorial EEG-focused, like your own data
raw
General
Filename(s) sample_audvis_raw.fif
MNE object type Raw
Measurement date 2002-12-03 at 19:01:10 UTC
Participant Unknown
Experimenter MEG
Acquisition
Duration 00:04:38 (HH:MM:SS)
Sampling frequency 600.61 Hz
Time points 166,800
Channels
EEG and
EOG
Head & sensor digitization 146 points
Filters
Highpass 0.10 Hz
Lowpass 172.18 Hz

Info: the metadata

raw.info behaves like a dictionary. Some of the most useful keys:

print("Sampling rate:", raw.info["sfreq"])
print("Number of channels:", raw.info["nchan"])
print("Bad channels:", raw.info["bads"])
print("Highpass/lowpass already applied:", raw.info["highpass"], raw.info["lowpass"])
raw.info  # printing it directly gives a full readable summary
Sampling rate: 600.614990234375
Number of channels: 61
Bad channels: ['EEG 053']
Highpass/lowpass already applied: 0.10000000149011612 172.17630004882812
General
MNE object type Info
Measurement date 2002-12-03 at 19:01:10 UTC
Participant Unknown
Experimenter MEG
Acquisition
Sampling frequency 600.61 Hz
Channels
EEG and
EOG
Head & sensor digitization 146 points
Filters
Highpass 0.10 Hz
Lowpass 172.18 Hz

Raw: the continuous recording

Underneath, Raw wraps a plain 2D NumPy array: rows are channels, columns are time samples. You can get that array directly with .get_data() — useful to remember that everything MNE does is ultimately array math with metadata attached.

data = raw.get_data()
print("data shape (channels, time samples):", data.shape)
print("data is in Volts by default; e.g. first channel, first 5 samples:", data[0, :5])
data shape (channels, time samples): (61, 166800)
data is in Volts by default; e.g. first channel, first 5 samples: [1.13989260e-05 9.85015885e-06 7.68188489e-06 5.82336435e-06
 6.81457530e-07]

Code patterns you'll see constantly

A few conventions come up everywhere in MNE — recognizing them now will save confusion later:

1. Many methods modify the object in place (and also return it, to allow chaining) — raw.filter(...) changes raw itself. If you want to keep the original untouched, .copy() first:

raw_filtered = raw.copy().filter(l_freq=1, h_freq=40)  # raw itself is untouched
raw.filter(l_freq=1, h_freq=40)                         # raw itself IS modified
This is exactly the pattern we used in your own-data notebook (raw_filt = raw.copy().filter(...)) specifically so the unfiltered raw stayed available for comparison.

2. preload: reading a file with preload=False (sometimes the default) only reads the header/metadata, not the actual signal — faster if you just want to inspect .info or crop before loading everything. Most processing steps require preload=True (either at read time or via raw.load_data() later).

3. picks: many functions accept a picks argument to operate on a subset of channels, e.g. picks="eeg", picks=["Fz", "Cz"], or picks="eog".

4. verbose: almost every function accepts verbose=... to control its own logging independent of the global mne.set_log_level(...) we set above.

Getting help without leaving the notebook

In Jupyter, append ? to any function or method to pop up its docstring (full parameter list, defaults, and explanation) — this is the fastest way to answer "what does this argument do?" without leaving VS Code.

raw.filter?

Summary

  • Info = metadata, attached to everything.
  • Raw = continuous recording (2D: channels × time).
  • Epochs = trial snippets (3D: trials × channels × time) — Chapter 6.
  • Evoked = the average of Epochs = your ERP — Chapter 7.
  • Remember .copy() before in-place methods, preload=True to actually load data, picks to select channels, and function? for instant docs.

Next: Chapter 3 — Loading and Inspecting Data