Skip to content

Commit

Permalink
[MRG] DOC: clarify cluster-based permutation testing (mne-tools#10658)
Browse files Browse the repository at this point in the history
  • Loading branch information
sappelhoff authored May 23, 2022
1 parent c3d6717 commit a2f6fe7
Show file tree
Hide file tree
Showing 26 changed files with 542 additions and 209 deletions.
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# simple makefile to simplify repetetive build env management tasks under posix

# caution: testing won't work on windows, see README

PYTHON ?= python
PYTESTS ?= py.test
CTAGS ?= ctags
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ https://mne.discourse.group
Licensing
^^^^^^^^^

MNE-Python is **BSD-licenced** (3 clause):
MNE-Python is **BSD-licenced** (BSD-3-Clause):

This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.
Expand Down
2 changes: 1 addition & 1 deletion doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Current (1.1.dev0)

Enhancements
~~~~~~~~~~~~
- Add :func:`mne.bem.distance_to_bem` to find depth of source positions (:gh:`10632` by :newcontrib:`Matt Courtemanche`)
- Add :func:`mne.bem.distance_to_bem` to find depth of source positions (:gh:`10632` by :newcontrib:`Matt Courtemanche`)

- Add support for ahdr files in :func:`mne.io.read_raw_brainvision` (:gh:`10515` by :newcontrib:`Alessandro Tonin`)

Expand Down
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@
'n_vertices', 'n_faces', 'n_channels', 'm', 'n', 'n_events', 'n_colors',
'n_times', 'obj', 'n_chan', 'n_epochs', 'n_picks', 'n_ch_groups',
'n_dipoles', 'n_ica_components', 'n_pos', 'n_node_names', 'n_tapers',
'n_signals', 'n_step', 'n_freqs', 'wsize', 'Tx', 'M', 'N', 'p', 'q',
'n_signals', 'n_step', 'n_freqs', 'wsize', 'Tx', 'M', 'N', 'p', 'q', 'r',
'n_observations', 'n_regressors', 'n_cols', 'n_frequencies', 'n_tests',
'n_samples', 'n_permutations', 'nchan', 'n_points', 'n_features',
'n_parts', 'n_features_new', 'n_components', 'n_labels', 'n_events_in',
Expand Down
14 changes: 6 additions & 8 deletions doc/install/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -570,23 +570,21 @@ First-time contributors

Welcome to MNE-Python! We're very happy to have you here. 🤗 And to ensure you
get proper credit for your work, please add a changelog entry with the
following pattern **at the top** of the respective subsection (bugfix,
new feature etc.):
following pattern **at the top** of the respective subsection (bugs,
enhancements, etc.):

.. code-block:: rst
Bug
---
Bugs
----
.. |Your Name| replace:: **Your Name**
- Short description of the changes (:gh:`0000` **by new contributor** |Your Name|_)
- Short description of the changes (:gh:`0000` by :newcontrib:`Firstname Lastname`)
- ...
where ``0000`` must be replaced with the respective GitHub pull request (PR)
number.
number, and ``Firstname Lastname`` must be replaced with your full name.

It is usually best to wait to add a line to the changelog until your PR is
finalized, to avoid merge conflicts (since the changelog is updated with
Expand Down
12 changes: 12 additions & 0 deletions doc/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,18 @@ @article{Sarvas1987
year = {1987}
}

@article{Sassenhagen2019,
author = {Sassenhagen, Jona and Draschkow, Dejan},
doi = {10.1111/psyp.13335},
journal = {Psychophysiology},
volume = {56},
number = {6},
pages = {e13335},
keywords = {cluster-based permutation test, EEG, MEG, statistics},
title = {Cluster-based permutation tests of MEG/EEG data do not establish significance of effect latency or location},
year = {2019}
}

@article{SavitzkyGolay1964,
author = {Savitzky, Abraham and Golay, Marcel J. E.},
doi = {10.1021/ac60214a047},
Expand Down
2 changes: 1 addition & 1 deletion examples/decoding/ems_filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
filters, surrogate single trials are created in which multi-sensor activity is
reduced to one time series which exposes experimental effects, if present.
We will first plot a trials x times image of the single trials and order the
We will first plot a trials × times image of the single trials and order the
trials by condition. A second plot shows the average time series for each
condition. Finally a topographic plot is created which exhibits the temporal
evolution of the spatial filters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
confidence intervals are computed as described in :footcite:`EfronHastie2016`
across channels.
The advantage of this method over summarizing the Space x Time x Frequency
The advantage of this method over summarizing the Space × Time × Frequency
output of a Morlet Wavelet in frequency bands is relative speed and, more
importantly, the clear-cut comparability of the spectral decomposition (the
same type of filter is used across all bands).
Expand Down
110 changes: 92 additions & 18 deletions mne/channels/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ def _recursive_flatten(cell, dtype):

@fill_doc
def read_ch_adjacency(fname, picks=None):
"""Parse FieldTrip neighbors .mat file.
"""Parse FieldTrip neighbors ``.mat`` file.
More information on these neighbor definitions can be found on the related
`FieldTrip documentation pages
Expand All @@ -1184,10 +1184,9 @@ def read_ch_adjacency(fname, picks=None):
Parameters
----------
fname : str
The file name. Example: 'neuromag306mag', 'neuromag306planar',
'ctf275', 'biosemi64', etc.
The file name. See "Notes" below for a list of valid arguments.
%(picks_all)s
Picks Must match the template.
Picks must match the template.
Returns
-------
Expand All @@ -1198,30 +1197,100 @@ def read_ch_adjacency(fname, picks=None):
See Also
--------
find_ch_adjacency
mne.viz.plot_ch_adjacency
find_ch_adjacency
mne.stats.combine_adjacency
Notes
-----
This function is closely related to :func:`find_ch_adjacency`. If you
don't know the correct file for the neighbor definitions,
:func:`find_ch_adjacency` can compute the adjacency matrix from 2d
sensor locations.
If you don't know the correct ``fname`` for the neighbor definitions,
of if the neighbor definition you need is not shipped by MNE-Python,
you may use :func:`find_ch_adjacency` to compute the
adjacency matrix based on your 2D sensor locations.
Note that depending on your use case, you may need to additionally use
:func:`mne.stats.combine_adjacency` to prepare a final "adjacency"
to pass to the eventual function.
Valid ``fname`` arguments are:
.. table::
:widths: auto
+----------------------+
| fname |
+======================+
| biosemi16 |
+----------------------+
| biosemi32 |
+----------------------+
| biosemi64 |
+----------------------+
| bti148 |
+----------------------+
| bti248 |
+----------------------+
| bti248grad |
+----------------------+
| ctf64 |
+----------------------+
| ctf151 |
+----------------------+
| ctf275 |
+----------------------+
| easycap32ch-avg |
+----------------------+
| easycap64ch-avg |
+----------------------+
| easycap128ch-avg |
+----------------------+
| easycapM1 |
+----------------------+
| easycapM11 |
+----------------------+
| easycapM14 |
+----------------------+
| easycapM15 |
+----------------------+
| KIT-157 |
+----------------------+
| KIT-208 |
+----------------------+
| KIT-NYU-2019 |
+----------------------+
| KIT-UMD-1 |
+----------------------+
| KIT-UMD-2 |
+----------------------+
| KIT-UMD-3 |
+----------------------+
| KIT-UMD-4 |
+----------------------+
| neuromag306mag |
+----------------------+
| neuromag306planar |
+----------------------+
"""
from scipy.io import loadmat
if not op.isabs(fname):
templates_dir = op.realpath(op.join(op.dirname(__file__),
'data', 'neighbors'))
templates = os.listdir(templates_dir)
orig_fname = fname
valid_fnames = []
for f in templates:
if f == fname:
break
if f == fname + '_neighb.mat':
fname += '_neighb.mat'
break

# collect list of valid fnames
if f.endswith('_neighb.mat'):
valid_fnames.append(f.rstrip('_neighb.mat'))
else:
raise ValueError('I do not know about this neighbor '
'template: "{}"'.format(fname))
raise ValueError(f'"{orig_fname}" is not a valid ``fname``. '
f'Try one of:\n\n{valid_fnames}')

fname = op.join(templates_dir, fname)

Expand Down Expand Up @@ -1259,7 +1328,7 @@ def _ch_neighbor_adjacency(ch_names, neighbors):
Returns
-------
ch_adjacency : scipy.sparse matrix
ch_adjacency : scipy.sparse.spmatrix
The adjacency matrix.
"""
from scipy import sparse
Expand Down Expand Up @@ -1290,15 +1359,15 @@ def find_ch_adjacency(info, ch_type):
This function tries to infer the appropriate adjacency matrix template
for the given channels. If a template is not found, the adjacency matrix
is computed using Delaunay triangulation based on 2d sensor locations.
is computed using Delaunay triangulation based on 2D sensor locations.
Parameters
----------
%(info_not_none)s
ch_type : str | None
The channel type for computing the adjacency matrix. Currently
supports 'mag', 'grad', 'eeg' and None. If None, the info must contain
only one channel type.
supports ``'mag'``, ``'grad'``, ``'eeg'`` and ``None``.
If ``None``, the info must contain only one channel type.
Returns
-------
Expand All @@ -1309,8 +1378,9 @@ def find_ch_adjacency(info, ch_type):
See Also
--------
read_ch_adjacency
mne.viz.plot_ch_adjacency
read_ch_adjacency
mne.stats.combine_adjacency
Notes
-----
Expand All @@ -1321,6 +1391,10 @@ def find_ch_adjacency(info, ch_type):
is always computed for EEG data and never loaded from a template file. If
you want to load a template for a given montage use
:func:`read_ch_adjacency` directly.
Note that depending on your use case, you may need to additionally use
:func:`mne.stats.combine_adjacency` to prepare a final "adjacency"
to pass to the eventual function.
"""
if ch_type is None:
picks = channel_indices_by_type(info)
Expand Down Expand Up @@ -1364,7 +1438,7 @@ def find_ch_adjacency(info, ch_type):
conn_name = KIT_NEIGHBORS.get(info['kit_system_id'])

if conn_name is not None:
logger.info('Reading adjacency matrix for %s.' % conn_name)
logger.info(f'Reading adjacency matrix for {conn_name}.')
return read_ch_adjacency(conn_name)
logger.info('Could not find a adjacency matrix for the data. '
'Computing adjacency based on Delaunay triangulations.')
Expand All @@ -1384,7 +1458,7 @@ def _compute_ch_adjacency(info, ch_type):
Returns
-------
ch_adjacency : scipy.sparse matrix, shape (n_channels, n_channels)
ch_adjacency : scipy.sparse.csr_matrix, shape (n_channels, n_channels)
The adjacency matrix.
ch_names : list
The list of channel names present in adjacency matrix.
Expand Down
2 changes: 1 addition & 1 deletion mne/forward/forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def _block_diag(A, n):
The block size
Returns
-------
bd : sparse matrix
bd : scipy.sparse.spmatrix
The block diagonal matrix
"""
from scipy import sparse
Expand Down
2 changes: 1 addition & 1 deletion mne/io/fiff/tests/test_raw_fiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def test_fix_types():
(ctf_fname, False, [])
):
raw = read_raw_fif(fname)
raw.info["bads"] = bads
raw.info["bads"] = bads
mag_picks = pick_types(raw.info, meg='mag', exclude=[])
other_picks = np.setdiff1d(np.arange(len(raw.ch_names)), mag_picks)
# we don't actually have any files suffering from this problem, so
Expand Down
19 changes: 15 additions & 4 deletions mne/stats/_adjacency.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-

# Authors: Eric Larson <[email protected]>
# Stefan Appelhoff <[email protected]>
#
# License: Simplified BSD
# License: BSD-3-Clause

import numpy as np

Expand All @@ -18,16 +19,26 @@ def combine_adjacency(*structure):
*structure : list
The adjacency along each dimension. Each entry can be:
- ndarray or sparse matrix
- ndarray or scipy.sparse.spmatrix
A square binary adjacency matrix for the given dimension.
For example created by :func:`mne.channels.find_ch_adjacency`.
- int
The number of elements along the given dimension. A lattice
adjacency will be generated.
adjacency will be generated, which is a binary matrix
reflecting that element N of an array is adjacent to
elements at indices N - 1 and N + 1.
Returns
-------
adjacency : scipy.sparse.coo_matrix, shape (n_features, n_features)
The adjacency matrix.
The square adjacency matrix, where the shape ``n_features``
corresponds to the product of the length of all dimensions.
For example ``len(times) * len(freqs) * len(chans)``.
See Also
--------
mne.channels.find_ch_adjacency
mne.channels.read_ch_adjacency
Notes
-----
Expand Down
Loading

0 comments on commit a2f6fe7

Please sign in to comment.