Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standardized measurement error (SME) #12707

Merged
merged 14 commits into from
Jul 11, 2024
Prev Previous commit
Next Next commit
Move to mne.stats.erp module
  • Loading branch information
cbrnr committed Jul 11, 2024
commit 0bba0617e0c0bfe2e57b31f59fa82645a81d974b
2 changes: 1 addition & 1 deletion doc/changes/devel/12707.newfeature.rst
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Add :meth:`~mne.Epochs.compute_sme` to compute the analytical standardized measurement error (SME) as a data quality measure for ERP studies, by `Clemens Brunner`_.
Add :func:`~mne.stats.erp.compute_sme` to compute the analytical standardized measurement error (SME) as a data quality measure for ERP studies, by `Clemens Brunner`_.
43 changes: 0 additions & 43 deletions mne/epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,49 +1282,6 @@ def _evoked_from_epoch_data(self, data, info, picks, n_events, kind, comment):

return evoked

def compute_sme(self, start=None, stop=None):
"""Compute standardized measurement error (SME).
The standardized measurement error :footcite:`LuckEtAl2021` can be used as a
universal measure of data quality in ERP studies.
Parameters
----------
start : int | float | None
Start time (in s) of the time window used for SME computation. If ``None``,
use the start of the epoch.
stop : int | float | None
Stop time (in s) of the time window used for SME computation. If ``None``,
use the end of the epoch.
Returns
-------
sme : array, shape (n_channels,)
SME in given time window for each channel.
Notes
-----
Currently, only the mean value in the given time window is supported, meaning
that the resulting SME is only valid in studies which quantify the amplitude of
an ERP component as the mean within the time window (as opposed to e.g. the
peak, which would require bootstrapping).
References
----------
.. footbibliography::
"""
_validate_type(start, ("numeric", None), "start", "int or float")
_validate_type(stop, ("numeric", None), "stop", "int or float")
start = self.tmin if start is None else start
stop = self.tmax if stop is None else stop
if start < self.tmin:
raise ValueError("start is out of bounds.")
if stop > self.tmax:
raise ValueError("stop is out of bounds.")

data = self.get_data(tmin=start, tmax=stop)
return data.mean(axis=2).std(axis=0) / np.sqrt(data.shape[0])

@property
def ch_names(self):
"""Channel names."""
Expand Down
2 changes: 2 additions & 0 deletions mne/stats/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __all__ = [
"bonferroni_correction",
"bootstrap_confidence_interval",
"combine_adjacency",
"compute_sme",
cbrnr marked this conversation as resolved.
Show resolved Hide resolved
"f_mway_rm",
"f_oneway",
"f_threshold_mway_rm",
Expand All @@ -29,6 +30,7 @@ from .cluster_level import (
spatio_temporal_cluster_test,
summarize_clusters_stc,
)
from .erp import compute_sme
cbrnr marked this conversation as resolved.
Show resolved Hide resolved
from .multi_comp import bonferroni_correction, fdr_correction
from .parametric import (
_parametric_ci,
Expand Down
47 changes: 47 additions & 0 deletions mne/stats/erp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import numpy as np

from mne.utils import _validate_type


def compute_sme(epochs, start=None, stop=None):
"""Compute standardized measurement error (SME).
The standardized measurement error :footcite:`LuckEtAl2021` can be used as a
universal measure of data quality in ERP studies.
Parameters
----------
start : int | float | None
Start time (in s) of the time window used for SME computation. If ``None``, use
the start of the epoch.
stop : int | float | None
Stop time (in s) of the time window used for SME computation. If ``None``, use
the end of the epoch.
Returns
-------
sme : array, shape (n_channels,)
SME in given time window for each channel.
Notes
-----
Currently, only the mean value in the given time window is supported, meaning that
the resulting SME is only valid in studies which quantify the amplitude of an ERP
component as the mean within the time window (as opposed to e.g. the peak, which
would require bootstrapping).
References
----------
.. footbibliography::
"""
_validate_type(start, ("numeric", None), "start", "int or float")
_validate_type(stop, ("numeric", None), "stop", "int or float")
start = epochs.tmin if start is None else start
stop = epochs.tmax if stop is None else stop
if start < epochs.tmin:
raise ValueError("start is out of bounds.")
if stop > epochs.tmax:
raise ValueError("stop is out of bounds.")

data = epochs.get_data(tmin=start, tmax=stop)
return data.mean(axis=2).std(axis=0) / np.sqrt(data.shape[0])
11 changes: 6 additions & 5 deletions mne/tests/test_epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
from mne.event import merge_events
from mne.io import RawArray, read_raw_fif
from mne.preprocessing import maxwell_filter
from mne.stats.erp import compute_sme
from mne.utils import (
_dt_to_stamp,
_record_warnings,
Expand Down Expand Up @@ -5269,14 +5270,14 @@ def test_epochs_sme():
"""Test SME computation."""
raw, events, _ = _get_data()
epochs = Epochs(raw, events)
sme = epochs.compute_sme(start=0, stop=0.1)
sme = compute_sme(epochs, start=0, stop=0.1)
assert sme.shape == (376,)

with pytest.raises(TypeError, match="int or float"):
epochs.compute_sme("0", 0.1)
compute_sme(epochs, "0", 0.1)
with pytest.raises(TypeError, match="int or float"):
epochs.compute_sme(0, "0.1")
compute_sme(epochs, 0, "0.1")
with pytest.raises(ValueError, match="out of bounds"):
epochs.compute_sme(-1.2, 0.3)
compute_sme(epochs, -1.2, 0.3)
with pytest.raises(ValueError, match="out of bounds"):
epochs.compute_sme(-0.1, 0.8)
compute_sme(epochs, -0.1, 0.8)