Repository: mne-tools/mne-python

Issue:
Make combine_evokeds transparently reorder channels
I have a multicenter dataset where the channels are not in the same order across datasets. It appears to me to be rather complicated to get them all into the same order, keep channel locations aligned, etc. Should we make this easier?


Discussion:
why is this needed? MNE should handle this transparently.

For example, I can't do a grand average.
then grand_average should be fixed.

`raw.reorder_channels(channel_order)` doesn't work for you?
What! I didn't know that existed. Still, mne.combine_evoked should do it behind the scenes.
Not that long ago `pick_channels` would reorder, but @larsoner changed its behaviour to not reorder, only pick. In that PR I asked him for similar reasons you present here to still allow to reorder and he added `reorder_channels`. It is more explicit this way, I like it.
If grand does not perform some safety checks for channel order it should. It does check other types of consistency like sfreq or times IIRC so why not chan order.
I think that grand_average should deal with it.

It's like added values in pandas with a channel index.

Ok, renaming.

If channels occur in different orders for combine_evoked, it should internally reorder a copy of every dataset to have the same order as the first before averaging.

Resolving patch:
diff --git a/mne/evoked.py b/mne/evoked.py
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -828,13 +828,21 @@ def grand_average(all_evoked, interpolate_bads=True):
 def _check_evokeds_ch_names_times(all_evoked):
     evoked = all_evoked[0]
     ch_names = evoked.ch_names
-    for ev in all_evoked[1:]:
+    for ii, ev in enumerate(all_evoked[1:]):
         if ev.ch_names != ch_names:
-            raise ValueError(
-                "%s and %s do not contain the same channels" % (evoked, ev))
+            if set(ev.ch_names) != set(ch_names):
+                raise ValueError(
+                    "%s and %s do not contain the same channels." % (evoked,
+                                                                     ev))
+            else:
+                warn("Order of channels differs, reordering channels ...")
+                ev = ev.copy()
+                ev.reorder_channels(ch_names)
+                all_evoked[ii + 1] = ev
         if not np.max(np.abs(ev.times - evoked.times)) < 1e-7:
             raise ValueError("%s and %s do not contain the same time instants"
                              % (evoked, ev))
+    return all_evoked
 
 
 def combine_evoked(all_evoked, weights):
@@ -861,7 +869,6 @@ def combine_evoked(all_evoked, weights):
     -----
     .. versionadded:: 0.9.0
     """
-    evoked = all_evoked[0].copy()
     if isinstance(weights, string_types):
         if weights not in ('nave', 'equal'):
             raise ValueError('weights must be a list of float, or "nave" or '
@@ -875,7 +882,8 @@ def combine_evoked(all_evoked, weights):
     if weights.ndim != 1 or weights.size != len(all_evoked):
         raise ValueError('weights must be the same size as all_evoked')
 
-    _check_evokeds_ch_names_times(all_evoked)
+    all_evoked = _check_evokeds_ch_names_times(all_evoked)
+    evoked = all_evoked[0].copy()
 
     # use union of bad channels
     bads = list(set(evoked.info['bads']).union(*(ev.info['bads']


Test patch:
diff --git a/mne/tests/test_evoked.py b/mne/tests/test_evoked.py
--- a/mne/tests/test_evoked.py
+++ b/mne/tests/test_evoked.py
@@ -485,6 +485,18 @@ def test_arithmetic():
     assert_equal(gave.nave, 2)
     pytest.raises(TypeError, grand_average, [1, evoked1])
 
+    # test channel (re)ordering
+    evoked1, evoked2 = read_evokeds(fname, condition=[0, 1], proj=True)
+    data2 = evoked2.data  # assumes everything is ordered to the first evoked
+    data = (evoked1.data + evoked2.data) / 2
+    evoked2.reorder_channels(evoked2.ch_names[::-1])
+    assert not np.allclose(data2, evoked2.data)
+    with pytest.warns(RuntimeWarning, match='reordering'):
+        ev3 = grand_average((evoked1, evoked2))
+    assert np.allclose(ev3.data, data)
+    assert evoked1.ch_names != evoked2.ch_names
+    assert evoked1.ch_names == ev3.ch_names
+
 
 def test_array_epochs():
     """Test creating evoked from array."""