Repository: mne-tools/mne-python

Issue:
Why do some examples use weights=(.5, -.5) for combine_evoked contrasts?
This came up during a workshop. The simple difference (contrast ERP/F) would equal (1, -1), right?

@dengemann 


Also bring back evoked arithmetics


Discussion:
Maybe a question on nave staying about the same ?

Then it's highly counterintuitive because the actual values change, hm ..?

I.e., you get different values if you do `evoked1.data  - evoked2.data` compared to `combine_evokeds` with `weights=(.5, -.5)`.
Which examples do that? 
This was a point of confusion during the Frankfurt workshop this week. Jona and I realized that we would need a clear-cut avereging and contrasting tutorial that develops the intuitions and different scenarios and explains weighted averaging.
https://mne-tools.github.io/stable/auto_tutorials/plot_introduction.html

@mmagnuski 
I ran into this problem today too.  I think that if a user passes `weights` as an iterable, the weights should be normalized by the `combine_evoked` function (but they aren't). It looks like the math in the code comment [here](https://github.com/mne-tools/mne-python/blob/master/mne/evoked.py#L887) is missing an overall denominator term (called `V_1` in the equation for `\hat{\sigma}_w^2` [here](https://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance)), so the function effectively assumes that `V_1` = 1, AKA, that the weights are already normalized, but nothing constrains the user to pass in normalized weights.

Here's an example illustrating the problem.  Imagine we want to subtract condition `aud_right` from condition `aud_left`:

```py
import os
import numpy as np
import mne
sample_data_folder = mne.datasets.sample.data_path()
sample_data_ave_file = os.path.join(sample_data_folder, 'MEG', 'sample',
                                    'sample_audvis-ave.fif')
evokeds = mne.read_evokeds(sample_data_ave_file, baseline=[None, 0])
aud_left, aud_right, vis_left, vis_right = evokeds

# here's the important bit
integer_weights = np.array([aud_left.nave, -1 * aud_right.nave])
sum_to_one_weights = integer_weights / np.sum(np.abs(integer_weights))

# this plot (wrong) says "Nave = 1" and thinks the difference is on the order of ~4000 fT/cm
mne.combine_evoked([aud_left, aud_right], weights=integer_weights).plot(picks='grad')

# this plot (right) says "Nave = 116" and thinks the difference is on the order of 40 fT/cm
mne.combine_evoked([aud_left, aud_right], weights=sum_to_one_weights).plot(picks='grad')
```
Let's solve this with a hopefully quick conversation Monday!
I'd be interested in participating in this too as it [bugged me before](https://github.com/mne-tools/mne-python/issues/4438). I wonder if there is a good case for [allowing weights other than +1 and -1](https://mne-tools.github.io/0.14/manual/source_localization/inverse.html?highlight=effective%20number%20averages#effective-number-of-averages). If not, restricting to just these two could remove a lot of confusion
I'd like that @jasmainak .

Did any discussion happen on Monday? :)
Yes @drammock can fill you in
@jona-sassenhagen here is what @larsoner and I discussed: 

When *adding* evokeds there are 2 sensible use cases:
- adding evokeds where you want to ignore the number of epochs used to create each evoked. This is already supported by `weights='equal'`
- adding evokeds where you want to respect the number of epochs used to create each evoked. This is already supported by `weights='nave'`.  This yields the same result as if you had pooled the conditions in advance (before calling `epochs.average()`)

When *subtracting* evokeds, the only case that makes sense (I think) is `weights=[1, -1]`, which ends up with `.data` equivalent to `evokeds[0].data - evokeds[1].data`.  The downside is that it yields kind of weird results for the resulting evoked object's `.nave` attribute, because `.nave` is designed to reflect "effective number of averages" so that the noise covariance can be scaled appropriately (see [the manual link](https://mne-tools.github.io/0.14/manual/source_localization/inverse.html#effective-number-of-averages) @jasmainak gave before).  For example:

```py
import os
import numpy as np
import mne

sample_data_folder = mne.datasets.sample.data_path()
sample_data_ave_file = os.path.join(sample_data_folder, 'MEG', 'sample',
                                    'sample_audvis-ave.fif')
evokeds = mne.read_evokeds(sample_data_ave_file, baseline=[None, 0])
combined = mne.combine_evoked(evokeds[:2], weights=[1, -1])
assert np.array_equal(combined.data, evokeds[0].data - evokeds[1].data)
print(evokeds[0].nave, evokeds[1].nave, combined.nave)                                                                                                                                                                                                      
# 55 61 29
```

Resolving patch:
diff --git a/mne/evoked.py b/mne/evoked.py
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -837,7 +837,13 @@ def combine_evoked(all_evoked, weights):
     """Merge evoked data by weighted addition or subtraction.
 
     Data should have the same channels and the same time instants.
-    Subtraction can be performed by passing negative weights (e.g., [1, -1]).
+    Subtraction can be performed by calling
+    ``combine_evoked([evoked1, -evoked2], 'equal')``
+
+    .. Warning::
+        If you provide an array of weights instead of using `'equal'` or
+        `'nave'`, strange things may happen with your resulting signal
+        amplitude and/or `.nave` attribute.
 
     Parameters
     ----------
diff --git a/tutorials/plot_eeg_erp.py b/tutorials/plot_eeg_erp.py
--- a/tutorials/plot_eeg_erp.py
+++ b/tutorials/plot_eeg_erp.py
@@ -181,7 +181,7 @@
 # Then, we construct and plot an unweighted average of left vs. right trials
 # this way, too:
 mne.combine_evoked(
-    all_evokeds, weights=(0.25, -0.25, 0.25, -0.25)).plot_joint(**joint_kwargs)
+    [aud_l, -aud_r, vis_l, -vis_r], weights='equal').plot_joint(**joint_kwargs)
 
 ###############################################################################
 # Often, it makes sense to store Evoked objects in a dictionary or a list -
diff --git a/tutorials/plot_introduction.py b/tutorials/plot_introduction.py
--- a/tutorials/plot_introduction.py
+++ b/tutorials/plot_introduction.py
@@ -280,9 +280,8 @@
 # This function can use ``weights='equal'``, which provides a simple
 # element-by-element subtraction (and sets the
 # ``mne.Evoked.nave`` attribute properly based on the underlying number
-# of trials) using either equivalent call:
+# of trials) using:
 
-contrast_a = mne.combine_evoked([evoked1, evoked2], weights=[0.5, -0.5])
 contrast_b = mne.combine_evoked([evoked1, -evoked2], weights='equal')
 print(contrast_b)
 


Test patch: