epoch metadata: getting the first event that occurred after the time locked one, when both have the same HED.

Hello everyone,

I have events that all have a HED that start with “Stimulus”. I want to know, for each stimulus, what type of stimulus followed it. I thought I could do that by using the “keep_first = “Stimulus”” argument in the code below, and I know this would work perfectly if the time locked event and the following one were not of the same type. But of course, in this case, since the time locked event is a Stimulus itself, it always gets registered as the first one, with a time of 0.

tmin = -0.1 
tmax = 0.9
baseline = (-0.1, 0)
reject_criteria = dict(eeg=100e-6, eog=100e-6)

metadata, events, event_id = mne.epochs.make_metadata(
events=events, event_id=event_id, sfreq=cleaned.info['sfreq'],
tmin= 0, tmax= 3,
keep_first="Stimulus")

epochs = mne.Epochs(cleaned, events, event_id, tmin, tmax, baseline=baseline, metadata=metadata, preload=True)

So, I thought I could simply change the “tmin” argument to 0.01sec to exclude that time locked event. But then I get the error below. As I am sure that there are some events in that time window, that the script does catch when tmin=0, I think it means I just cannot use a metadata time window that does not include the time locked event itself.

"Traceback (most recent call last):

Input In [18] in <cell line: 110>
metadata, events, event_id = mne.epochs.make_metadata(

File ~\anaconda3\lib\site-packages\mne\epochs.py:2879 in make_metadata
assert not events_in_window.empty

AssertionError"

I feel like this should be simple and I am missing something obvious… but I am stuck.

Thank you in advance for your reply.

  • MNE version: e.g. 1.4.2
  • operating system: Windows 11

Hello @r.lamarque, this is an interesting question. Could you perhaps share a sequence of the event names as it appears in your experiment? Or maybe even share a sample data file?

Best wishes,
Richard

Hello Richard,

Thank you for your reply.

I do not think I can share a sample data file, but I have saved the epochs metadata for one participant (for tmin=0, tmax=3, here: _FullEpochsMetadata.csv - Google Drive), and I think it gives an idea of how they appear in sequence. In case that is useful, here is my event_id dictionary:

event_id = {
        'Stimulus/Standard/Underived/SMA/sla': 30,
        'Stimulus/Standard/Derived/SSMA/ssla': 90,
        'Stimulus/Standard/Underived/SMA/sma': 10,
        'Stimulus/Standard/Derived/SSMA/ssma': 70,
        'Stimulus/Standard/Underived/SMA/sna': 20,
        'Stimulus/Standard/Derived/SSMA/ssna': 80,
        'Stimulus/Standard/Underived/MA/la': 60,
        'Stimulus/Standard/Underived/MA/ma': 40,
        'Stimulus/Standard/Underived/MA/na': 50,
        'Stimulus/Standard/Underived/ESMA/esla': 31,
        'Stimulus/Standard/Underived/ESMA/esma': 11,
        'Stimulus/Standard/Underived/ESMA/esna': 21,
        'Stimulus/Standard/Derived/ESMA/esla': 91,
        'Stimulus/Standard/Derived/ESMA/esma': 71,
        'Stimulus/Standard/Derived/ESMA/esna': 81,
        'Stimulus/Standard/Underived/EMA/ela': 61,
        'Stimulus/Standard/Underived/EMA/ema': 41,
        'Stimulus/Standard/Underived/EMA/ena': 51,
        'Stimulus/Deviant/Underived/SMA/sla': 130,
        'Stimulus/Deviant/Derived/SSMA/ssla': 190,
        'Stimulus/Deviant/Underived/SMA/sma': 110,
        'Stimulus/Deviant/Derived/SSMA/ssma': 170,
        'Stimulus/Deviant/Underived/SMA/sna': 120,
        'Stimulus/Deviant/Derived/SSMA/ssna': 180,
        'Stimulus/Deviant/Underived/MA/la': 160,
        'Stimulus/Deviant/Underived/MA/ma': 140,
        'Stimulus/Deviant/Underived/MA/na': 150,
        'Stimulus/Deviant/Underived/ESMA/esla': 131,
        'Stimulus/Deviant/Underived/ESMA/esma': 111,
        'Stimulus/Deviant/Underived/ESMA/esna': 121,
        'Stimulus/Deviant/Derived/ESMA/esla': 191,
        'Stimulus/Deviant/Derived/ESMA/esma': 171,
        'Stimulus/Deviant/Derived/ESMA/esna': 181,
        'Stimulus/Deviant/Underived/EMA/ela': 161,
        'Stimulus/Deviant/Underived/EMA/ema': 141,
        'Stimulus/Deviant/Underived/EMA/ena': 151,
        }

To give a bit more context: it is an auditory MMN study. The participants hear a lot of “Standard” audio stimuli one after the other in quick succession, with some occasional “Deviant” stimuli interspresed in between. My goal is to identify the “Standards” that are followed by “Deviants”. As you can see in the dictionary, I only have events for the stimuli, so initially they did not have the “Stimulus/” part in their HED, I added that to be able to use “keep_first = “Stimulus””. Alternatively, I thought I could keep the first “Deviant” and “Standard” and compare the times. But the same issue will occur, the first “Standard” will alway be at 0, so it will always precede any eventual “Deviant”.

I guess one solution would be, in the metadata, to somehow mark the rows with “Standard” in the “event_name” column that are above a row that has “Deviant”.

Hello @r.lamarque, sorry, I meant to ask for the events and event_id (which you already shared). :slight_smile: You can save the events to disk via mne.write_events().

Thanks,
Richard

Hello,

Thanks again for your reply. I have used mne.write_events(), and I got that file: events.fif - Google Drive. I hope this worked. Just in case, I add a plot, that shows all the events and when they appear.

Thanks! What’s the sampling frequency of your data?

Edit: Also, the events you sent me – are those the ones before calling make_metadata() or after? I need the “before” events.

Hello,

The sampling frequency is 512Hz

I think it was after, I did it again before this time events.fif - Google Drive

Hello,

Just to let you and everyone who may be interested know, I have solved my problem with this:

# Add a "Conserve" column in the epochs metadata that allows to conserve only Deviant stimuli, and the one Standard stimuli that precede them.
# This is done by returning "Yes" if the event's name contains "Deviant", or if it contains "Standard" and the following event's name contains "Deviant"
# The "or "" " part ensures that the last event, which does not have a following event's name, does not create an error.
# "axis=1" ensures the function is applied row-wise, and not column-wise.
metadata['Conserve'] = metadata.apply(
    lambda row: (
        "Yes" if (
            ("Deviant" in row['event_name'])  # If the event's name contains Deviant
            or ("Standard" in row['event_name'] and "Deviant" in (metadata['event_name'].shift(-1)[row.name] or ""))
            # Or if the event's name contains "Standard" and is followed by one that contains "Deviant"
        ) else "No"
    ),
    axis=1
)

# Create the epochs
epochs = mne.Epochs(cleaned, events, event_id, tmin, tmax, baseline=baseline, metadata=metadata, preload=True)

# Drop the epochs that have "No" in the "Conserve" column of their metadata
epochs.drop([idx for idx, value in enumerate(epochs.metadata['Conserve']) if value == "No"])

Basically, just looking at whether the event’s name in the following row has “Deviant” in it.

Thank you for your help anyway!

1 Like