{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# The Epochs data structure: discontinuous data\n\nThis tutorial covers the basics of creating and working with :term:`epoched\n` data. It introduces the :class:`~mne.Epochs` data structure in\ndetail, including how to load, query, subselect, export, and plot data from an\n:class:`~mne.Epochs` object. For more information about visualizing\n:class:`~mne.Epochs` objects, see `tut-visualize-epochs`. For info on\ncreating an :class:`~mne.Epochs` object from (possibly simulated) data in a\n:class:`NumPy array `, see `tut-creating-data-structures`.\n\nAs usual we'll start by importing the modules we need:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: The MNE-Python contributors.\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import mne" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ":class:`~mne.Epochs` objects are a data structure for representing and\nanalyzing equal-duration chunks of the EEG/MEG signal. :class:`~mne.Epochs`\nare most often used to represent data that is time-locked to repeated\nexperimental events (such as stimulus onsets or subject button presses), but\ncan also be used for storing sequential or overlapping frames of a continuous\nsignal (e.g., for analysis of resting-state activity; see\n`fixed-length-events`). Inside an :class:`~mne.Epochs` object, the data\nare stored in an :class:`array ` of shape ``(n_epochs,\nn_channels, n_times)``.\n\n:class:`~mne.Epochs` objects have many similarities with :class:`~mne.io.Raw`\nobjects, including:\n\n- They can be loaded from and saved to disk in ``.fif`` format, and their\n data can be exported to a :class:`NumPy array ` through the\n :meth:`~mne.Epochs.get_data` method or to a :class:`Pandas DataFrame\n ` through the :meth:`~mne.Epochs.to_data_frame` method.\n\n- Both :class:`~mne.Epochs` and :class:`~mne.io.Raw` objects support channel\n selection by index or name, including :meth:`~mne.Epochs.pick`,\n :meth:`~mne.Epochs.pick_channels` and :meth:`~mne.Epochs.pick_types`\n methods.\n\n- :term:`SSP projector ` manipulation is possible through\n :meth:`~mne.Epochs.add_proj`, :meth:`~mne.Epochs.del_proj`, and\n :meth:`~mne.Epochs.plot_projs_topomap` methods.\n\n- Both :class:`~mne.Epochs` and :class:`~mne.io.Raw` objects have\n :meth:`~mne.Epochs.copy`, :meth:`~mne.Epochs.crop`,\n :meth:`~mne.Epochs.time_as_index`, :meth:`~mne.Epochs.filter`,\n :meth:`~mne.Epochs.resample`, and :meth:`~mne.Epochs.compute_psd` methods.\n\n- Both :class:`~mne.Epochs` and :class:`~mne.io.Raw` objects have\n :attr:`~mne.Epochs.times`, :attr:`~mne.Epochs.ch_names`,\n :attr:`~mne.Epochs.proj`, and :class:`info ` attributes.\n\n- Both :class:`~mne.Epochs` and :class:`~mne.io.Raw` objects have built-in\n plotting methods :meth:`~mne.Epochs.plot`, and legacy plotting methods\n :meth:`~mne.Epochs.plot_psd` and :meth:`~mne.Epochs.plot_psd_topomap`\n (in new code, PSD plotting is done via the\n :class:`~mne.time_frequency.Spectrum` class).\n\n\n## Creating Epoched data from a ``Raw`` object\n\nThe example dataset we've been using thus far doesn't include pre-epoched\ndata, so in this section we'll load the continuous data and create epochs\nbased on the events recorded in the :class:`~mne.io.Raw` object's STIM\nchannels. As we often do in these tutorials, we'll :meth:`~mne.io.Raw.crop`\nthe :class:`~mne.io.Raw` data to save memory:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "sample_data_folder = mne.datasets.sample.data_path()\nsample_data_raw_file = sample_data_folder / \"MEG\" / \"sample\" / \"sample_audvis_raw.fif\"\nraw = mne.io.read_raw_fif(sample_data_raw_file, verbose=False).crop(tmax=60)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we saw in the `tut-events-vs-annotations` tutorial, we can extract an\nevents array from :class:`~mne.io.Raw` objects using :func:`mne.find_events`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "events = mne.find_events(raw, stim_channel=\"STI 014\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Note

We could also have loaded the events from file, using\n :func:`mne.read_events`::\n\n sample_data_events_file = os.path.join(sample_data_folder,\n 'MEG', 'sample',\n 'sample_audvis_raw-eve.fif')\n events_from_file = mne.read_events(sample_data_events_file)\n\n See `tut-section-events-io` for more details.

\n\n\nThe :class:`~mne.io.Raw` object and the events array are the bare minimum\nneeded to create an :class:`~mne.Epochs` object, which we create with the\n:class:`mne.Epochs` class constructor. However, you will almost surely want\nto change some of the other default parameters. Here we'll change ``tmin``\nand ``tmax`` (the time relative to each event at which to start and end each\nepoch). Note also that the :class:`~mne.Epochs` constructor accepts\nparameters ``reject`` and ``flat`` for rejecting individual epochs based on\nsignal amplitude. See the `tut-reject-epochs-section` section for\nexamples.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "epochs = mne.Epochs(raw, events, tmin=-0.3, tmax=0.7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You'll see from the output that:\n\n- all 320 events were used to create epochs\n\n- baseline correction was automatically applied (by default, baseline is\n defined as the time span from ``tmin`` to ``0``, but can be customized with\n the ``baseline`` parameter)\n\n- no additional metadata was provided (see `tut-epochs-metadata` for\n details)\n\n- the projection operators present in the :class:`~mne.io.Raw` file were\n copied over to the :class:`~mne.Epochs` object\n\nIf we print the :class:`~mne.Epochs` object, we'll also see a note that the\nepochs are not copied into memory by default, and a count of the number of\nepochs created for each integer Event ID.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(epochs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the Event IDs are in quotes; since we didn't provide an event\ndictionary, the :class:`mne.Epochs` constructor created one automatically and\nused the string representation of the integer Event IDs as the dictionary\nkeys. This is more clear when viewing the ``event_id`` attribute:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(epochs.event_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This time let's pass ``preload=True`` and provide an event dictionary; our\nprovided dictionary will get stored as the ``event_id`` attribute and will\nmake referencing events and pooling across event types easier:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "event_dict = {\n \"auditory/left\": 1,\n \"auditory/right\": 2,\n \"visual/left\": 3,\n \"visual/right\": 4,\n \"face\": 5,\n \"buttonpress\": 32,\n}\nepochs = mne.Epochs(raw, events, tmin=-0.3, tmax=0.7, event_id=event_dict, preload=True)\nprint(epochs.event_id)\ndel raw # we're done with raw, free up some memory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the output now mentions \"1 bad epoch dropped\". In the tutorial\nsection `tut-reject-epochs-section` we saw how you can specify channel\namplitude criteria for rejecting epochs, but here we haven't specified any\nsuch criteria. In this case, it turns out that the last event was too close\nthe end of the (cropped) raw file to accommodate our requested ``tmax`` of\n0.7 seconds, so the final epoch was dropped because it was too short. Here\nare the ``drop_log`` entries for the last 4 epochs (empty lists indicate\nepochs that were *not* dropped):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(epochs.drop_log[-4:])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Note

If you forget to provide the event dictionary to the :class:`~mne.Epochs`\n constructor, you can add it later by assigning to the ``event_id``\n attribute::\n\n epochs.event_id = event_dict

\n\n\n### Basic visualization of ``Epochs`` objects\n\nThe :class:`~mne.Epochs` object can be visualized (and browsed interactively)\nusing its :meth:`~mne.Epochs.plot` method:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "epochs.plot(n_epochs=10, events=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the individual epochs are sequentially numbered along the bottom\naxis and are separated by vertical dashed lines.\nEpoch plots are interactive (similar to :meth:`raw.plot()\n`) and have many of the same interactive controls as\n:class:`~mne.io.Raw` plots. Horizontal and vertical scrollbars allow browsing\nthrough epochs or channels (respectively), and pressing :kbd:`?` when the\nplot is focused will show a help screen with all the available controls. See\n`tut-visualize-epochs` for more details (as well as other ways of\nvisualizing epoched data).\n\n\n\n## Subselecting epochs\n\nNow that we have our :class:`~mne.Epochs` object with our descriptive event\nlabels added, we can subselect epochs easily using square brackets. For\nexample, we can load all the \"catch trials\" where the stimulus was a face:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(epochs[\"face\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also pool across conditions easily, thanks to how MNE-Python handles\nthe ``/`` character in epoch labels (using what is sometimes called\n\"tag-based indexing\"):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# pool across left + right\nprint(epochs[\"auditory\"])\nassert len(epochs[\"auditory\"]) == (\n len(epochs[\"auditory/left\"]) + len(epochs[\"auditory/right\"])\n)\n# pool across auditory + visual\nprint(epochs[\"left\"])\nassert len(epochs[\"left\"]) == (\n len(epochs[\"auditory/left\"]) + len(epochs[\"visual/left\"])\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also pool conditions by passing multiple tags as a list. Note that\nMNE-Python will not complain if you ask for tags not present in the object,\nas long as it can find *some* match: the below example is parsed as\n(inclusive) ``'right'`` **or** ``'bottom'``, and you can see from the output\nthat it selects only ``auditory/right`` and ``visual/right``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(epochs[[\"right\", \"bottom\"]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, if no match is found, an error is returned:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "try:\n print(epochs[[\"top\", \"bottom\"]])\nexcept KeyError:\n print(\"Tag-based selection with no matches raises a KeyError!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Selecting epochs by index\n\n:class:`~mne.Epochs` objects can also be indexed with integers, :term:`slices\n`, or lists of integers. This method of selection ignores event\nlabels, so if you want the first 10 epochs of a particular type, you can\nselect the type first, then use integers or slices:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(epochs[:10]) # epochs 0-9\nprint(epochs[1:8:2]) # epochs 1, 3, 5, 7\n\nprint(epochs[\"buttonpress\"][:4]) # first 4 \"buttonpress\" epochs\nprint(epochs[\"buttonpress\"][[0, 1, 2, 3]]) # same as previous line" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Selecting, dropping, and reordering channels\n\nYou can use the :meth:`~mne.Epochs.pick`, :meth:`~mne.Epochs.pick_channels`,\n:meth:`~mne.Epochs.pick_types`, and :meth:`~mne.Epochs.drop_channels` methods\nto modify which channels are included in an :class:`~mne.Epochs` object. You\ncan also use :meth:`~mne.Epochs.reorder_channels` for this purpose; any\nchannel names not provided to :meth:`~mne.Epochs.reorder_channels` will be\ndropped. Note that these *channel* selection methods modify the object\nin-place (unlike the square-bracket indexing to select *epochs* seen above)\nso in interactive/exploratory sessions you may want to create a\n:meth:`~mne.Epochs.copy` first.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "epochs_eeg = epochs.copy().pick(picks=\"eeg\")\nprint(epochs_eeg.ch_names)\n\nnew_order = [\"EEG 002\", \"STI 014\", \"EOG 061\", \"MEG 2521\"]\nepochs_subset = epochs.copy().reorder_channels(new_order)\nprint(epochs_subset.ch_names)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "del epochs_eeg, epochs_subset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Changing channel name and type\n\nYou can change the name or type of a channel using\n:meth:`~mne.Epochs.rename_channels` or :meth:`~mne.Epochs.set_channel_types`.\nBoth methods take :class:`dictionaries ` where the keys are existing\nchannel names, and the values are the new name (or type) for that channel.\nExisting channels that are not in the dictionary will be unchanged.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "epochs.rename_channels({\"EOG 061\": \"BlinkChannel\"})\n\nepochs.set_channel_types({\"EEG 060\": \"ecg\"})\nprint(list(zip(epochs.ch_names, epochs.get_channel_types()))[-4:])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# let's set them back to the correct values before moving on\nepochs.rename_channels({\"BlinkChannel\": \"EOG 061\"})\nepochs.set_channel_types({\"EEG 060\": \"eeg\"})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Selection in the time domain\n\nTo change the temporal extent of the :class:`~mne.Epochs`, you can use the\n:meth:`~mne.Epochs.crop` method:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "shorter_epochs = epochs.copy().crop(tmin=-0.1, tmax=0.1, include_tmax=True)\n\nfor name, obj in dict(Original=epochs, Cropped=shorter_epochs).items():\n print(f\"{name} epochs has {obj.get_data(copy=False).shape[-1]} time samples\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cropping removed part of the baseline. When printing the\ncropped :class:`~mne.Epochs`, MNE-Python will inform you about the time\nperiod that was originally used to perform baseline correction by displaying\nthe string \"baseline period cropped after baseline correction\":\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(shorter_epochs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, if you wanted to *expand* the time domain of an :class:`~mne.Epochs`\nobject, you would need to go back to the :class:`~mne.io.Raw` data and\nrecreate the :class:`~mne.Epochs` with different values for ``tmin`` and/or\n``tmax``.\n\nIt is also possible to change the \"zero point\" that defines the time values\nin an :class:`~mne.Epochs` object, with the :meth:`~mne.Epochs.shift_time`\nmethod. :meth:`~mne.Epochs.shift_time` allows shifting times relative to the\ncurrent values, or specifying a fixed time to set as the new time value of\nthe first sample (deriving the new time values of subsequent samples based on\nthe :class:`~mne.Epochs` object's sampling frequency).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# shift times so that first sample of each epoch is at time zero\nlater_epochs = epochs.copy().shift_time(tshift=0.0, relative=False)\nprint(later_epochs.times[:3])\n\n# shift times by a relative amount\nlater_epochs.shift_time(tshift=-7, relative=True)\nprint(later_epochs.times[:3])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "del shorter_epochs, later_epochs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that although time shifting respects the sampling frequency (the spacing\nbetween samples), it does not enforce the assumption that there is a sample\noccurring at exactly time=0.\n\n\n### Extracting data in other forms\n\nThe :meth:`~mne.Epochs.get_data` method returns the epoched data as a\n:class:`NumPy array `, of shape ``(n_epochs, n_channels,\nn_times)``; an optional ``picks`` parameter selects a subset of channels by\nindex, name, or type:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "eog_data = epochs.get_data(picks=\"EOG 061\")\nmeg_data = epochs.get_data(picks=[\"mag\", \"grad\"])\nchannel_4_6_8 = epochs.get_data(picks=slice(4, 9, 2))\n\nfor name, arr in dict(EOG=eog_data, MEG=meg_data, Slice=channel_4_6_8).items():\n print(f\"{name} contains {arr.shape[1]} channels\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that if your analysis requires repeatedly extracting single epochs from\nan :class:`~mne.Epochs` object, ``epochs.get_data(item=2)`` will be much\nfaster than ``epochs[2].get_data()``, because it avoids the step of\nsubsetting the :class:`~mne.Epochs` object first.\n\nYou can also export :class:`~mne.Epochs` data to :class:`Pandas DataFrames\n`. Here, the :class:`~pandas.DataFrame` index will be\nconstructed by converting the time of each sample into milliseconds and\nrounding it to the nearest integer, and combining it with the event types and\nepoch numbers to form a hierarchical :class:`~pandas.MultiIndex`. Each\nchannel will appear in a separate column. Then you can use any of Pandas'\ntools for grouping and aggregating data; for example, here we select any\nepochs numbered 10 or less from the ``auditory/left`` condition, and extract\ntimes between 100 and 107 ms on channels ``EEG 056`` through ``EEG 058``\n(note that slice indexing within Pandas' :obj:`~pandas.DataFrame.loc` is\ninclusive of the endpoint):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "df = epochs.to_data_frame(index=[\"condition\", \"epoch\", \"time\"])\ndf.sort_index(inplace=True)\nprint(df.loc[(\"auditory/left\", slice(0, 10), slice(100, 107)), \"EEG 056\":\"EEG 058\"])\n\ndel df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "See the `tut-epochs-dataframe` tutorial for many more examples of the\n:meth:`~mne.Epochs.to_data_frame` method.\n\n\n## Loading and saving ``Epochs`` objects to disk\n\n:class:`~mne.Epochs` objects can be loaded and saved in the ``.fif`` format\njust like :class:`~mne.io.Raw` objects, using the :func:`mne.read_epochs`\nfunction and the :meth:`~mne.Epochs.save` method. Functions are also\navailable for loading data that was epoched outside of MNE-Python, such as\n:func:`mne.read_epochs_eeglab` and :func:`mne.read_epochs_kit`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "epochs.save(\"saved-audiovisual-epo.fif\", overwrite=True)\nepochs_from_file = mne.read_epochs(\"saved-audiovisual-epo.fif\", preload=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The MNE-Python naming convention for epochs files is that the file basename\n(the part before the ``.fif`` or ``.fif.gz`` extension) should end with\n``-epo`` or ``_epo``, and a warning will be issued if the filename you\nprovide does not adhere to that convention.\n\nAs a final note, be aware that the class of the epochs object is different\nwhen epochs are loaded from disk rather than generated from a\n:class:`~mne.io.Raw` object:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(type(epochs))\nprint(type(epochs_from_file))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In almost all cases this will not require changing anything about your code.\nHowever, if you need to do type checking on epochs objects, you can test\nagainst the base class that these classes are derived from:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(\n all(\n [\n isinstance(epochs, mne.BaseEpochs),\n isinstance(epochs_from_file, mne.BaseEpochs),\n ]\n )\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Iterating over ``Epochs``\n\nIterating over an :class:`~mne.Epochs` object will yield :class:`arrays\n` rather than single-trial :class:`~mne.Epochs` objects:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for epoch in epochs[:3]:\n print(type(epoch))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to iterate over :class:`~mne.Epochs` objects, you can use an\ninteger index as the iterator:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for index in range(3):\n print(type(epochs[index]))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 0 }