Repository: mne-tools/mne-python

Issue:
Make event_id = None by default in constructor
It seems like the default behavior for specifying the `event_id` should be None. Anybody opposed to setting a default. It would remain backwards-compatible. 



Discussion:
You'll need to also set `tmin` and `tmax` to something because they come afterward, assuming you're talking about the `Epochs` constructor. Would you propose to have it be `tmin=None, tmax=None` and throw an error if people don't set them? I guess then people could do `Epochs(raw, events, tmin=-0.2, tmax=1.)` instead of `Epochs(raw, events, None, -0.2, 1.)`. Is that the idea?

I'd like to see defaults (None, -.2, 1) for event_id, tmin, and tmax. A backwards-compatible change that makes a lot of stuff less verbose.

> I'd like to see defaults (None, -.2, 1) for event_id, tmin, and tmax. A
> backwards-compatible change that makes a lot of stuff less verbose.
> 
> fine with me although I would use tmax=0.5 as default :)

I'd just make sure to use something that doesn't break your computer if you happen to have 1000+ events sampled at 1000 Hz. (I guess `preload=False` by default, but still.)

let's set defaults then

any volunteer?

I'm also +1 for defaults. I would recommend (0, 1) for tmin tmax, at least it'll be in percentage of the original time.

0, 1 doesn't play well with baselining though does it? Also I _really_ hate it when people don't show their baseline in their plots.

If they want to baseline, they should be required to set the timing manually. 

> Also I really hate it when people don't show their baseline in their plots.

ha!

Hello,
I would like to work on this.

Sorry overlooked this one. I'm not really convinced we need these defaults here. You should really set them explicitly. Ok for `event_id` which would just mean: take all events. But tmin and tmax should really be chosen explicitly. People should be reminded of this by not allowing them to construct epochs without making a choice, so her'es my -1 on this. Do we _really_ need this?

I agree with @dengemann. `tmin` and `tmax` should be set explicitly. But `event_id` can be set to `None` by default, which would mean -- take all events.

If we set event_id to a kwarg, but not tmin and tmax, we're changing the order of the args, don't we?

I am not convinced defaults for tmin and tmax are inherently bad either. "People should be reminded"? This is not a thing where a bad default will slip in unexpectedly; either your epoch time window shows you what you care about, or it doesn't, and it is immediately clear if it does. This is different from stuff that can silently creep in, like baselining (which we _do_ have a default for!).

We do, however, not strictly need it. In analyzing real data, you will almost always decide on a specific time window. The only real difference would be in examples and tests, where it allows you to be a bit more concise.

In summary, I'm not convinced of the arguments against defaults for tmin and tmax, but I'm not strongly pro either.

Maybe it's just not worth the effort then. Changing the API for just some comfort is not necessary.

It's a two-line change though that in the long run could save hundreds of lines for demos and tests.

But am I seeing it correctly that if we want to keep the current API, we need to make either all 3 kwargs (due to the order), or keep them all as positional args?

> we need to make either all 3 kwargs (due to the order), or keep them all as positional args?

That's right yes. 

> It's a two-line change though that in the long run could save hundreds of lines for demos and tests.

Another good argument for the tests. For the demos this exactly not what we want to keep implicit.

We could make tmin=tmax=None (raise error) and event_id=None (use all) by
default. Then people could at least do

```
Epochs(raw, events, tmin=-0.2, tmax=1.0)
```

But that isn't all that elegant.

Thinking about it more, the -0.2 at least does make sense as a default
since it's very widely used. At this point I'm +0.5 on setting tmin/tmax to
some numerical defaults. It seems like it could simplify a few things,
especially when someone just wants a quick minimal look at some data.

Ok I buy the arguments for simplifying tests. But if we add these defaults let's add some verbosity and keep explicit setting in examples.

One way to make it verbose would be along these lines:

```
...
Epochs(inst, events, event_id=None, tmin=None, tmax=None, ...)
...
    if tmin == None:
        warnings.warn("No window start giving, setting to -.2.")
        tmin = -.2
```

etc

But I'm not sure that's worth it.

> `warning.warn("No window start giving, setting to -.2.")

warning.warn("No tmin given, defaults to -.2.")

And since we're there, we can also make a default `events` too, can't we?

One of the things I would really love to see is a `raw.to_epochs()` method. Basically, here you can use all the `events` in `raw.events` and default `tmin` / `tmax` and it would make sense. Or you could do `raw.to_epochs(tmin=tmin, tmax=tmax)`.

(Analogously, you can have `epochs.to_raw()` and it would give you a raw object from the concatenated epochs)

I'm -1 on epochs.to_raw() . It would create plenty of issues with the subsequent filtering

> I'm -1 on epochs.to_raw() . It would create plenty of issue with the subsequent filtering

Point taken. Probably not such a great idea to go from epochs to raw.

I'm not sure what the purpose of `.to_raw()` would be, but I like `raw.to_epochs`. It would avoid the `mne.Epochs` or `from mne import Epochs` that's right now just boilerplate. And code wise, it's probably a very small change.

+1 `raw.to_epochs`
-1 `epochs.to_raw`

Why add another way to construct epochs from raw with raw.to_epochs instead
of extending support of Epochs(raw)?

Well I'd think it would be very simple to have raw.to_epochs() just be a call to mne.Epochs(self), wouldn't it?

I feel `raw.to_epochs` is a cleaner approach to the issue @teonlamont raised. It offers exactly what you want to do: quickly look at the epochs data without many intermediate steps / arguments. Just give two arguments (`tmin` and `tmax`) and voila -- you have your epochs object. It's a simple transformation that works in most cases.

To me, `Epochs` is the data container -- which need not necessarily be tied to `raw` (in most cases it does), but can also be read from a file (e.g., EEGLAB set files) or a real-time stream. It is more generic. If you want to give events which don't come from the stim channel, then use the constructor.

For the experiments I know, there'd only be very few usage cases for a `raw.to_epochs()` without kwargs.  I just think making `raw.to_epochs()` an alias of `mne.Epochs(raw)` would be a handy shortcut.

[I actually got mistaken, I thought we were discussing about `EpochsArray`, which I personally would like to get as much default (`tmin`, `events`, `event_id`) as possible to quickly look up data (from a 3D matrix) without having to `make_info`, define an `events` array etc.)...

So.. I'm personally not in need of additional functions or param default here.]

> To me, Epochs is the data container -- which need not necessarily be tied
> to raw (in most cases it does), but can also be read from a file (e.g.,
> EEGLAB set files) or a real-time stream. It is more generic. If you want to
> give events which don't come from the stim channel, then use this object.

I think of Epochs(raw) as being how you construct epochs from a raw object.
I suspect that is how users think about using that class. There are other
ways to get access to epochs sure -- reading saved ones from disk for
example -- but not from a raw object. In other words, I'm -1 on increasing
the number of ways we will need to answer the question "what function or
method do I use to create epochs from my raw object?", as this proposal
would do, unless there is a compelling reason to.

I don't think events=None case (since I think that's all we're really
talking about) is worth making a new function for. Convenience is slightly
more compelling, but I'm still -0.5 on that (it goes against the "one clear
way" mantra, which I think we violate a bit too often). If other people do
agree on adding the method, though, I don't yet see a reason to make it
anything other than a wrapper to Epochs, with appropriate expansion of
Epochs capabilities.

> I'm -1 on increasing the number of ways we will need to answer the question "what function or method do I use to create epochs from my raw object?", as this proposal would do, unless there is a compelling reason to.

This was exactly the question bothering me when I proposed this. However, I think we have precedence in other packages in the scientific python ecosystem to justify such a move. See for example how you can convert a [Series into a DataFrame](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.to_frame.html) or [a list to a numpy array](http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.asarray.html). I would consider transforming objects an exception to this maxim.

> I don't yet see a reason to make it anything other than a wrapper to Epochs

I'm fine with that.

What do you think would be more consistent/MNE style Eric/everyone?

I think `epochs = raw.to_epochs()` would be more than convenience; it would also save at least one line of boilerplate in basically every script, demo, everything, while, if at all, cleaning up the namespace.

I think using Epochs(raw, ...) has been "the one way to do it" since the
start of the package (something like 5 years?), so there is a lot of
cumulative material and code surrounding it. I'd rather not change all
examples, docs, and teaching materials to use the new method. I actually
find the existing class-instantiation way a bit nicer from an instructional
perspective because it makes it obvious which class users will deal with.
So to me saving a line (which it doesn't do if your import is `import mne`)
isn't worth the disruption to existing patterns. I don't want to end up
like pandas where it feels like the interface, recommended ways of doing
things, etc. seem to change release to release.

> I think using Epochs(raw, ...) has been "the one way to do it" since the start of the package (something like 5 years?), so there is a lot of cumulative material and code surrounding it. I'd rather not change all examples, docs, and teaching materials to use the new method. I actually find the existing class-instantiation way a bit nicer from an instructional perspective because it makes it obvious which class users will deal with.

https://en.wikipedia.org/wiki/Status_quo_bias ;) On a more serious note, let's wait for other opinions then.

No new API methods etc. for this please, YAGNI.
We have Epochs and it did a great job so far. -1 on raw methods, especily if its only for these convenience reasons. It will blow up our APIs and ways to do it and generate lots of additional maintenance work for no clear benefit. 
Let's implement the initial idea in a clean and simple way. Maybe logger.info would be enough.

> On 21 Feb 2016, at 21:55, Eric Larson notifications@github.com wrote:
> 
> Why add another way to construct epochs from raw with raw.to_epochs instead
> of extending support of Epochs(raw)?
> —
> Reply to this email directly or view it on GitHub.

I am really -100 on a raw to epochs method for the reason @Eric89GXL mentioned. For stable APIs you must not change core APIs often. Pandas is a bad example, their API politics are a reason why their package is a PITA to maintain in production code. Eric and I removed pandas entirely for these reasons from some of our projects. Stop "geeking" and put your energy into new computational or viz functionality. 
If you need shortcuts put them in your private library. 

> On 21 Feb 2016, at 23:45, Mainak Jas notifications@github.com wrote:
> 
> I think using Epochs(raw, ...) has been "the one way to do it" since the start of the package (something like 5 years?), so there is a lot of cumulative material and code surrounding it. I'd rather not change all examples, docs, and teaching materials to use the new method. I actually find the existing class-instantiation way a bit nicer from an instructional perspective because it makes it obvious which class users will deal with.
> 
> https://en.wikipedia.org/wiki/Status_quo_bias ;) On a more serious note, let's wait for other opinions then.
> 
> —
> Reply to this email directly or view it on GitHub.

I feel that something that has been a pain to users for 5 years doesn't need to continue being a pain to use. Pandas might be a bad example, but even folks at numpy follow the same pattern. It's a common pattern that's in my muscle memory. IMHO, "zen of python" is a good starting point, but there are reasonable use cases where we should allow for exceptions. My 2c.

But I a gree with JR that especially the EpochsArray can benefit from sensible defaults.

> On 21 Feb 2016, at 23:45, Mainak Jas notifications@github.com wrote:
> 
> I think using Epochs(raw, ...) has been "the one way to do it" since the start of the package (something like 5 years?), so there is a lot of cumulative material and code surrounding it. I'd rather not change all examples, docs, and teaching materials to use the new method. I actually find the existing class-instantiation way a bit nicer from an instructional perspective because it makes it obvious which class users will deal with.
> 
> https://en.wikipedia.org/wiki/Status_quo_bias ;) On a more serious note, let's wait for other opinions then.
> 
> —
> Reply to this email directly or view it on GitHub.

the `event_id` is completely separate from the method suggestion. I think that since we have internals that handle when None is specified, we should be able to have `Epochs(..., event_id=None,...)` by default.

but to be honest, we should have a function for epochs creation. we are inconsistent with new object construction. for instance, for Raw, we have the io module for the `read_*` functions, for Epochs we construct with the class, for SourceEstimates we use the `apply_inverse` operators, and Time Frequency is completely a mix. there should really be clear nomenclature for making these objects. 

Friends, listen. Just chatted with @agramfort and he agrees that we should
stop talking about core API changes for a good while. If you don't manage
to see the issue and the costs that go along with this now don't be
worried. In the mean time there are many more exciting and valuable things
to do. For some of you, PhD theses are to be advanced, fascinating work on
viz, signal processing, statistics and code documentation is waiting for
us. And I'm almost 100% sure that the weaknesses that have been existing to
our Epochs API are not actual impediments to all these fascinating things
that are waiting for us. Maybe in a bit you will then understand, that once
you have a working and good API, you don't want to touch it just because
you find another more sexy way to do it. Debian but also numpy and sklearn
are great software projects with a wide user base exactly because they
understood that core API development has to be slow. Think of _really_
slow. Now tripple that. I think we should learn from them and invest our
collective energy accordingly. For us, slow, in the moment this means not
to touch core API issues beyond bugs and documentation. There might be a
time when we see at one glance a new consistent way of doing everything.
But it will feel different, that's not now. These gradual little API
changes lead in to hell directly. A mess is waiting there for us and that's
why we are not going there. Now please re-orient your attention and release
your enthusiasm on other topics that won't drive me and @agramfort mad.
Thank you.

On Mon, Feb 22, 2016 at 2:34 AM, Teon L Brooks notifications@github.com
wrote:

> but to be honest, we should have a function for epochs creation. we are
> inconsistent with new object construction. for instance, for Raw, we have
> the io module for the read_\* functions, for Epochs we construct with the
> class, for SourceEstimates we use the apply_inverse operators, and Time
> Frequency is completely a mix. there should really be clear nomenclature
> for making these objects.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/mne-tools/mne-python/issues/2872#issuecomment-186973850
> .


Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -144,7 +144,7 @@ class _BaseEpochs(ProjMixin, ContainsMixin, UpdateChannelsMixin,
     This class provides basic functionality and should never be instantiated
     directly. See Epochs below for an explanation of the parameters.
     """
-    def __init__(self, info, data, events, event_id, tmin, tmax,
+    def __init__(self, info, data, events, event_id=None, tmin=-0.2, tmax=0.5,
                  baseline=(None, 0), raw=None,
                  picks=None, name='Unknown', reject=None, flat=None,
                  decim=1, reject_tmin=None, reject_tmax=None, detrend=None,
@@ -1689,9 +1689,9 @@ class Epochs(_BaseEpochs):
         and a dict is created with string integer names corresponding
         to the event id integers.
     tmin : float
-        Start time before event.
+        Start time before event. If nothing is provided, defaults to -0.2
     tmax : float
-        End time after event.
+        End time after event. If nothing is provided, defaults to 0.5
     baseline : None or tuple of length 2 (default (None, 0))
         The time interval to apply baseline correction.
         If None do not apply it. If baseline is (a, b)
@@ -1835,9 +1835,9 @@ class Epochs(_BaseEpochs):
     mne.Epochs.equalize_event_counts
     """
     @verbose
-    def __init__(self, raw, events, event_id, tmin, tmax, baseline=(None, 0),
-                 picks=None, name='Unknown', preload=False, reject=None,
-                 flat=None, proj=True, decim=1, reject_tmin=None,
+    def __init__(self, raw, events, event_id=None, tmin=-0.2, tmax=0.5,
+                 baseline=(None, 0), picks=None, name='Unknown', preload=False,
+                 reject=None, flat=None, proj=True, decim=1, reject_tmin=None,
                  reject_tmax=None, detrend=None, add_eeg_ref=True,
                  on_missing='error', verbose=None):
         if not isinstance(raw, _BaseRaw):
@@ -1888,7 +1888,7 @@ class EpochsArray(_BaseEpochs):
         If some events don't match the events of interest as specified
         by event_id, they will be marked as 'IGNORED' in the drop log.
     tmin : float
-        Start time before event.
+        Start time before event. If nothing provided, defaults to -0.2.
     event_id : int | list of int | dict | None
         The id of the event to consider. If dict,
         the keys can later be used to access associated events. Example:


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
@@ -1979,4 +1979,13 @@ def test_seeg():
     assert_equal(len(picks), n_channels)
 
 
+def test_default_values():
+    """Test default event_id, tmax tmin values are working correctly"""
+    raw, events = _get_data()[:2]
+    epoch_1 = Epochs(raw, events[:1], preload=True)
+    epoch_2 = Epochs(raw, events[:1], event_id=None, tmin=-0.2, tmax=0.5,
+                     preload=True)
+    assert_equal(hash(epoch_1), hash(epoch_2))
+
+
 run_tests_if_main()