Skip to content

Chapter 3: Loading and Inspecting Data

This chapter covers reading EEG files into MNE and the checks worth doing before any processing — the same checks we did on your own file, explained in full here.

File formats and their reader functions

MNE has a dedicated reader for most EEG/MEG file formats. A few common ones:

Format Extension Reader function
European Data Format .edf, .bdf mne.io.read_raw_edf
BrainVision .vhdr (+ .vmrk, .eeg) mne.io.read_raw_brainvision
Neuroscan .cnt mne.io.read_raw_cnt
EEGLAB .set mne.io.read_raw_eeglab
Neuromag/Elekta/MEGIN (MEG, used by the sample dataset) .fif mne.io.read_raw_fif

All of them return the same kind of object — a Raw — so once loaded, everything downstream (this whole tutorial) works identically regardless of the original file format. This is why your own .edf file and the sample dataset's .fif file can go through the same pipeline.

import mne
import matplotlib.pyplot as plt
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"])
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

The pre-processing checklist

Before touching the data, always check: 1. Channel names and types — are they what you expect? 2. Sampling rate and duration. 3. Annotations/events present. 4. Whether electrode positions (montage) are already set. 5. Any obviously bad channels by eye.

print("Channel names:", raw.ch_names)
print("Channel types:", set(raw.get_channel_types()))
print("Sampling rate (Hz):", raw.info["sfreq"])
print("Duration (s):", raw.times[-1])
Channel names: ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 004', 'EEG 005', 'EEG 006', 'EEG 007', 'EEG 008', 'EEG 009', 'EEG 010', 'EEG 011', 'EEG 012', 'EEG 013', 'EEG 014', 'EEG 015', 'EEG 016', 'EEG 017', 'EEG 018', 'EEG 019', 'EEG 020', 'EEG 021', 'EEG 022', 'EEG 023', 'EEG 024', 'EEG 025', 'EEG 026', 'EEG 027', 'EEG 028', 'EEG 029', 'EEG 030', 'EEG 031', 'EEG 032', 'EEG 033', 'EEG 034', 'EEG 035', 'EEG 036', 'EEG 037', 'EEG 038', 'EEG 039', 'EEG 040', 'EEG 041', 'EEG 042', 'EEG 043', 'EEG 044', 'EEG 045', 'EEG 046', 'EEG 047', 'EEG 048', 'EEG 049', 'EEG 050', 'EEG 051', 'EEG 052', 'EEG 053', 'EEG 054', 'EEG 055', 'EEG 056', 'EEG 057', 'EEG 058', 'EEG 059', 'EEG 060', 'EOG 061']
Channel types: {'eog', 'eeg'}
Sampling rate (Hz): 600.614990234375
Duration (s): 277.7136813300495

Channel types and picks

A file often mixes EEG with other channel types (EOG for eye movements, ECG for heartbeat, stim/trigger channels). MNE needs correct types so it knows, e.g., not to treat a trigger channel as brain signal. If a channel's type is wrong, fix it with raw.set_channel_types({"chan_name": "eog"}).

Once types are correct, most functions accept a picks argument to operate only on a subset — picks="eeg" is extremely common.

for ch_name, ch_type in zip(raw.ch_names, raw.get_channel_types()):
    print(f"{ch_name:>8}  ->  {ch_type}")
 EEG 001  ->  eeg
 EEG 002  ->  eeg
 EEG 003  ->  eeg
 EEG 004  ->  eeg
 EEG 005  ->  eeg
 EEG 006  ->  eeg
 EEG 007  ->  eeg
 EEG 008  ->  eeg
 EEG 009  ->  eeg
 EEG 010  ->  eeg
 EEG 011  ->  eeg
 EEG 012  ->  eeg
 EEG 013  ->  eeg
 EEG 014  ->  eeg
 EEG 015  ->  eeg
 EEG 016  ->  eeg
 EEG 017  ->  eeg
 EEG 018  ->  eeg
 EEG 019  ->  eeg
 EEG 020  ->  eeg
 EEG 021  ->  eeg
 EEG 022  ->  eeg
 EEG 023  ->  eeg
 EEG 024  ->  eeg
 EEG 025  ->  eeg
 EEG 026  ->  eeg
 EEG 027  ->  eeg
 EEG 028  ->  eeg
 EEG 029  ->  eeg
 EEG 030  ->  eeg
 EEG 031  ->  eeg
 EEG 032  ->  eeg
 EEG 033  ->  eeg
 EEG 034  ->  eeg
 EEG 035  ->  eeg
 EEG 036  ->  eeg
 EEG 037  ->  eeg
 EEG 038  ->  eeg
 EEG 039  ->  eeg
 EEG 040  ->  eeg
 EEG 041  ->  eeg
 EEG 042  ->  eeg
 EEG 043  ->  eeg
 EEG 044  ->  eeg
 EEG 045  ->  eeg
 EEG 046  ->  eeg
 EEG 047  ->  eeg
 EEG 048  ->  eeg
 EEG 049  ->  eeg
 EEG 050  ->  eeg
 EEG 051  ->  eeg
 EEG 052  ->  eeg
 EEG 053  ->  eeg
 EEG 054  ->  eeg
 EEG 055  ->  eeg
 EEG 056  ->  eeg
 EEG 057  ->  eeg
 EEG 058  ->  eeg
 EEG 059  ->  eeg
 EEG 060  ->  eeg
 EOG 061  ->  eog

Montage (electrode positions)

Some file formats/acquisition systems store real digitized 3D electrode positions (this sample dataset does — check raw.get_montage() below). Others, like your own .edf export, don't, and you have to attach a generic standard montage yourself by matching channel names — which is why we needed the channel-renaming step (EEG Fp1-CPzFp1) before raw.set_montage(...) would work on your file.

montage = raw.get_montage()
print(montage)
montage.plot(kind="topomap", show_names=True)
plt.show()
<DigMontage | 78 extras (headshape), 4 HPIs, 3 fiducials, 60 channels>

png

Where events live: annotations vs. stim channel

Event markers show up in one of two places depending on the recording system:

  • raw.annotations — a list of labeled time points attached to the file (common in .edf exports, like yours).
  • A dedicated stim/trigger channel — a channel whose value jumps to encode which event just happened, read with mne.find_events() (used by this sample dataset, channel STI 014).

We'll use both methods for real in Chapter 6. For now, just check what's present:

print("Annotations:", raw.annotations)
print("Any channel with 'stim' type?", "stim" in raw.get_channel_types())
# note: we already picked only eeg/eog above, so the stim channel from this file was dropped here on purpose
Annotations: <Annotations | 0 segments>
Any channel with 'stim' type? False

Visualizing raw data

Two complementary views: the raw traces over time, and the power spectral density (how much signal power exists at each frequency — a quick way to spot line noise or excessive high-frequency muscle contamination).

raw.plot(n_channels=20, duration=10, scalings="auto")
plt.show()

png

raw.compute_psd(picks="eeg").plot()
plt.show()

png

Look for a spike at 50 or 60 Hz (line noise — the notch filter target in Chapter 4) and check whether power trails off smoothly at higher frequencies or has an odd bump (possible muscle contamination).

Marking bad channels

While scrolling through raw.plot() above, a channel that's flat, extremely noisy throughout, or wildly different from its neighbors is a candidate to mark as "bad". Bad channels are excluded from most analysis and plotting by default, and can optionally be reconstructed later by interpolating from neighboring good channels (raw.interpolate_bads(), needs a montage).

raw.info["bads"] = []  # e.g. ["EEG 053"] if you spotted a bad one above

Summary

  • Every format has a dedicated mne.io.read_raw_* function, but they all produce the same Raw object.
  • Always check channel names/types, sampling rate, montage, and where events live before processing.
  • raw.plot() for time-domain inspection, raw.compute_psd().plot() for frequency-domain inspection.

Next: Chapter 4 — Preprocessing