{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# The Evoked data structure: evoked/averaged data\n\nThis tutorial covers the basics of creating and working with :term:`evoked`\ndata. It introduces the :class:`~mne.Evoked` data structure in detail,\nincluding how to load, query, subset, export, and plot data from an\n:class:`~mne.Evoked` object. For details on creating an :class:`~mne.Evoked`\nobject from (possibly simulated) data in a :class:`NumPy array\n`, see `tut-creating-data-structures`.\n\nAs usual, we 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": [ "## Creating ``Evoked`` objects from ``Epochs``\n\n:class:`~mne.Evoked` objects typically store EEG or MEG signals that have\nbeen *averaged* over multiple :term:`epochs`, which is a common technique for\nestimating stimulus-evoked activity. The data in an :class:`~mne.Evoked`\nobject are stored in an :class:`array ` of shape\n``(n_channels, n_times)`` (in contrast to an :class:`~mne.Epochs` object,\nwhich stores data of shape ``(n_epochs, n_channels, n_times)``). Thus, to\ncreate an :class:`~mne.Evoked` object, we'll start by epoching some raw data,\nand then averaging together all the epochs from one condition:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "root = mne.datasets.sample.data_path() / \"MEG\" / \"sample\"\nraw_file = root / \"sample_audvis_raw.fif\"\nraw = mne.io.read_raw_fif(raw_file, verbose=False)\n\nevents = mne.find_events(raw, stim_channel=\"STI 014\")\n# we'll skip the \"face\" and \"buttonpress\" conditions to save memory\nevent_dict = {\n \"auditory/left\": 1,\n \"auditory/right\": 2,\n \"visual/left\": 3,\n \"visual/right\": 4,\n}\nepochs = mne.Epochs(raw, events, tmin=-0.3, tmax=0.7, event_id=event_dict, preload=True)\nevoked = epochs[\"auditory/left\"].average()\n\ndel raw # reduce memory usage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may have noticed that MNE informed us that \"baseline correction\" has been\napplied. This happened automatically during creation of the\n:class:`~mne.Epochs` object, but may also be initiated (or disabled)\nmanually. We will discuss this in more detail later.\n\nThe information about the baseline period of :class:`~mne.Epochs` is\ntransferred to derived :class:`~mne.Evoked` objects to maintain provenance as\nyou process your data:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(f\"Epochs baseline: {epochs.baseline}\")\nprint(f\"Evoked baseline: {evoked.baseline}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic visualization of ``Evoked`` objects\n\nWe can visualize the average evoked response for left-auditory stimuli using\nthe :meth:`~mne.Evoked.plot` method, which yields a butterfly plot of each\nchannel type:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evoked.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like the ``plot()`` methods for :meth:`Raw ` and\n:meth:`Epochs ` objects,\n:meth:`evoked.plot() ` has many parameters for customizing\nthe plot output, such as color-coding channel traces by scalp location, or\nplotting the :term:`global field power` alongside the channel traces.\nSee `tut-visualize-evoked` for more information on visualizing\n:class:`~mne.Evoked` objects.\n\n\n## Subsetting ``Evoked`` data\n\n.. admonition:: Evokeds are not memory-mapped\n :class: sidebar note\n\n :class:`~mne.Evoked` objects use a :attr:`~mne.Evoked.data` *attribute*\n rather than a :meth:`~mne.Epochs.get_data` *method*; this reflects the fact\n that the data in :class:`~mne.Evoked` objects are always loaded into\n memory and never `memory-mapped`_ from their location on disk (because they\n are typically *much* smaller than :class:`~mne.io.Raw` or\n :class:`~mne.Epochs` objects).\n\n\nUnlike :class:`~mne.io.Raw` and :class:`~mne.Epochs` objects,\n:class:`~mne.Evoked` objects do not support selection by square-bracket\nindexing. Instead, data can be subsetted by indexing the\n:attr:`~mne.Evoked.data` attribute:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(evoked.data[:2, :3]) # first 2 channels, first 3 timepoints" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To select based on time in seconds, the :meth:`~mne.Evoked.time_as_index`\nmethod can be useful, although beware that depending on the sampling\nfrequency, the number of samples in a span of given duration may not always\nbe the same (see the `time-as-index` section of the tutorial on\n`Raw data ` for details).\n\n\n### Selecting, dropping, and reordering channels\n\nBy default, when creating :class:`~mne.Evoked` data from an\n:class:`~mne.Epochs` object, only the primary data channels will be retained:\n``eog``, ``ecg``, ``stim``, and ``misc`` channel types will be dropped. You\ncan control which channel types are retained via the ``picks`` parameter of\n:meth:`epochs.average() `, by passing ``'all'`` to\nretain all channels, or by passing a list of integers, channel names, or\nchannel types. See the documentation of :meth:`~mne.Epochs.average` for\ndetails.\n\nIf you've already created the :class:`~mne.Evoked` object, you can use the\n:meth:`~mne.Evoked.pick`, :meth:`~mne.Evoked.pick_channels`,\n:meth:`~mne.Evoked.pick_types`, and :meth:`~mne.Evoked.drop_channels` methods\nto modify which channels are included in an :class:`~mne.Evoked` object.\nYou can also use :meth:`~mne.Evoked.reorder_channels` for this purpose; any\nchannel names not provided to :meth:`~mne.Evoked.reorder_channels` will be\ndropped. Note that *channel* selection methods modify the object in-place, so\nin interactive/exploratory sessions you may want to create a\n:meth:`~mne.Evoked.copy` first.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evoked_eeg = evoked.copy().pick(picks=\"eeg\")\nprint(evoked_eeg.ch_names)\n\nnew_order = [\"EEG 002\", \"MEG 2521\", \"EEG 003\"]\nevoked_subset = evoked.copy().reorder_channels(new_order)\nprint(evoked_subset.ch_names)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Similarities among the core data structures\n\n:class:`~mne.Evoked` objects have many similarities with :class:`~mne.io.Raw`\nand :class:`~mne.Epochs` objects, 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 ` (but through\n the :attr:`~mne.Evoked.data` attribute instead of a ``get_data()``\n method). :class:`Pandas DataFrame ` export is also\n available through the :meth:`~mne.Evoked.to_data_frame` method.\n\n- You can change the name or type of a channel using\n :meth:`evoked.rename_channels() ` or\n :meth:`evoked.set_channel_types() `.\n Both methods take :class:`dictionaries ` where the keys are existing\n channel names, and the values are the new name (or type) for that channel.\n Existing channels that are not in the dictionary will be unchanged.\n\n- :term:`SSP projector ` manipulation is possible through\n :meth:`~mne.Evoked.add_proj`, :meth:`~mne.Evoked.del_proj`, and\n :meth:`~mne.Evoked.plot_projs_topomap` methods, and the\n :attr:`~mne.Evoked.proj` attribute. See `tut-artifact-ssp` for more\n information on SSP.\n\n- Like :class:`~mne.io.Raw` and :class:`~mne.Epochs` objects,\n :class:`~mne.Evoked` objects have :meth:`~mne.Evoked.copy`,\n :meth:`~mne.Evoked.crop`, :meth:`~mne.Evoked.time_as_index`,\n :meth:`~mne.Evoked.filter`, and :meth:`~mne.Evoked.resample` methods.\n\n- Like :class:`~mne.io.Raw` and :class:`~mne.Epochs` objects,\n :class:`~mne.Evoked` objects have ``evoked.times``,\n :attr:`evoked.ch_names `, and :class:`info `\n attributes.\n\n\n\n## Loading and saving ``Evoked`` data\n\nSingle :class:`~mne.Evoked` objects can be saved to disk with the\n:meth:`evoked.save() ` method. One difference between\n:class:`~mne.Evoked` objects and the other data structures is that multiple\n:class:`~mne.Evoked` objects can be saved into a single ``.fif`` file, using\n:func:`mne.write_evokeds`. The `example data `\nincludes such a ``.fif`` file: the data have already been epoched and\naveraged, and the file contains separate :class:`~mne.Evoked` objects for\neach experimental condition:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evk_file = root / \"sample_audvis-ave.fif\"\nevokeds_list = mne.read_evokeds(evk_file, verbose=False)\nprint(evokeds_list)\nprint(type(evokeds_list))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that :func:`mne.read_evokeds` returned a :class:`list` of\n:class:`~mne.Evoked` objects, and each one has an ``evoked.comment``\nattribute describing the experimental condition that was averaged to\ngenerate the estimate:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for evok in evokeds_list:\n print(evok.comment)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to load only some of the conditions present in a ``.fif`` file,\n:func:`~mne.read_evokeds` has a ``condition`` parameter, which takes either a\nstring (matched against the comment attribute of the evoked objects on disk),\nor an integer selecting the :class:`~mne.Evoked` object based on the order\nit is stored in the file. Passing lists of integers or strings is also\npossible. If only one object is selected, the :class:`~mne.Evoked` object\nwill be returned directly (rather than inside a list of length one):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "right_vis = mne.read_evokeds(evk_file, condition=\"Right visual\")\nprint(right_vis)\nprint(type(right_vis))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Previously, when we created an :class:`~mne.Evoked` object by averaging\nepochs, baseline correction was applied by default when we extracted epochs\nfrom the `~mne.io.Raw` object (the default baseline period is ``(None, 0)``,\nwhich ensures zero mean for times before the stimulus event). In contrast, if\nwe plot the first :class:`~mne.Evoked` object in the list that was loaded\nfrom disk, we'll see that the data have not been baseline-corrected:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evokeds_list[0].plot(picks=\"eeg\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This can be remedied by either passing a ``baseline`` parameter to\n:func:`mne.read_evokeds`, or by applying baseline correction after loading,\nas shown here:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Original baseline (none set)\nprint(f\"Baseline after loading: {evokeds_list[0].baseline}\")\n\n# Apply a custom baseline correction\nevokeds_list[0].apply_baseline((None, 0))\nprint(f\"Baseline after calling apply_baseline(): {evokeds_list[0].baseline}\")\n\n# Visualize the evoked response\nevokeds_list[0].plot(picks=\"eeg\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that :meth:`~mne.Evoked.apply_baseline` operated in-place. Similarly,\n:class:`~mne.Evoked` objects may have been saved to disk with or without\n:term:`projectors ` applied; you can pass ``proj=True`` to the\n:func:`~mne.read_evokeds` function, or use the :meth:`~mne.Evoked.apply_proj`\nmethod after loading.\n\n\n## Combining ``Evoked`` objects\n\nOne way to pool data across multiple conditions when estimating evoked\nresponses is to do so *prior to averaging* (recall that MNE-Python can select\nbased on partial matching of epoch labels separated by ``/``; see\n`tut-section-subselect-epochs` for more information):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "left_right_aud = epochs[\"auditory\"].average()\nleft_right_aud" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This approach will weight each epoch equally and create a single\n:class:`~mne.Evoked` object. Notice that the printed representation includes\n``(average, N=145)``, indicating that the :class:`~mne.Evoked` object was\ncreated by averaging across 145 epochs. In this case, the event types were\nfairly close in number:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "left_aud = epochs[\"auditory/left\"].average()\nright_aud = epochs[\"auditory/right\"].average()\nprint([evok.nave for evok in (left_aud, right_aud)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, this may not always be the case. If for statistical reasons it is\nimportant to average *the same number* of epochs from different conditions,\nyou can use :meth:`~mne.Epochs.equalize_event_counts` prior to averaging.\n\nAnother approach to pooling across conditions is to create separate\n:class:`~mne.Evoked` objects for each condition, and combine them afterwards.\nThis can be accomplished with the function :func:`mne.combine_evoked`, which\ncomputes a weighted sum of the :class:`~mne.Evoked` objects given to it. The\nweights can be manually specified as a list or array of float values, or can\nbe specified using the keyword ``'equal'`` (weight each :class:`~mne.Evoked`\nobject by $\\frac{1}{N}$, where $N$ is the number of\n:class:`~mne.Evoked` objects given) or the keyword ``'nave'`` (weight each\n:class:`~mne.Evoked` object proportional to the number of epochs averaged\ntogether to create it):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "left_right_aud = mne.combine_evoked([left_aud, right_aud], weights=\"nave\")\nassert left_right_aud.nave == left_aud.nave + right_aud.nave" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the ``nave`` attribute of the resulting :class:`~mne.Evoked` object\nwill reflect the *effective* number of averages, and depends on both the\n``nave`` attributes of the contributing :class:`~mne.Evoked` objects and the\nweights with which they are combined. Keeping track of effective ``nave`` is\nimportant for inverse imaging, because ``nave`` is used to scale the noise\ncovariance estimate, which in turn affects the magnitude of estimated source\nactivity (see `minimum_norm_estimates` for more information, especially\nthe `whitening_and_scaling` section). Note that\n:func:`mne.grand_average` does *not* adjust ``nave`` to reflect the effective\nnumber of averaged epochs; it simply sets ``nave`` to the number of *evokeds*\nthat were averaged together. For this reason, it is best to use\n:func:`mne.combine_evoked` rather than :func:`mne.grand_average` if you\nintend to perform inverse imaging on the resulting :class:`~mne.Evoked`\nobject.\n\n\n## Other uses of ``Evoked`` objects\n\nAlthough the most common use of :class:`~mne.Evoked` objects is to store\n*averages* of epoched data, there are a few other uses worth noting here.\nFirst, the method :meth:`epochs.standard_error() `\nwill create an :class:`~mne.Evoked` object (just like\n:meth:`epochs.average() ` does), but the data in the\n:class:`~mne.Evoked` object will be the standard error across epochs instead\nof the average. To indicate this difference, :class:`~mne.Evoked` objects\nhave a :attr:`~mne.Evoked.kind` attribute that takes values ``'average'`` or\n``'standard error'`` as appropriate.\n\nAnother use of :class:`~mne.Evoked` objects is to represent *a single trial\nor epoch* of data, usually when looping through epochs. This can be easily\naccomplished with the :meth:`epochs.iter_evoked() `\nmethod, and can be useful for applications where you want to do something\nthat is only possible for :class:`~mne.Evoked` objects. For example, here\nwe use the :meth:`~mne.Evoked.get_peak` method (which is not available for\n:class:`~mne.Epochs` objects) to get the peak response in each trial:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for ix, trial in enumerate(epochs[:3].iter_evoked()):\n channel, latency, value = trial.get_peak(ch_type=\"eeg\", return_amplitude=True)\n latency = int(round(latency * 1e3)) # convert to milliseconds\n value = int(round(value * 1e6)) # convert to \u00b5V\n print(f\"Trial {ix}: peak of {value} \u00b5V at {latency} ms in channel {channel}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. REFERENCES\n\n\n" ] } ], "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 }