Skip to content

WIP: add reduction keyword to pwelch - #4000

Closed
mmagnuski wants to merge 29 commits into
mne-tools:masterfrom
mmagnuski:welch_reduction
Closed

WIP: add reduction keyword to pwelch#4000
mmagnuski wants to merge 29 commits into
mne-tools:masterfrom
mmagnuski:welch_reduction

Conversation

@mmagnuski

@mmagnuski mmagnuski commented Feb 17, 2017

Copy link
Copy Markdown
Member

This PR adds ability to specify reduction to apply to welch windows (#3336)
It's actually part of #3820 with some tests added.
@Eric89GXL I was looking for a branch/stash fixing #3621 and found this instead. But I'll push PR adressing n_fft issue today.

This adds reduction keyword argument for reduction across windows, where that can be either 'mean', 'median', callable or None:

  • 'mean' and 'median' perform mean or median respectively
  • callable is called on the windows (may additionally ensure that window dimension was reduced)
  • None omit the reduction so psd_welch returns an array of shape windows x channels x freqs for raw and windows x epochs x channels x freqs for epochs.
  • @kingjr proposed using float values < 0.5 for trimmed mean - I've implemented it here too (using scipy.stats.trim_mean)

I've added it as get_reduction to mne.utils. It can also be private if you prefer.
I've also added a few tests to test_psd - they pass locally, but I'll see what CIs have to say.
❗️ the windows are the last dimension if a reduction is applied (so if custom function is used it has to reduce across axis=-1), but when reduction is None they are moved to the -3 dimension. This means that raw output is of diemnsions windows x channels x freqs and epochs are of epochs x windows x channels x freqs ❗️

TODOs:

  • I'll modify existing tutorial/example that shows how to use reduction
  • I'll add a whats new entry once all is cool (CIs green, comments adressed etc.)

Closes #3336

@larsoner

Copy link
Copy Markdown
Member

Sounds good.

I've added it as get_reduction to mne.utils. It can also be private if you prefer.

Yes, private please.

the windows are the last dimension if a reduction is applied (so if custom function is used it has to reduce across axis=-1

That's what I would expect

but then it might be even more confusing - with windows being first or second dim depending on input type...)

You can just say that the output dimensions will be shape (..., n_windows, n_freq, n_time), i.e. windows are the -3 dimension. I think this is better than always putting them first.

@larsoner

Copy link
Copy Markdown
Member

I can add an example that shows how to use reduction (but may be a separate PR, I guess)

I would say just change an existing example to use reduction='median' just to make people aware of it

@codecov-io

codecov-io commented Feb 17, 2017

Copy link
Copy Markdown

Codecov Report

Merging #4000 into master will increase coverage by <.01%.
The diff coverage is 85.18%.

@@            Coverage Diff             @@
##           master    #4000      +/-   ##
==========================================
+ Coverage   83.01%   83.01%   +<.01%     
==========================================
  Files         346      346              
  Lines       63074    63170      +96     
  Branches     9694     9715      +21     
==========================================
+ Hits        52358    52439      +81     
- Misses       8019     8028       +9     
- Partials     2697     2703       +6

@mmagnuski

Copy link
Copy Markdown
Member Author

I've changed np.moveaxis to np.rollaxis - the former is not present in older numpy.
Can I save the lambda in the tests? flake doesn't like me for using lambda - but I like the one-liner better than creating a def...

@larsoner

Copy link
Copy Markdown
Member

Why not make all well with the universe and do:

windows_max, freqs = psd_welch(
    raw, proj=False, reduction=lambda x: x.max(-1), **kws_psd)

@larsoner

larsoner commented Feb 17, 2017

Copy link
Copy Markdown
Member

Otherwise we'd need to add # noqa: EXXX mmagnuski disapproves of this PEP8 and that's not a design pattern we want to embrace...

@mmagnuski

Copy link
Copy Markdown
Member Author

Scipy 0.12 problem: TypeError: trim_mean() got an unexpected keyword argument 'axis'
One option is to use scipy.stats.mstats.trimboth - it was present in scipy 0.12 and has an axis kwarg.

@larsoner

larsoner commented Feb 17, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

Copy link
Copy Markdown
Member Author

The old trim_mean was undocummented, but used stats.trimboth which does a[lowercut:uppercut] so along first dimension.

@larsoner

larsoner commented Feb 18, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

Copy link
Copy Markdown
Member Author

Yes, I'd add this to fixes. I'll commit that later (tomorrow - European time)

@mmagnuski

Copy link
Copy Markdown
Member Author

@Eric89GXL sorry for the delay, I've added _trim_mean to fixes, now CIs should come back green - then I'll add whats new (and I'll have to rebase once #4003 gets merged).

@mmagnuski

Copy link
Copy Markdown
Member Author

One Test object size estimation. failure and one valid because I forgot to use fixes.get_trim_mean in tests.

@larsoner

Copy link
Copy Markdown
Member

object_size is fixd in master now

@mmagnuski

Copy link
Copy Markdown
Member Author

Ok, I'll rebase later today.

@larsoner

Copy link
Copy Markdown
Member

@mmagnuski please set to MRG if it's ready from your end

Comment thread mne/fixes.py Outdated
scale = 1.0 / (fs * (win * win).sum())
elif scaling == 'spectrum':
scale = 1.0 / win.sum()**2
scale = 1.0 / win.sum() ** 2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually it's better to leave these so copy-paste diffs from scipy match

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I'll revert that commit :)

Comment thread mne/time_frequency/psd.py Outdated
reduction is performed and psd's for individual windows are returned
(so that the output psds are of shape (windows, channels, frequencies)
for 2d input data and (windows, epochs, channels, frequencies) for 3d
input data)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just say "with the windows added as the first dimension"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's actually wrong. You previously commented that it should be -2 dim and I changed it that way. It makes sense as -2 - especially for epochs epochs x windows x channels x freqs)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-3 I mean

Comment thread mne/time_frequency/psd.py Outdated
psds = np.concatenate(f_spectrogram, axis=0)
if reduction is not None:
reduction = _get_reduction(reduction)
psds = reduction(psds).reshape(np.hstack([dshape, -1]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

easier to use dshape + (-1,) than hstack (shapes should always be tuples)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Comment thread mne/utils.py Outdated
raise ValueError('reduction, if float, means proportion to trim in'
' trimmed mean, which has to be > 0 and < 0.5, '
'got {}'.format(reduction))
from .fixes import get_trim_mean

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you really need to nest this? fixes shouldn't require anything from utils...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, I'll de-nest

@mmagnuski

mmagnuski commented Mar 9, 2017

Copy link
Copy Markdown
Member Author

Which example should I modify? I started changing the tutorial on freq analyses in channel space but it uses psd_multitaper - so I started adding a comparison between welch and mutlitper and then I stopped not being sure whether it should be added there (and whether it requires such changes). I didn't see any examples that use psd_welch (apart from real-time acquisition with fieldtrip buffer or plotting custom topographies).

@mmagnuski

Copy link
Copy Markdown
Member Author

Travis errors seem to be unrelated: one job threw lots of docstyle errors from viz/utils at me and the other timed out when downloading mne test data.

Comment thread mne/utils.py Outdated
else:
raise ValueError('reduction, if string, must be "mean" or "median"'
', got {}'.format(reduction))
elif isinstance(reduction, (float, np.float64, np.float32)):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import numbers

isinstance(reduction, numbers.Real)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have to add this.

@agramfort

Copy link
Copy Markdown
Member

@mmagnuski yes I'll update plot_sensors_time_frequency.py with a section on the PSD of Raw data before going to Epochs.

@mmagnuski

Copy link
Copy Markdown
Member Author

Sorry for the delays here, I was busy lately, I'll try to finish it this week.

@jona-sassenhagen

Copy link
Copy Markdown
Contributor

Will you use combine if my epochs.plot_image code is merged first? :)

@agramfort

agramfort commented Jul 3, 2017 via email

Copy link
Copy Markdown
Member


# normalize multitaper and welch to put them on the same scale
psd_m /= psd_m.sum() / len(freqs_m)
psd_w /= psd_w.sum() / len(freqs_w)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

psd_m.sum() -> psd_m.mean()

and why do you need to do this? both functions don't use the same spectral estimation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we don't - I was wondering whether this makes sense to add, but I thought that if almost everything in the tutorial was done with multitapering and now I am introducting Welch, a comparison would be nice. But I don't thinks it is necessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't. Our PSD functions should be consistent in their normalizations

@mmagnuski mmagnuski Jul 3, 2017

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I see I didn't get your first question ("and why do you need to do this? both functions don't use the same spectral estimation?") - I thought you were asking why we should compare welch and multitapering in one figure. So yes, IIRC, there were big differences in psd returned by both functions - that's why I decided to normalize. I'll check that later to show an example of this.

picks=[ch_index], fmin=2, fmax=17, n_jobs=1)

# drop channel dimension and average epochs
psd_m = psds_m.squeeze(axis=1).mean(axis=0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please avoid squeeze. It's dangerous.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can use [:, 0] instead - but can squeeze be dangerous if I specify the axis?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the [:, 0] it's more explicit


###############################################################################
# While in multitaper the averaging is done across independent realizations of
# the signal (using slepian tapers), welch method averages across time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slepian -> Slepian
welch -> Welch

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, I'll change.


psd_w = psds_w[:, 0].mean(axis=0)
psd_w_trim = psds_w_trimmed[:, 0].mean(axis=0)
psd_w_med = psds_w_median[:, 0].mean(axis=0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need to average after a median?
what are you averaging?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the epochs are averaged over

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you think it's weird to take the mean over epochs and the median of short windows within epochs?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yes I agree that it is kind of weird. :) I can change it to median or I'll change that example later so that psd_welch is presented on raw data (as you suggested in other comments).

# The reduction in power that can be seen in the figure above is due to the
# fact that values for power spectral density follow a positive skewed
# gamma-like distribution. Lets take a look at this distribution. First we will
# use ``combine=None`` to get all the welch windows without averaging. Notice

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

welch -> Welch

please check everywhere

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@agramfort

agramfort commented Jul 3, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

mmagnuski commented Jul 3, 2017

Copy link
Copy Markdown
Member Author

why would you want to do trimmed mean on EEG in the first place

Well, I'm not sure if I would be doing that but in the original issue about adding reduction options to psd @kingjr proposed trimmed mean and your response was:

yes I think that trimmed mean would be nice.

😋

@mmagnuski

Copy link
Copy Markdown
Member Author

(BTW - I checked the example before - it looks ok for me, is something wrong with how it looks?)

@agramfort

agramfort commented Jul 3, 2017 via email

Copy link
Copy Markdown
Member

@agramfort

agramfort commented Jul 3, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

mmagnuski commented Jul 3, 2017

Copy link
Copy Markdown
Member Author

if you had said this, I would have asked that you illustrate this in an
example :)

Ah, ok - I though you are asking why would I use trimmed mean over median.
Actually my own interest in adding combine is the None option where I get all the windows and can fit gamma distribution to power distributions of the alpha peak for example. That's why my example went in this direction. But I agree that using median/trimmed mean to be more robust against artifacts may be the more poular scenario. I just think that people should be aware that they will always get reduced spectrum even if all their signal is super-artifact-free. I can try too look into mne sample data for some raw file segments with some artifacts and resketch the tutorial, but I will be able to do it only later this week.

it was just for others to see / comment

Ok, I wasn't sure because I posted the link previously and the example didn't change since then. :)

@agramfort

agramfort commented Jul 3, 2017 via email

Copy link
Copy Markdown
Member

@agramfort

agramfort commented Jul 4, 2017 via email

Copy link
Copy Markdown
Member

@agramfort

Copy link
Copy Markdown
Member

@mmagnuski you need to rebase.

any hope to complete this soon?

I am thinking about pushing a release soon.

@mmagnuski

Copy link
Copy Markdown
Member Author

Yes, I can take a look during the weekend and finish the remaining issues here.

@agramfort

agramfort commented Jul 13, 2017 via email

Copy link
Copy Markdown
Member

@agramfort

Copy link
Copy Markdown
Member

@mmagnuski you need to rebase. Let's try to finish this before the release late august

@mmagnuski

Copy link
Copy Markdown
Member Author

Yes, I'll finish this within a couple of days, sorry.

@larsoner

Copy link
Copy Markdown
Member

Ping @mmagnuski it's getting close to release time, any time to polish this one?

@mmagnuski

Copy link
Copy Markdown
Member Author

Yes, I should finally have time for that soon. Another option is that I revert changes to tutorial, we merge this now and I add a follow-up PR for changes in the tutorial. Everything else is finished here.

@agramfort

agramfort commented Aug 12, 2017 via email

Copy link
Copy Markdown
Member

@larsoner

Copy link
Copy Markdown
Member

@mmagnuski any time for this one now?

@mmagnuski

Copy link
Copy Markdown
Member Author

Yes, I should finally have time for this in next few days! :)

@larsoner

larsoner commented Apr 6, 2018

Copy link
Copy Markdown
Member

@mmagnuski any time to come back to this one? You'll probably want to start fresh because of how many viz / PSD functions have changed :(

@larsoner larsoner changed the title [MRG]: add reduction keyword to pwelch WIP: add reduction keyword to pwelch Apr 6, 2018
@mmagnuski

Copy link
Copy Markdown
Member Author

Ah, my forgotten baby! I got a bit stuck last time trying to find artifacts in test data that would demonstrate usefulness of these reductions. I didn't go very far with this as my main interest was originally in using better estimates of non-gaussian single-trial-power distributions, not necessarily artifact-robustness. But being able to get all the windows without reducing them would be enough gain for me from this PR actually.
I was promising to get back to this PR so many times already that I'm not going to do the same this time - but I'll try to surprise you with a new PR in not so distant future. ;)

@mmagnuski mmagnuski closed this Apr 6, 2018
@larsoner

larsoner commented Apr 6, 2018

Copy link
Copy Markdown
Member

Sounds good :)

@larsoner

Copy link
Copy Markdown
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mne.time_frequency.psd_welch add median option

6 participants