Before any analysis, raw EEG data is almost always contaminated — noisy channels, movement artifacts, and often sampled at rates far higher than necessary. This day covers the three foundational cleaning operations that every preprocessing pipeline begins with. Think of it as quality control for your neural data: reduce redundancy (downsample), identify broken sensors (bad channels), and excise corrupted time windows (bad segments).
Why Downsample?
Nyquist, Aliasing & the Anti-Alias Filter
Most EEG amplifiers record at 500–2048 Hz, but neural oscillations of interest rarely exceed 100 Hz. Carrying all those extra samples wastes memory and slows every downstream computation. Downsampling reduces the sampling rate — but there is a critical catch.
The Nyquist theorem states that you can only faithfully represent frequencies up to half the sampling rate (fs/2). If you downsample from 1000 Hz to 250 Hz without first removing frequencies above 125 Hz, those high-frequency components “fold back” into your signal as phantom oscillations — this is aliasing, and it is irreversible.
# ALWAYS low-pass filter BEFORE downsampling raw.load_data() raw.filter(l_freq=None, h_freq=100) # anti-alias: remove >100 Hz raw.resample(sfreq=250) # safe to downsample now # Check the new sampling rate print(f"New sfreq: {raw.info['sfreq']} Hz") print(f"Nyquist: {raw.info['sfreq'] / 2} Hz")
Cohen’s intuition (ANTS Ch. 6): Think of aliasing like a wagon wheel in a movie — when the camera’s frame rate is too low, the wheel appears to spin backward. The anti-alias filter is your insurance policy: apply it before you reduce the frame rate.
Identifying Bad Channels
Flat, Noisy & Bridged Electrodes
A “bad” channel is any electrode whose signal no longer reflects the underlying neural activity. Common culprits: flat channels (dead amplifier, dried gel), noisy channels (broken wire, excessive impedance), and bridged channels (conductive gel connecting two electrodes, making them nearly identical).
Bad channels propagate errors into every subsequent step — re-referencing, ICA, source localization — so catching them early is critical. MNE lets you mark them manually or use automated detection.
# Manual marking after visual inspection raw.info['bads'] = ['Fp1', 'T8'] # Automated: RANSAC-based detection (requires autoreject) from autoreject import Ransac ransac = Ransac(verbose=False, n_jobs=1) ransac.fit(epochs) print(f"Bad channels detected: {ransac.bad_chs_}") # Interpolate: reconstruct from neighbours using spherical splines raw.interpolate_bads(reset_bads=True)
Mental model: Interpolation replaces the bad channel’s data with a weighted average of its spatial neighbours — imagine filling a hole in a topographic map by smoothly blending the surrounding elevation contours. It recovers the spatial pattern but not the original unique neural information at that site.
Pitfall: If you interpolate too many channels (>10–15% of your montage), your data becomes a smooth spatial average rather than genuine EEG. Consider whether the recording should simply be excluded.
Marking Bad Segments
Annotations & reject_by_annotation
Even after fixing bad channels, stretches of the continuous recording can be corrupted by movement,
muscle clenching, or equipment bumps. MNE uses the Annotations system — any
annotation whose description starts with "bad" is treated as a bad segment and automatically
excluded from epoching, filtering, and ICA fitting.
import mne # Interactive: click-drag in the raw plot to mark bad segments raw.plot(block=True) # Programmatic: mark a 5-second artifact starting at t=42s annot = mne.Annotations(onset=[42.0], duration=[5.0], description=['bad_muscle']) raw.set_annotations(raw.annotations + annot) # Later, epoching will automatically skip bad segments epochs = mne.Epochs(raw, events, reject_by_annotation=True)
Why “bad_” prefix? MNE treats the description prefix as a semantic tag.
bad_blink, bad_muscle, bad_movement — all are excluded automatically,
but their distinct names let you audit why data was rejected later.
Peak-to-Peak Epoch Rejection
Amplitude Thresholds for Quality Control
After epoching, the simplest artifact rejection strategy is peak-to-peak (PTP) thresholding: if any channel in an epoch swings more than a defined voltage range, the entire epoch is dropped. Typical thresholds: ~100–150 µV for EEG, ~250 µV for EOG.
This is a blunt instrument — it catches large artifacts but misses subtle ones and wastes data by rejecting entire epochs when only one channel is bad. More sophisticated approaches (ICA, autoreject) are covered in later days.
# Define PTP amplitude thresholds (in Volts) reject = dict(eeg=150e-6, # 150 uV eog=250e-6) # 250 uV # Also define minimum thresholds to catch flat channels flat = dict(eeg=1e-6) # 1 uV (practically flat) epochs = mne.Epochs(raw, events, tmin=-0.2, tmax=0.5, reject=reject, flat=flat, preload=True) # See how many epochs survived print(epochs.drop_log_stats())
Rule of thumb: If you are rejecting more than ~25% of your epochs, your thresholds may be too aggressive, or the recording quality needs addressing upstream (bad channels, filtering).
The Preprocessing Pipeline Order
When to Do What
Order matters enormously in preprocessing. Here is the canonical pipeline that most EEG labs follow:
Step 1
Import &
Montage
Step 2
Mark Bad
Channels
Step 3
Filter
(HP + LP)
Step 4
Downsample
Step 5
ICA / ASR
Artifact Fix
Key dependencies: Filter before downsampling (anti-alias). Mark bad channels before ICA (bad channels corrupt unmixing). Interpolate after ICA (preserve rank for decomposition).