Repository: mne-tools/mne-python

Issue:
drop_channels silently passes when no channels are dropped
Maybe it's only me who's this thick skulled, but I do this all the time:

`raw.drop_channels('EEG 000')`

And then wonder why my channel is still in there.

I think it would be helpful if
- drop_channels would be a bit more verbose on what it does
- particularly, if it would warn if the channels intended to be dropped are not found, and if the action would result in no drops at all
- we could do a little `ch_names = [ch_names] if isinstance(ch_names, string_types) else ch_names` at the beginning of `.drop_channels`



Discussion:
+1 for a ValueError when trying to drop a channel that is not present in
the data

Ok I will get on this ASAP, and afterwards, when it's still open, the ica.get_components issue.


Resolving patch:
diff --git a/mne/channels/channels.py b/mne/channels/channels.py
--- a/mne/channels/channels.py
+++ b/mne/channels/channels.py
@@ -560,7 +560,7 @@ def drop_channels(self, ch_names):
         Parameters
         ----------
         ch_names : list
-            The list of channels to remove.
+            List of the names of the channels to remove.
 
         Returns
         -------
@@ -575,10 +575,26 @@ def drop_channels(self, ch_names):
         -----
         .. versionadded:: 0.9.0
         """
-        bad_idx = [self.ch_names.index(c) for c in ch_names
-                   if c in self.ch_names]
+        msg = ("'ch_names' should be a list of strings (the name[s] of the "
+               "channel to be dropped), not a {0}.")
+        if isinstance(ch_names, string_types):
+            raise ValueError(msg.format("string"))
+        else:
+            if not all([isinstance(ch_name, string_types)
+                        for ch_name in ch_names]):
+                raise ValueError(msg.format(type(ch_names[0])))
+
+        missing = [ch_name for ch_name in ch_names
+                   if ch_name not in self.ch_names]
+        if len(missing) > 0:
+            msg = "Channel(s) {0} not found, nothing dropped."
+            raise ValueError(msg.format(", ".join(missing)))
+
+        bad_idx = [self.ch_names.index(ch_name) for ch_name in ch_names
+                   if ch_name in self.ch_names]
         idx = np.setdiff1d(np.arange(len(self.ch_names)), bad_idx)
         self._pick_drop_channels(idx)
+
         return self
 
     def _pick_drop_channels(self, idx):


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
@@ -1785,7 +1785,7 @@ def test_pick_channels_mixin():
                     baseline=(None, 0), preload=True)
     ch_names = epochs.ch_names[:3]
     epochs.preload = False
-    assert_raises(RuntimeError, epochs.drop_channels, ['foo'])
+    assert_raises(RuntimeError, epochs.drop_channels, [ch_names[0]])
     epochs.preload = True
     ch_names_orig = epochs.ch_names
     dummy = epochs.copy().pick_channels(ch_names)
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
@@ -344,11 +344,16 @@ def test_drop_channels_mixin():
     assert_equal(ch_names, dummy.ch_names)
     assert_equal(ch_names_orig, evoked.ch_names)
     assert_equal(len(ch_names_orig), len(evoked.data))
+    dummy2 = evoked.copy().drop_channels([drop_ch[0]])
+    assert_equal(dummy2.ch_names, ch_names_orig[1:])
 
     evoked.drop_channels(drop_ch)
     assert_equal(ch_names, evoked.ch_names)
     assert_equal(len(ch_names), len(evoked.data))
 
+    for ch_names in ([1, 2], "fake", ["fake"]):
+        assert_raises(ValueError, evoked.drop_channels, ch_names)
+
 
 def test_pick_channels_mixin():
     """Test channel-picking functionality"""