Epoching and baseline
Events to epochs, choosing windows, justifying baseline correction, baseline as a covariate, and overlapping trials.
Prerequisites: L2.8 · Pipeline order and reproducibility
3 claims on this page are unverified. TODO(confirm) marks a specific statement the author has not yet checked against a
primary source. Everything else on this page has been reviewed. Treat a marked claim as
provisional and go to the cited source rather than quoting the sentence.
Objectives
- Convert events to epochs
- Choose epoch windows
- Apply and justify baseline correction
- Describe baseline as a covariate alternative
- Handle overlapping trials
Why this matters
Epoching is where continuous data becomes trials, and two decisions that look clerical — where the window starts and ends, and which stretch of it counts as zero — fix the meaning of every number you will report afterwards. A baseline window that happens to contain a condition difference moves that difference into the rest of the epoch with its sign reversed; an epoch too short for a slow component clips it, and no later analysis can put it back. This lesson makes both choices explicit and shows what each one costs.
Concepts
From events to epochs
An event is a pair: a sample index and a code. In MNE the event array has one row per event with three columns — sample, previous value, event id — and event_id maps readable names onto those ids. Epoching cuts a fixed window around each event:
epochs = mne.Epochs(raw, events, event_id={'target': 1, 'standard': 2},
tmin=-0.2, tmax=0.8, baseline=(-0.2, 0),
preload=True, metadata=trial_table)
Two things follow from that one call. First, an epoch is a view of the continuous data: everything already done to the continuous recording — filtering, re-referencing, ICA cleaning, annotation-based rejection — is inside it, which is why the canonical order of L2.8 puts epoching near the end. Second, the epoch is defined by an event, so every problem with the events is now a problem with the epochs: a constant lag between the trigger and the physical stimulus shifts every latency you will report (pf-trigger-offsets, L0.3), and event codes that pack several factors into one integer have to be unpacked before they can be modelled.
Events reach the epoching call from a stimulus channel (mne.find_events), from annotations (mne.events_from_annotations) or from a BIDS _events.tsv. L2.1 is where those are made trustworthy; this lesson assumes they already are.
Choosing the epoch window
Three constraints bound the window, and they pull in different directions.
- It must contain the component, plus enough pre-stimulus data to estimate a baseline. A window running from about −200 ms to somewhere between 800 and 1000 ms is a common choice for a late positive component; earlier sensory components need far less after the stimulus and no less before it. TODO(confirm): typical windows vary by paradigm and lab, and the numbers here are nominal textbook ranges for expert review, not values taken from a source.
- It must keep filter and edge artifacts away from the measurement window. Filtering the continuous data before epoching (L2.4) is what keeps filter edge effects out of the epoch entirely; filtering epochs puts them inside it (
pf-filter-epoched-data, L1.5). Time-frequency analysis (L4.2) needs additional padding at both ends that an ERP does not. - It should not routinely contain the next event. When it does, the epochs overlap — the subject of a section below.
Making the window wider is cheap before you have looked at the data and expensive afterwards, because widening it later is a decision taken with the effect in view. Choose it generously and in advance for the analyses you might want, and crop when you measure (L3.3).
Baseline correction is a subtraction, and it carries an assumption
Baseline correction subtracts, per epoch and per channel, the mean of the signal over a baseline interval B:
x'(t) = x(t) − mean( x(t) for t in B )
That is the whole operation. What it buys is the removal of the slow, trial-to-trial offset that would otherwise add variance to the average: residual drift that the high-pass did not take, a slow change in electrode potential, the tail of the previous trial. What it assumes is that the baseline interval contains nothing that differs between conditions or covaries with the trial — that its expected value is the same everywhere you will compare.
Because the same constant is subtracted from every sample of the epoch, a difference that lives in the baseline reappears, with its sign reversed, across the whole rest of the epoch. That is the entire mechanism of pf-baseline-contamination, and it is why the effect can flip sign when the baseline window moves.
The conventional choice is the pre-stimulus interval, commonly −200 to 0 ms. Two alternatives are worth knowing. A longer baseline estimates the offset more precisely but reaches further back, and so is more likely to catch the tail of the preceding trial. A baseline placed somewhere else entirely — before a preceding cue, say — is the right choice when the interval just before the stimulus is exactly where the experimental manipulation acts, which is the usual situation in cued designs.
Baseline correction is not free. (Alday, 2019) makes the arithmetic explicit: subtracting an estimated mean adds that estimate’s own noise to every sample in the epoch, so the variance of the corrected waveform is the variance of the signal plus the variance of the baseline mean. A short, noisy baseline therefore makes the whole epoch noisier. It also interacts with the high-pass filter, which is already doing a version of the same job (L2.4): applying an aggressive high-pass and then subtracting a baseline is doing it twice, and neither operation is harmless for a slow component.
Baseline as a covariate
The alternative to subtracting the baseline is to model it. Leave the data un-baselined and fit, per trial,
amplitude ~ condition + baseline_mean
The regression estimates how much of the post-stimulus amplitude the baseline actually predicts and removes only that much; the subtraction assumes the coefficient is exactly one. (Alday, 2019) argues that this is the better default when the assumption behind subtraction is doubtful, because it does not force the baseline’s noise into every trial at unit weight, and because it makes the relationship between baseline and response an estimated quantity rather than a fixed one.
What it costs: the analysis becomes a model, with the model’s assumptions and its own diagnostics, and the result is no longer a waveform you can simply plot — you have to decide what value of the covariate the plotted waveform corresponds to. L3.6 (single-trial regression) and L6.2 (mixed models) are where this becomes routine rather than exotic.
Overlapping trials
When the interval between events is shorter than the epoch, the response to the next trial sits inside the current epoch. Averaging does not remove it: if the stimulus onset asynchrony is constant, the neighbouring response is time-locked too, and it appears in the average as a deflection that looks exactly like a component.
Three responses, in increasing order of effort:
- Jitter the interval. If the onset asynchrony varies from trial to trial, the overlapping response is smeared across latencies and largely averages out. This is a design decision taken before recording (L3.5), and it is by far the cheapest fix.
- Keep the epoch short enough that the overlap falls outside the measurement window. Cheap, but it clips slow components and does nothing about overlap from the preceding trial reaching into the baseline.
- Deconvolve. Model the recording as a sum of overlapping responses and estimate each one separately — the ADJAR family historically, linear deconvolution in current practice. L3.6 covers it in overview.
The case where overlap is not merely noise is when the interval distribution differs between conditions. Then the overlapping response differs between conditions too, and it produces a difference that no amount of averaging will remove.
Metadata belongs on the epochs
epochs.metadata is a table with one row per epoch. Build it at epoching time, while the mapping from event to trial is still obvious, and put in everything you might later condition on: reaction time, accuracy, stimulus identity, block, trial index within block, the interval to the previous and next event. Selection then reads like the design:
correct = epochs["accuracy == 1"]
Any pandas query expression works, so reaction-time bands, stimulus identity and block all become one-line selections. Metadata is also the precondition for single-trial regression (L3.6) and for mixed models (L6.2), both of which need per-trial predictors aligned to per-trial data. Because the rows follow the surviving epochs through drop_bad(), and because epochs.drop_log records why each dropped epoch was dropped, building the table at epoching time is what keeps trial-level covariates aligned after rejection (L2.5). Reconstructing them afterwards from the original event list is possible and is a reliable source of off-by-one errors.
The baseline window has exactly the status of a filter cutoff: an analysis choice that must be fixed before the effect is visible, and reported. The test is a counterfactual — if you had moved the baseline window and the effect had grown, would you have published the other window? If you cannot answer that before running the comparison, the window is not pre-specified, and the honest report shows the effect under both.
The data behind this lesson
ds-erpcoreis the ERP dataset for Level 3: CC BY 4.0, open access and per-subject downloadable; Biosemi ActiveTwo, 30 EEG + 3 EOG channels in a 10-20 placement scheme, 1024 Hz, mains 60 Hz, no software filters, 40 participants per paradigm, recorded against the Biosemi CMS arrangement. The P3 paradigm is the one this level uses throughout. TODO(confirm): the author mirrors the ERP CORE entry into the catalogue registry and signs off the dataset page (§10.11 item 8); the shipped asset sidecars also record a licence conflict in the source — the OSF node record says CC BY 4.0, the per-paradigm component’s own LICENSE file says CC BY-SA 4.0 and its dataset_description.json says CC0 — which the author reconciles (§13 item 22).- The widget ships sub-001’s P3 trials — 200 of them, 40 target and 160 standard, at four midline channels — cut over −1.0 to 1.5 s, which is wider than any sensible analysis window, and deliberately stored without baseline correction, so that
tmin,tmaxand the baseline can all move inside real data rather than being recomputed from a model. Each trial carries its condition, its reaction time, its accuracy and the gap to the next stimulus (median about 1.5 s), which is what lets the overlap panel show real overlap rather than a cartoon of it. The asset is band-passed 0.1–40 Hz, re-referenced offline to the average of P9 and P10 and resampled to 256 Hz; its sidecar records all of it.
Explore
A productive sequence: start with the conventional window and baseline and note the shape of the average; drag the baseline earlier and then later while watching the whole epoch move up or down, not just the baseline; shrink the baseline to a few tens of milliseconds and watch the average get noisier rather than cleaner; then extend tmax until the next event enters the window and look at what appears at the end of the average. Finish by putting the baseline where the two conditions differ before the stimulus, and watch the difference wave invert.
Practice
Epoching and baseline: Epochs with trial metadata on ERP CORE P3, and what the P3 does when the baseline window moves nb-3-1-epochs
Downloads from ds-erpcore.
The notebook builds Epochs with metadata from ds-erpcore P3 events, compares baseline variants — the conventional pre-stimulus window, a shorter one, a deliberately contaminated one and no baseline at all — and reports the P3 measurement under each. Its final cell prints the numbers this lesson’s exercise asks for.
Exercises
Exercise ex-3-1-contaminated-baseline
NumericMove the baseline window so that it covers the interval in which the two conditions already differ before the stimulus. Report the P3 mean amplitude (target minus standard, at Pz, in the notebook's a-priori window) with that baseline.
Exercise ex-3-1-conventional-baseline
NumericNow report the same measurement with the conventional pre-stimulus baseline.
Exercise ex-3-1-explain-baseline-change
Free responseExplain the difference between the two numbers. What did moving the baseline actually do to the data, and which of the two results would you report?
Exercise ex-3-1-overlap
Multiple choiceYour design uses a fixed 800 ms stimulus onset asynchrony and you epoch from −200 to 1000 ms, so every epoch contains the next stimulus. Which statement is correct?
Pitfalls
Baseline contains condition differences
- Symptom
- Effect flips sign when the baseline window moves.
- Cause
Baseline correction subtracts one constant — the mean over the baseline interval — from every sample of the epoch, separately for each trial and each channel. If the conditions differ during that interval, a different constant is subtracted in each condition, and the difference reappears across the whole rest of the epoch with its sign reversed:
- Detect
- Plot the un-baselined averages. This is the whole diagnosis. If the conditions are separated before the stimulus, the assumption behind the subtraction is false, and the measured effect is a difference of differences. - Measure in the baseline. Run the same statistical test on the mean amplitude of the baseline interval itself. A significant condition difference there is the problem, stated as…
- Fix
- Fix the baseline interval before you look at the effect, and report it. Sensitivity analyses are a check on a pre-specified choice, not a menu. - Move the baseline to an interval the manipulation cannot reach — before a preceding cue, or before the trial’s condition is knowable to the participant. In a cued design this is usually possible and is the cleanest fix. - Model the baseline instead of…
In other tools
In other toolsEEGLAB · FieldTrip — names only
The equivalents of what this lesson does, for a reader who works in another toolbox. Function names only: their own documentation is the place to learn how to call them.
EEGLAB
pop_epochEEGLABpop_rmbaseEEGLAB
FieldTrip
ft_definetrialFieldTripft_preprocessing(demean)FieldTrip
Names checked 2026-09-18 against EEGLAB 2026.0.0 (plugins at the versions in EEGLAB’s own plugin list) and FieldTrip 20251218.
Reading
- Alday (2019). How much baseline correction. unverified
- Luck (2014). An Introduction to the Event-Related Potential Technique, 2nd ed.. unverified