Repository: mne-tools/mne-python

Issue:
Allow making evokeds per event type
#### Describe the new feature or enhancement
It is very common that you want Evokeds for each event type, for example:
```python
epochs = mne.Epochs(raw, events, {'left': 1, 'right': 2})
evokeds = [epochs['left'].average(), epochs['right'].average()]  # <-- Make this easier
```

#### Describe your proposed implementation
Add a parameter to the `Epochs.average` method:
```python
evokeds = epochs.average(per_event_type=True)
```

Furthermore, the `.comment` attribute should be set to the event type.

#### Describe possible alternatives
Keep the following list comprehension as canonical solution:
```python
evokeds = [epochs[cond] for cond in epochs.event_id.keys()]
for ev, cond in zip(evokeds, epochs.event_id.keys()): 
    ev.comment = cond
```


Discussion:

Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -951,7 +951,7 @@ def subtract_evoked(self, evoked=None):
         return self
 
     @fill_doc
-    def average(self, picks=None, method="mean"):
+    def average(self, picks=None, method="mean", by_event_type=False):
         """Compute an average over epochs.
 
         Parameters
@@ -965,11 +965,23 @@ def average(self, picks=None, method="mean"):
             (n_channels, n_time).
             Note that due to file type limitations, the kind for all
             these will be "average".
+        by_event_type : bool
+            When ``False`` (the default) all epochs are averaged and a single
+            :class:`Evoked` object is returned. When ``True``, epochs are first
+            grouped by event type (as specified using the ``event_id``
+            parameter) and a list is returned containing a separate
+            :class:`Evoked` object for each event type. The ``.comment``
+            attribute is set to the label of the event type.
+
+            .. versionadded:: 0.24.0
 
         Returns
         -------
-        evoked : instance of Evoked | dict of Evoked
-            The averaged epochs.
+        evoked : instance of Evoked | list of Evoked
+            The averaged epochs. When ``by_event_type=True`` was specified, a
+            list is returned containing a separate :class:`Evoked` object
+            for each event type. The list has the same order as the event types
+            as specified in the ``event_id`` dictionary.
 
         Notes
         -----
@@ -991,22 +1003,44 @@ def average(self, picks=None, method="mean"):
 
         This would compute the trimmed mean.
         """
-        return self._compute_aggregate(picks=picks, mode=method)
+        if by_event_type:
+            evokeds = list()
+            for event_type in self.event_id.keys():
+                ev = self[event_type]._compute_aggregate(picks=picks,
+                                                         mode=method)
+                ev.comment = event_type
+                evokeds.append(ev)
+        else:
+            evokeds = self._compute_aggregate(picks=picks, mode=method)
+        return evokeds
 
     @fill_doc
-    def standard_error(self, picks=None):
+    def standard_error(self, picks=None, by_event_type=False):
         """Compute standard error over epochs.
 
         Parameters
         ----------
         %(picks_all_data)s
+        by_event_type : bool
+            When ``False`` (the default) all epochs are averaged and a single
+            :class:`Evoked` object is returned. When ``True``, epochs are first
+            grouped by event type (as specified using the ``event_id``
+            parameter) and a list is returned containing a separate
+            :class:`Evoked` object for each event type. The ``.comment``
+            attribute is set to the label of the event type.
+
+            .. versionadded:: 0.24.0
 
         Returns
         -------
-        evoked : instance of Evoked
-            The standard error over epochs.
+        std_err : instance of Evoked | list of Evoked
+            The standard error over epochs. When ``by_event_type=True`` was
+            specified, a list is returned containing a separate :class:`Evoked`
+            object for each event type. The list has the same order as the
+            event types as specified in the ``event_id`` dictionary.
         """
-        return self._compute_aggregate(picks, "std")
+        return self.average(picks=picks, method="std",
+                            by_event_type=by_event_type)
 
     def _compute_aggregate(self, picks, mode='mean'):
         """Compute the mean, median, or std over epochs and return Evoked."""


Test patch:
diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py
--- a/mne/tests/test_epochs.py
+++ b/mne/tests/test_epochs.py
@@ -3275,9 +3275,14 @@ def test_average_methods():
     n_epochs, n_channels, n_times = 5, 10, 20
     sfreq = 1000.
     data = rng.randn(n_epochs, n_channels, n_times)
+
     events = np.array([np.arange(n_epochs), [0] * n_epochs, [1] * n_epochs]).T
+    # Add second event type
+    events[-2:, 2] = 2
+    event_id = dict(first=1, second=2)
+
     info = create_info(n_channels, sfreq, 'eeg')
-    epochs = EpochsArray(data, info, events)
+    epochs = EpochsArray(data, info, events, event_id=event_id)
 
     for method in ('mean', 'median'):
         if method == "mean":
@@ -3290,6 +3295,14 @@ def fun(data):
         evoked_data = epochs.average(method=method).data
         assert_array_equal(evoked_data, fun(data))
 
+    # Test averaging by event type
+    ev = epochs.average(by_event_type=True)
+    assert len(ev) == 2
+    assert ev[0].comment == 'first'
+    assert_array_equal(ev[0].data, np.mean(data[:-2], axis=0))
+    assert ev[1].comment == 'second'
+    assert_array_equal(ev[1].data, np.mean(data[-2:], axis=0))
+
 
 @pytest.mark.parametrize('relative', (True, False))
 def test_shift_time(relative):