""" .. _tut-evoked-class: =============================================== The Evoked data structure: evoked/averaged data =============================================== This tutorial covers the basics of creating and working with :term:`evoked` data. It introduces the :class:`~mne.Evoked` data structure in detail, including how to load, query, subset, export, and plot data from an :class:`~mne.Evoked` object. For details on creating an :class:`~mne.Evoked` object from (possibly simulated) data in a :class:`NumPy array `, see :ref:`tut-creating-data-structures`. As usual, we start by importing the modules we need: """ # Authors: The MNE-Python contributors. # License: BSD-3-Clause # Copyright the MNE-Python contributors. # %% import mne # %% # Creating ``Evoked`` objects from ``Epochs`` # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # :class:`~mne.Evoked` objects typically store EEG or MEG signals that have # been *averaged* over multiple :term:`epochs`, which is a common technique for # estimating stimulus-evoked activity. The data in an :class:`~mne.Evoked` # object are stored in an :class:`array ` of shape # ``(n_channels, n_times)`` (in contrast to an :class:`~mne.Epochs` object, # which stores data of shape ``(n_epochs, n_channels, n_times)``). Thus, to # create an :class:`~mne.Evoked` object, we'll start by epoching some raw data, # and then averaging together all the epochs from one condition: root = mne.datasets.sample.data_path() / "MEG" / "sample" raw_file = root / "sample_audvis_raw.fif" raw = mne.io.read_raw_fif(raw_file, verbose=False) events = mne.find_events(raw, stim_channel="STI 014") # we'll skip the "face" and "buttonpress" conditions to save memory event_dict = { "auditory/left": 1, "auditory/right": 2, "visual/left": 3, "visual/right": 4, } epochs = mne.Epochs(raw, events, tmin=-0.3, tmax=0.7, event_id=event_dict, preload=True) evoked = epochs["auditory/left"].average() del raw # reduce memory usage # %% # You may have noticed that MNE informed us that "baseline correction" has been # applied. This happened automatically during creation of the # :class:`~mne.Epochs` object, but may also be initiated (or disabled) # manually. We will discuss this in more detail later. # # The information about the baseline period of :class:`~mne.Epochs` is # transferred to derived :class:`~mne.Evoked` objects to maintain provenance as # you process your data: print(f"Epochs baseline: {epochs.baseline}") print(f"Evoked baseline: {evoked.baseline}") # %% # Basic visualization of ``Evoked`` objects # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # We can visualize the average evoked response for left-auditory stimuli using # the :meth:`~mne.Evoked.plot` method, which yields a butterfly plot of each # channel type: evoked.plot() # %% # Like the ``plot()`` methods for :meth:`Raw ` and # :meth:`Epochs ` objects, # :meth:`evoked.plot() ` has many parameters for customizing # the plot output, such as color-coding channel traces by scalp location, or # plotting the :term:`global field power` alongside the channel traces. # See :ref:`tut-visualize-evoked` for more information on visualizing # :class:`~mne.Evoked` objects. # # # Subsetting ``Evoked`` data # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # .. admonition:: Evokeds are not memory-mapped # :class: sidebar note # # :class:`~mne.Evoked` objects use a :attr:`~mne.Evoked.data` *attribute* # rather than a :meth:`~mne.Epochs.get_data` *method*; this reflects the fact # that the data in :class:`~mne.Evoked` objects are always loaded into # memory and never `memory-mapped`_ from their location on disk (because they # are typically *much* smaller than :class:`~mne.io.Raw` or # :class:`~mne.Epochs` objects). # # # Unlike :class:`~mne.io.Raw` and :class:`~mne.Epochs` objects, # :class:`~mne.Evoked` objects do not support selection by square-bracket # indexing. Instead, data can be subsetted by indexing the # :attr:`~mne.Evoked.data` attribute: print(evoked.data[:2, :3]) # first 2 channels, first 3 timepoints # %% # To select based on time in seconds, the :meth:`~mne.Evoked.time_as_index` # method can be useful, although beware that depending on the sampling # frequency, the number of samples in a span of given duration may not always # be the same (see the :ref:`time-as-index` section of the tutorial on # :ref:`Raw data ` for details). # # # Selecting, dropping, and reordering channels # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # By default, when creating :class:`~mne.Evoked` data from an # :class:`~mne.Epochs` object, only the primary data channels will be retained: # ``eog``, ``ecg``, ``stim``, and ``misc`` channel types will be dropped. You # can control which channel types are retained via the ``picks`` parameter of # :meth:`epochs.average() `, by passing ``'all'`` to # retain all channels, or by passing a list of integers, channel names, or # channel types. See the documentation of :meth:`~mne.Epochs.average` for # details. # # If you've already created the :class:`~mne.Evoked` object, you can use the # :meth:`~mne.Evoked.pick`, :meth:`~mne.Evoked.pick_channels`, # :meth:`~mne.Evoked.pick_types`, and :meth:`~mne.Evoked.drop_channels` methods # to modify which channels are included in an :class:`~mne.Evoked` object. # You can also use :meth:`~mne.Evoked.reorder_channels` for this purpose; any # channel names not provided to :meth:`~mne.Evoked.reorder_channels` will be # dropped. Note that *channel* selection methods modify the object in-place, so # in interactive/exploratory sessions you may want to create a # :meth:`~mne.Evoked.copy` first. evoked_eeg = evoked.copy().pick(picks="eeg") print(evoked_eeg.ch_names) new_order = ["EEG 002", "MEG 2521", "EEG 003"] evoked_subset = evoked.copy().reorder_channels(new_order) print(evoked_subset.ch_names) # %% # Similarities among the core data structures # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # :class:`~mne.Evoked` objects have many similarities with :class:`~mne.io.Raw` # and :class:`~mne.Epochs` objects, including: # # - They can be loaded from and saved to disk in ``.fif`` format, and their # data can be exported to a :class:`NumPy array ` (but through # the :attr:`~mne.Evoked.data` attribute instead of a ``get_data()`` # method). :class:`Pandas DataFrame ` export is also # available through the :meth:`~mne.Evoked.to_data_frame` method. # # - You can change the name or type of a channel using # :meth:`evoked.rename_channels() ` or # :meth:`evoked.set_channel_types() `. # Both methods take :class:`dictionaries ` where the keys are existing # channel names, and the values are the new name (or type) for that channel. # Existing channels that are not in the dictionary will be unchanged. # # - :term:`SSP projector ` manipulation is possible through # :meth:`~mne.Evoked.add_proj`, :meth:`~mne.Evoked.del_proj`, and # :meth:`~mne.Evoked.plot_projs_topomap` methods, and the # :attr:`~mne.Evoked.proj` attribute. See :ref:`tut-artifact-ssp` for more # information on SSP. # # - Like :class:`~mne.io.Raw` and :class:`~mne.Epochs` objects, # :class:`~mne.Evoked` objects have :meth:`~mne.Evoked.copy`, # :meth:`~mne.Evoked.crop`, :meth:`~mne.Evoked.time_as_index`, # :meth:`~mne.Evoked.filter`, and :meth:`~mne.Evoked.resample` methods. # # - Like :class:`~mne.io.Raw` and :class:`~mne.Epochs` objects, # :class:`~mne.Evoked` objects have ``evoked.times``, # :attr:`evoked.ch_names `, and :class:`info ` # attributes. # # # .. _tut-section-load-evk: # # Loading and saving ``Evoked`` data # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # Single :class:`~mne.Evoked` objects can be saved to disk with the # :meth:`evoked.save() ` method. One difference between # :class:`~mne.Evoked` objects and the other data structures is that multiple # :class:`~mne.Evoked` objects can be saved into a single ``.fif`` file, using # :func:`mne.write_evokeds`. The :ref:`example data ` # includes such a ``.fif`` file: the data have already been epoched and # averaged, and the file contains separate :class:`~mne.Evoked` objects for # each experimental condition: evk_file = root / "sample_audvis-ave.fif" evokeds_list = mne.read_evokeds(evk_file, verbose=False) print(evokeds_list) print(type(evokeds_list)) # %% # Notice that :func:`mne.read_evokeds` returned a :class:`list` of # :class:`~mne.Evoked` objects, and each one has an ``evoked.comment`` # attribute describing the experimental condition that was averaged to # generate the estimate: for evok in evokeds_list: print(evok.comment) # %% # If you want to load only some of the conditions present in a ``.fif`` file, # :func:`~mne.read_evokeds` has a ``condition`` parameter, which takes either a # string (matched against the comment attribute of the evoked objects on disk), # or an integer selecting the :class:`~mne.Evoked` object based on the order # it is stored in the file. Passing lists of integers or strings is also # possible. If only one object is selected, the :class:`~mne.Evoked` object # will be returned directly (rather than inside a list of length one): right_vis = mne.read_evokeds(evk_file, condition="Right visual") print(right_vis) print(type(right_vis)) # %% # Previously, when we created an :class:`~mne.Evoked` object by averaging # epochs, baseline correction was applied by default when we extracted epochs # from the `~mne.io.Raw` object (the default baseline period is ``(None, 0)``, # which ensures zero mean for times before the stimulus event). In contrast, if # we plot the first :class:`~mne.Evoked` object in the list that was loaded # from disk, we'll see that the data have not been baseline-corrected: evokeds_list[0].plot(picks="eeg") # %% # This can be remedied by either passing a ``baseline`` parameter to # :func:`mne.read_evokeds`, or by applying baseline correction after loading, # as shown here: # Original baseline (none set) print(f"Baseline after loading: {evokeds_list[0].baseline}") # Apply a custom baseline correction evokeds_list[0].apply_baseline((None, 0)) print(f"Baseline after calling apply_baseline(): {evokeds_list[0].baseline}") # Visualize the evoked response evokeds_list[0].plot(picks="eeg") # %% # Notice that :meth:`~mne.Evoked.apply_baseline` operated in-place. Similarly, # :class:`~mne.Evoked` objects may have been saved to disk with or without # :term:`projectors ` applied; you can pass ``proj=True`` to the # :func:`~mne.read_evokeds` function, or use the :meth:`~mne.Evoked.apply_proj` # method after loading. # # # Combining ``Evoked`` objects # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # One way to pool data across multiple conditions when estimating evoked # responses is to do so *prior to averaging* (recall that MNE-Python can select # based on partial matching of epoch labels separated by ``/``; see # :ref:`tut-section-subselect-epochs` for more information): left_right_aud = epochs["auditory"].average() left_right_aud # %% # This approach will weight each epoch equally and create a single # :class:`~mne.Evoked` object. Notice that the printed representation includes # ``(average, N=145)``, indicating that the :class:`~mne.Evoked` object was # created by averaging across 145 epochs. In this case, the event types were # fairly close in number: left_aud = epochs["auditory/left"].average() right_aud = epochs["auditory/right"].average() print([evok.nave for evok in (left_aud, right_aud)]) # %% # However, this may not always be the case. If for statistical reasons it is # important to average *the same number* of epochs from different conditions, # you can use :meth:`~mne.Epochs.equalize_event_counts` prior to averaging. # # Another approach to pooling across conditions is to create separate # :class:`~mne.Evoked` objects for each condition, and combine them afterwards. # This can be accomplished with the function :func:`mne.combine_evoked`, which # computes a weighted sum of the :class:`~mne.Evoked` objects given to it. The # weights can be manually specified as a list or array of float values, or can # be specified using the keyword ``'equal'`` (weight each :class:`~mne.Evoked` # object by :math:`\frac{1}{N}`, where :math:`N` is the number of # :class:`~mne.Evoked` objects given) or the keyword ``'nave'`` (weight each # :class:`~mne.Evoked` object proportional to the number of epochs averaged # together to create it): left_right_aud = mne.combine_evoked([left_aud, right_aud], weights="nave") assert left_right_aud.nave == left_aud.nave + right_aud.nave # %% # Note that the ``nave`` attribute of the resulting :class:`~mne.Evoked` object # will reflect the *effective* number of averages, and depends on both the # ``nave`` attributes of the contributing :class:`~mne.Evoked` objects and the # weights with which they are combined. Keeping track of effective ``nave`` is # important for inverse imaging, because ``nave`` is used to scale the noise # covariance estimate, which in turn affects the magnitude of estimated source # activity (see :ref:`minimum_norm_estimates` for more information, especially # the :ref:`whitening_and_scaling` section). Note that # :func:`mne.grand_average` does *not* adjust ``nave`` to reflect the effective # number of averaged epochs; it simply sets ``nave`` to the number of *evokeds* # that were averaged together. For this reason, it is best to use # :func:`mne.combine_evoked` rather than :func:`mne.grand_average` if you # intend to perform inverse imaging on the resulting :class:`~mne.Evoked` # object. # # # Other uses of ``Evoked`` objects # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # Although the most common use of :class:`~mne.Evoked` objects is to store # *averages* of epoched data, there are a few other uses worth noting here. # First, the method :meth:`epochs.standard_error() ` # will create an :class:`~mne.Evoked` object (just like # :meth:`epochs.average() ` does), but the data in the # :class:`~mne.Evoked` object will be the standard error across epochs instead # of the average. To indicate this difference, :class:`~mne.Evoked` objects # have a :attr:`~mne.Evoked.kind` attribute that takes values ``'average'`` or # ``'standard error'`` as appropriate. # # Another use of :class:`~mne.Evoked` objects is to represent *a single trial # or epoch* of data, usually when looping through epochs. This can be easily # accomplished with the :meth:`epochs.iter_evoked() ` # method, and can be useful for applications where you want to do something # that is only possible for :class:`~mne.Evoked` objects. For example, here # we use the :meth:`~mne.Evoked.get_peak` method (which is not available for # :class:`~mne.Epochs` objects) to get the peak response in each trial: for ix, trial in enumerate(epochs[:3].iter_evoked()): channel, latency, value = trial.get_peak(ch_type="eeg", return_amplitude=True) latency = int(round(latency * 1e3)) # convert to milliseconds value = int(round(value * 1e6)) # convert to µV print(f"Trial {ix}: peak of {value} µV at {latency} ms in channel {channel}") # %% # .. REFERENCES # # .. _`memory-mapped`: https://en.wikipedia.org/wiki/Memory-mapped_file