EEG motor dataset, cannot see the expected reslts

Your outline is close, but I would change a few points before automating it.

  1. Make the run explicit. all_data["S001"][0] does not show which run index 0 represents. In EEGMMIDB, T1/T2 depend on the run: for runs 3, 4, 7, 8, 11 and 12 they represent left/right fist; in the other task runs they represent both fists/both feet. Please store and verify the run number before epoching.

Also, np.arange(8, 12) gives 8–11 Hz. Use np.arange(8, 13) if you want 8–12 Hz. Beta should be calculated separately.

  1. These are not the original event counts:
original_t1 = len(epochs["T1"])
original_t2 = len(epochs["T2"])

Epochs may already have removed events at recording boundaries or because of bad annotations. Count the original events directly:

original = {
    label: int(np.sum(events[:, 2] == code))
    for label, code in event_id.items()
}

Also, epochs.drop_bad() will not add meaningful amplitude rejection unless reject or flat criteria were configured. Use the same predefined criteria for every participant, and inspect epochs.drop_log and epochs.drop_log_stats() for the actual reasons.

  1. Ten seconds is not enough for raw-data QC. Inspect the full run or several representative sections. Look for flat channels, clipping, abrupt steps, sustained drift, repeated large transients and persistent narrow-band noise. Compare C3/C4 with neighbouring channels and inspect their PSD. A channel should be marked bad because of signal quality, not because its ERD pattern is weak.

  2. vlim=(None, None) scales each plot independently. Calculate one limit from both channels and reuse it:

motor_data = power_mu.copy().pick(["C3", "C4"]).data
limit = np.nanpercentile(np.abs(motor_data), 99)

for channel in ("C3", "C4"):
    power_mu.plot(
        picks=channel,
        vlim=(-limit, limit),
        title=f"S001 T1 {channel}",
    )

This gives C3 and C4 the same robust colour scale. Use the same approach for T2.

  1. Minima and maxima are very sensitive to single artifacts. For the QC table, I would store retained epoch counts, drop percentage and reasons, bad-channel status, and robust percentiles calculated over the same time-frequency window.

Process both T1 and T2 within the same participant loop. This keeps their run mapping, preprocessing and QC paired. Save one row per participant and condition, then calculate participant-level contrasts and only afterwards perform the grand average.

Thank you! Since I already loaded all participants’ run 3 from the beginning

for sf in subfolders: 
subject_raws = 
folder = Path(base / sf) #  base is this:\Users\ginuk\Downloads\eeg-motor-movementimagery-dataset-1.0.0\files. add each subject to the base

for file_path in folder.rglob("*R03.edf"):
    raw = mne.io.read_raw_edf(file_path, preload=True, verbose="WARNING")

I could do this later

``for sf in subfolders:

subject_r03 = all_data[sf][0]```

to indicate that we are getting the each subjects r03. This is what you meant by making the run explicit right?

Also when I did this:

original = {
label: int(np.sum(events[:, 2] == code))

for label, code in event_id.items()

}`I got the number of objects in each event label. This is similar to when I did raw_resampled.annotations. Just to confirm, this contains all the events under each event label right? And not the epochs count? I’m not sure if these numbers remain the same even after rejecting bad epochs. I tend to get confused between events and epochs.

Yes — since you are loading files with *R03.edf, the run is already explicit. all_data[sf][0] is fine if exactly one R03 file is loaded per participant.

original counts events in the original events array, before epoch rejection. Dropping bad epochs does not change that array or the raw annotations. To count retained epochs after rejection:

epochs.drop_bad()
retained = {
    label: int(np.sum(epochs.events[:, 2] == code))
    for label, code in event_id.items()
}

You can also inspect epochs.drop_log to see which epochs were removed.