EEG motor dataset, cannot see the expected reslts

:red_question_mark: If you have a question or issue with MNE-Python, please include the following info:

  • MNE version: 11.0
  • operating system: Windows 11

So I tried analyzing data for all 109 participants in this dataset EEG Motor Movement/Imagery Dataset v1.0.0, and made C4 and C3 plots for the alpha and beta frequency bands for the left hand activity. I attached the plots below, please take a look. I’m not sure if this is the expected pattern I should see. I was expecting a stronger difference in the alpha band (C3 is stronger), but instead I saw this effect in the beta band.

In the meantime, I found this paper which I think is relavant: The Cortical Physiology of Ipsilateral Limb Movements - PMC . According to this paper, it says that ipsilerateral brain activity in both beta and alpha is also there during movement, which seems to align with what I found. However, I haven’t found any papers of the unique roles of alpha vs beta in hand movement. Can you please clarify what is expected, and any papers you have read related to this?


As an additional step, I looked at the numerical power values with each of the plots with this code (this is just one of the analyses for LEFT hand ALPHA. I did the same code for all frequencies of interest and both hands). This is to confirm that I get the desired power effects despite the plot not showing it.

print(grand_power_left_alpha.ch_names.index("C3"))
print(grand_power_left_alpha.ch_names.index("C4")) #this tells the numerical index for the location of c3 and c4

c3_idx = grand_power_left_alpha.ch_names.index("C3")
c4_idx = grand_power_left_alpha.ch_names.index("C4")

c3_power = grand_power_left_alpha.data\[c3_idx\]
c4_power = grand_power_left_alpha.data\[c4_idx\]

c3_alpha = c3_power.mean(axis=0)
c4_alpha = c4_power.mean(axis=0)

time_mask = (grand_power_left_alpha.times >= 0) & (grand_power_left_alpha.times <= 0.8)

print("C3 alpha:", c3_alpha\[time_mask\].mean())
print("C4 alpha:", c4_alpha\[time_mask\].mean())

and got this:

C3 alpha: 5.715649001507285e-09
C4 alpha: 7.653899077490554e-09

→ as expected the C4 is larger. I got the same pattern for LEFT BETA as well

However for RIGHT BETA, C4 is larger, which doesn’t seem right.

c3_idx_right = grand_power_right_beta.ch_names.index("C3")

c4_idx_right = grand_power_right_beta.ch_names.index("C4")

c3_power_right = grand_power_right_beta.data\[c3_idx_right\]

c4_power_right = grand_power_right_beta.data\[c4_idx_right\]

c3_beta_right = c3_power_right.mean(axis=0)

c4_beta_right = c4_power_right.mean(axis=0)

time_mask_right = (grand_power_right_beta.times >= 0) & (grand_power_right_beta.times <= 0.8) #"Give me only C3 alpha values during movement (0–0.8 seconds)."

print("C3 beta:", c3_beta_right\[time_mask_right\].mean())#make sure to use the new mask at the top!!

print("C4 beta:", c4_beta_right\[time_mask_right\].mean())
C3 beta: 1.5978303070695456e-09
C4 beta: 2.2528216151065487e-09

Why don’t I get the correct results? I would appreciate your guidance.

Hi,

Could you format your code snippet inside triple-backticks? Ideally, also post the code that builds grand_power_left_alpha, it could be related to epoching, baseline, or averaging.

I’m not sure what “the correct results” means in this context. Are you trying to replicate a specific published work or a general expectation about contralateral lateralisation?

Cheers,

Carina

Thank you for your reply. Here is my above code with backticks added.

Here’s my code for grand_power_left_alpha:

power_list_left_alpha = \[ \]

for subject, raws in all_data.items():
raw_resampled = raws\[0\]

events, event_id = mne.events_from_annotations(raw)
epochs = mne.Epochs(raw_resampled, events, event_id, tmin=-0.5, tmax=2, preload=True)#we have to get a bigger range for alpha because it has longer wavelengths

left_epochs = epochs[“T1”]

power = mne.time_frequency.tfr_morlet(
left_epochs,
freqs=np.arange(8, 12),
n_cycles=7,
return_itc=False
)

power_list_left_alpha.append(power)

grand_power_left_alpha = mne.grand_average(power_list_left_alpha)

print(‘done’)


My expected result is having greater power in mu band (which i thought was the same as alpha band) in the contralateral hemisphere, but I realized I understood it wrong. Instead, it's supposed to be mu suppression. I think the below link has the expected results. Sorry for the confusion, I'm still figuring this out.

https://www.bci2000.org/mediawiki/index.php/User_Tutorial:Introduction_to_the_Mu_Rhythm

Hi,

Yes, your correction about mu suppression is the right direction. Looking at the code, I would check a few implementation details before interpreting the C3/C4 difference.

First, the events and epochs appear to use different objects:

raw_resampled = raws[0]
events, event_id = mne.events_from_annotations(raw)
epochs = mne.Epochs(raw_resampled, events, event_id, ...)

Apart from raw possibly referring to a different recording, event sample indices may no longer match after resampling. I would either create the epochs before resampling or resample the events together with the data:

events, event_id = mne.events_from_annotations(raw)
raw_resampled, events_resampled = raw.copy().resample(
    new_sfreq, events=events
)
epochs = mne.Epochs(
    raw_resampled,
    events_resampled,
    event_id,
    tmin=-1.5,
    tmax=2,
    preload=True,
)

Second, if raws contains several runs, raws[0] uses only the first one. It is worth checking which run this is. In EEGMMIDB, the meanings of T1 and T2 depend on the run: they represent left/right fist in runs 3, 4, 7, 8, 11 and 12, but both fists/both feet in runs 5, 6, 9, 10, 13 and 14. Executed and imagined movement should also be analysed separately.

Finally, I would apply baseline correction to each subject’s TFR before the grand average, for example:

power.apply_baseline(baseline=(-1.0, 0), mode="percent")

With percent baseline correction, more negative values represent stronger suppression. I would then calculate the within-subject contralateral-versus-ipsilateral difference before averaging across participants, rather than comparing the absolute C3 and C4 power values after the grand average.

The MNE ERDS example follows this general approach:

Ok, thanks. Can this above code be done with time frequency plots using tfr morlet? I am trying to apply what i learned from an eeg course i did on udemy, and I don’t want to overcomplicate everything fast. Is it necessary to use ERD for this purpose?

I would like your guidance on how to apply tgis for time frequency plots.

Yes — you can use Morlet wavelets for this. Morlet TFR and ERD are not two competing approaches.

The Morlet transform estimates how spectral power changes across time and frequency. ERD/ERS describes those same power changes relative to a pre-movement baseline. Therefore, you do not need a separate “ERD function”; you can compute a Morlet TFR and then apply baseline normalization:

freqs = np.arange(6, 31)

power = epochs.compute_tfr(
    method="morlet",
    freqs=freqs,
    n_cycles=freqs / 2,
    return_itc=False,
    average=False,
    decim=2,
)

power.apply_baseline(
    baseline=(-1.0, 0),
    mode="percent",
)

After this correction, negative values indicate power suppression relative to baseline (ERD), while positive values indicate increased power (ERS). You can then average the trials for each condition and plot C3 and C4.

For your lateralisation question, baseline-normalized power will usually be more informative than absolute power because absolute C3/C4 values can differ for reasons unrelated to the movement condition.

I would first test this on one subject and one correctly identified run. The event/resampling and T1/T2 issues mentioned above still need to be resolved before interpreting the group result.

```(power_beta.apply_baseline(baseline=(-1.0, 0), mode="percent")``` So I tried doing this before the grand average and it says the baseline interval is greater than the epochs data, which makes sense. My epochs range is this; tmin=-0.2, tmax=0.8, which I chose because that's what was shown to me in the tutorial in my course. Do I change the epochs range or the baseline range? Which do you reccomend and why?

Yes, I would extend the epoch rather than shorten the baseline to (-0.2, 0).

With your current epochs, MNE is correct: the interval (-1, 0) is not fully present in the data. Also, 200 ms is very short for a stable spectral baseline, particularly if you apply the same approach to the 8–12 Hz range. At 8 Hz, it contains fewer than two cycles.

For example:

epochs = mne.Epochs(
    raw_resampled,
    events_resampled,
    event_id,
    tmin=-1.5,
    tmax=2.0,
    baseline=None,
    preload=True,
)

# Select the relevant condition after confirming
# what T1/T2 mean in this particular run.
condition_epochs = epochs["T1"]

power_beta = mne.time_frequency.tfr_morlet(
    condition_epochs,
    freqs=np.arange(13, 31),
    n_cycles=7,
    return_itc=False,
)

power_beta.apply_baseline(
    baseline=(-1.0, 0.0),
    mode="percent",
)

# Optional: create a separate object showing only the movement interval
power_beta_plot = power_beta.copy().crop(tmin=0.0, tmax=0.8)

Apply the baseline to each subject’s TFR before calculating the grand average.

This assumes that -1…0 s is genuinely a resting pre-movement interval. If a cue or another part of the task already starts before time zero, the baseline should instead be chosen before that cue.

Ok thanks. Also, which result should I be expecting if I’m using TFR, given that there is mu suppression in the opposite hemisphere to the hand movement? Will it show as low power? Please let me know.want to know what to expect after baseline correction. Ideally, blue should represent low power/negative values.

Yes. With mode="percent":

  • 0 means no change from baseline;
  • negative values mean lower power, or ERD/mu suppression;
  • positive values mean higher power, or ERS.

For right-hand movement, you would typically expect a more negative mu-band change over C3 than C4. For left-hand movement, the pattern is usually reversed.

For example, if C3 is -0.30 and C4 is -0.05 during right-hand movement, that means approximately a 30% decrease over C3 versus a 5% decrease over C4 — stronger contralateral suppression over C3.

Blue represents suppression only if your chosen colormap maps negative values to blue. Always check the colorbar and use a diverging scale centered on zero. Also, not every participant will show equally clear lateralisation, so keep executed and imagined movement separate.

I’m only looking at real movement in this data file. Here is the code I wrote for epoching one participant’s data before making a plot, incorporating baseline correction

```events, event_id = mne.events_from_annotations(subject1)
epochs = mne.Epochs(subject1, events, event_id, tmin=-1.5, tmax=2, preload=True)#we have to get a bigger range for alpha because it has longer wavelengths

left_epochs = epochs[“T1”]

power_mu = mne.time_frequency.tfr_morlet(
left_epochs,
freqs=np.arange(8, 12),
n_cycles=7,
return_itc=False
)
power_mu.apply_baseline(baseline=(-1, 0), mode=“percent”)```

Here is what I got for left and right hemisphere activity for LEFT hand movement in one participant after plotting:

I realized the scale on the colour bar is slightly different between these two graphs. However, it is centered on zero as you suggested it should be. These results are still inconclusive to me, as I expected more dark blue (more negative values) in the contralateral hemisphere. Can you please let me know if there is anything to fix?

Should i use my old approach with the time mask to look at the numerical values of power? please let me know if i should use this approach again.

Edit:Using the old approach i got these values, which still don’t give the expected results of mu suppression. Can you please help me understand why this is that case?

C3: -0.04203356897831294
C4: 0.1567672136040133

Hi @viranovskaya, did you see my previous question? Please let me know.

Hi, yes, I saw it — sorry for the delay.

Your calculation shows that this participant does not have the expected contralateral mu suppression in the selected 0–0.8 s window: for left-hand movement, C3 changes by about −4.2%, while C4 increases by about 15.7%. This is not necessarily a coding error, since a clear lateralised pattern is not guaranteed in every participant.

Before interpreting it, I would check a few things:

  1. Add baseline=None when creating the epochs. Otherwise, MNE applies a time-domain baseline correction before the TFR baseline correction:
epochs = mne.Epochs(
    subject1,
    events,
    event_id,
    tmin=-1.5,
    tmax=2.0,
    baseline=None,
    preload=True,
)
  1. Confirm that T1 means executed left-fist movement in every run you included. In this dataset, the meaning of T1 and T2 depends on the run, so execution, imagery, bilateral-fist and feet runs should not be combined under the same label.

  2. Use exactly the same color limits for the C3 and C4 plots. In the screenshot, the two color bars have very different ranges, so their colors cannot be compared directly.

  3. Try a later window as well, for example 0.5–2.0 s. With seven cycles in the 8–11 Hz range, the estimate is temporally smoothed around movement onset.

The time-mask approach is fine for a numerical summary, but I would first run these checks and then examine the effect across participants rather than deciding from one participant alone.

Thanks for the reply. I adjusted the color bars to make it more consistent, but first, I looked at the minimum and maximum powers that are there for c3 and c4.

Then, based on that, toI set the vmin and vmax -2.53 and 2.53 with center as 0. However, I also realized that I set vmin and vmax to any number; it doesn’t show an error of it being out of range. Can you explain why this is? and which numbers should I ideally set as vmin and vmax? please let me know

```#epoching one subject
events, event_id = mne.events_from_annotations(subject)
epochs = mne.Epochs(subject1, events, event_id, tmin=-0.5, tmax=2, baseline=None, preload=True)#we have to get a bigger range for alpha because it has longer wavelengths

left_epochs = epochs[“T1”]

power_mu = mne.time_frequency.tfr_morlet(
left_epochs,
freqs=np.arange(8, 12),
n_cycles=7,
return_itc=False
)
power_mu.apply_baseline(baseline=(-0.5, 2), mode=“percent”)```

and got this plot, which still doesn’t clearly show mu suppression in contralateral hemisphere:

I don’t understand why for epochs, you suggested baseline=none but for power_mu, you suggested baseline=percentage. Can you explain the difference between the two and why you used different ones?

I want to make sure I’m doing this right for one subject before doing the same for other subjects and comparing across them

EEG Motor Movement/Imagery Dataset v1.0.0 - I checked this before, but please confirm if in trial 3 T1 is left fist and T2 is right fist (real movement).

Thank you

Hi,

The main issue is this line:

power_mu.apply_baseline(baseline=(-0.5, 2), mode="percent")

This uses the whole epoch, including the movement period, as the reference. For ERD, the baseline should contain only pre-movement activity. I would change the epoch and baseline back to:

events, event_id = mne.events_from_annotations(subject1)

epochs = mne.Epochs(
    subject1,
    events,
    event_id,
    tmin=-1.5,
    tmax=2.0,
    baseline=None,
    preload=True,
)

left_epochs = epochs["T1"]

power_mu = mne.time_frequency.tfr_morlet(
    left_epochs,
    freqs=np.arange(8, 12),
    n_cycles=7,
    return_itc=False,
)

power_mu.apply_baseline(
    baseline=(-1.0, 0.0),
    mode="percent",
)

baseline=None in mne.Epochs means that no time-domain voltage baseline is subtracted. power_mu.apply_baseline() is a different step: it expresses spectral power relative to the pre-movement period. That is why the two settings are not contradictory.

The colour limits only control the display. MNE allows values outside the data range because changing them does not change the underlying TFR values; it only changes the colour mapping. For comparing C3 and C4, use the same limits for both plots, preferably a scale centred on zero. The automatic vlim=(None, None) is a reasonable starting point.

Also, please make sure that events and epochs are created from the same object. In the posted snippet, events come from subject while epochs use subject1.

For run 3, yes: T1 is executed left-fist movement and T2 is executed right-fist movement.

After fixing the baseline, a single participant may still not show a very clear contralateral pattern. That alone does not mean the code is wrong.

Ok thanks. I just made these changes. Now, I’m going to look at a few subjects individually by changing the subject number in the brackets of this code each time I run it for another subject. subject = all_data[“S001”][0]. I remember you saying I should look at individual subjects first before doing a grand average. What should I check for exactly in each participants before moving onto the grand average?

I would make a small QC table for each participant rather than deciding from the plot alone.

For each participant, I would check:

  1. The correct run and event mapping are used, with the same preprocessing, epoch window and baseline for everyone.
  2. The number of retained epochs for T1 and T2, including how many were dropped and why.
  3. Signal quality around C3 and C4: bad channels, large artifacts or unusually noisy epochs.
  4. T1 and T2 time-frequency plots made with identical settings and colour limits. Look for a movement-related decrease in mu and/or beta power relative to baseline, but do not require every participant to show a textbook pattern.
  5. Extreme values that could dominate the group average.

It may also help to compute a within-participant contralateral-minus-ipsilateral contrast before averaging across participants. For left-fist movement this would compare C4 with C3; for right-fist movement, C3 with C4. Because ERD values are negative after percent baseline correction, a more negative contrast would indicate stronger contralateral suppression.

I would exclude a participant only for a predefined data-quality reason, such as too few usable epochs or unusable motor channels, not simply because the expected lateralisation is weak. Once these checks are consistent, average the participant-level results rather than pooling all trials across participants.

Thank you. Here is my code outline for the steps. Please look at my code for the steps below.

1. The correct run and event mapping are used, with the same preprocessing, epoch window and baseline for everyone.

```events, event_id = mne.events_from_annotations(subject)

epochs = mne.Epochs(subject, events, event_id, tmin=-1.5, tmax=2, baseline=None, preload=True)

left_epochs = epochs[“T1”]

power_mu = mne.time_frequency.tfr_morlet(

    left_epochs,

    freqs=np.arange(8, 12),

    n_cycles=7,

    return_itc=False

)

power_mu.apply_baseline(baseline=(-1, 0), mode=“percent”)```

#do same for right hand, if I am

2.Number of retained epochs for t1 and t2

```original_t1 = len(epochs[“T1”])

original_t2 = len(epochs[“T2”])

epochs.drop_bad()

retained_t1 = len(epochs[“T1”])

retained_t2 = len(epochs[“T2”])

print(“T1:”, original_t1, “→”, retained_t1)

print(“T2:”, original_t2, “→”, retained_t2)```

3. Signal quality around c3 and c4, bad channels

```subejct.plot(

picks=\["C3", "C4"\],

duration=10,

n_channels=2

)```

What exactly do I look for here? Do I look for abnormal spikes? Please specify.

4. T1 and T2 time-frequency plots made with identical settings and colour limits. Look for a movement-related decrease in mu and/or beta power relative to baseline, but do not require every participant to show a textbook pattern.

For this, I do what I already did, right? With this:

```power_mu.plot(

picks="C3",  vlim=(None, None),

title="S001 Left-hand Mu Same Hemisphere"

)

power_mu.plot(

picks="C4", vlim=(None, None),

title="S001 Left-hand Mu Opposite hemisphere"

)```

Also, I realized when I do vlim=(None, None) for both of them, they show different color scales. Why is that and how do I fix it?

5. Extreme values

```c3_power = power_mu.data[c3_idx]

c4_power = power_mu.data[c4_idx]

print(“C3 max:”, c3_power.max()) print(“C3 min:”, c3_power.min()) ````

#see if there are similar between participants, look for outlier

I loop through all of these for all the participants, right? Then automate a table from this?

Also, I think it’s easier to look at T1 first, for all participants, with all the steps. Then move onto T2. Is this a reasonable approach?