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-CPz → Fp1) 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>

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.edfexports, 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, channelSTI 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()

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

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 sameRawobject. - 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.