Skip to content

np.nan in mne.stats.permutation_t_test fails silently #13767

Description

@skjerns

I've recently encountered a situation where I had a np.nan in the data I was running a permutation_t_test on. This failed (expectedly), however, it did not give me any warning/error! I only realized because a pvalue=0 seemed a bit suspicious in my plot

I think that it should at least print a warning, similar to how it give a RuntimeWarning when one test's observations are all zero (probably due to underlying numpy warnings.)

I assume this is the same for other functions in the stats module. If this is something that should be improved, probably best to do it systematically for all functions?

import mne
import numpy as np

x = np.random.rand(20, 20)
x[:, 0] = np.nan
t_obs, pvals, h0 = mne.stats.permutation_t_test(x)
# Permuting 9999 times...
# no warning printed! But all h0 are np.nan

# when one test is 0 for all observations, there is a warning
x = np.random.rand(20, 20)
x[:, 0] = 0
t_obs, pvals, h0 =  mne.stats.permutation_t_test(x)
# Permuting 9999 times...
# /home/simon/anaconda3/envs/default/lib/python3.14/site-packages/mne/stats/permutations.py:20: RuntimeWarning: invalid value encountered in divide
#  max_abs = np.max(np.abs(mus) / (stds / sqrt(n_samples)), axis=1)  # t-max

Activity

  1. changed the title [-]np.nan in mne.stats.permutation_t_test[/-] [+]np.nan in mne.stats.permutation_t_test fails silently[/+] on Mar 18, 2026
  2. larsoner commented on Mar 18, 2026

    @larsoner
    Member

    Yeah we probably want a np.isfinite(x).all() check in there. Probably lots of functions in MNE could benefit from this but to keep scope and review manageable but still extensible I think a def _validate_data(data): in mne/utils somewhere that mne/stats uses could make sense. (I like using a private function here in case we eventually want to add other validations like dim or shape or whatever we can with new kwargs, and for now just have it return np.isfinite(data).all()...)

  3. skjerns commented on Mar 19, 2026

    @skjerns
    ContributorAuthor

    Do you want me to do a PR?

  4. larsoner commented on Mar 20, 2026

    @larsoner
    Member

    Sure that would be great!

  5. skjerns commented on Mar 23, 2026

    @skjerns
    ContributorAuthor

    Yeah we probably want a np.isfinite(x).all() check in there. Probably lots of functions in MNE could benefit from this but to keep scope and review manageable but still extensible I think a def _validate_data(data): in mne/utils somewhere that mne/stats uses could make sense. (I like using a private function here in case we eventually want to add other validations like dim or shape or whatever we can with new kwargs, and for now just have it return np.isfinite(data).all()...)

    actually this function already exists! However it raises a ValueError instead of printing a RuntimeWarning

    def _check_if_nan(data, msg=" to be plotted"):
        """Raise if any of the values are NaN."""
        if not np.isfinite(data).all():
            raise ValueError(f"Some of the values {msg} are NaN.")

    Should I extend that function or create a new _validate_data that calls that and acts as a basis for future checks?

  6. drammock commented on Mar 23, 2026

    @drammock
    Member

    Should I extend that function or create a new _validate_data that calls that and acts as a basis for future checks?

    extend it with an on_nan or on_present or similarly-named param, which takes values "error", "warn" (usually we do error|warn|ignore, but I don't see the point of "ignore" in this case)

  7. drammock commented on Mar 23, 2026

    @drammock
    Member

    default should be "error" so you avoid needing to pass the param in all the existing places where it's already in use

  8. JiwaniZakir commented on Apr 17, 2026

    @JiwaniZakir

    The silent failure stems from how NumPy handles NaN propagation: arithmetic on NaN values (e.g., NaN + x, NaN * x) silently produces NaN without triggering any RuntimeWarning, whereas the zero-column case happens to produce a 0/0 division at line 20 of mne/stats/permutations.py (np.abs(mus) / (stds / sqrt(n_samples))), which does trip numpy's invalid-value warning. Since there's no explicit NaN guard at the entry point of permutation_t_test (or the other stats functions), the NaN propagates all the way through the permutation loop and into h0, ultimately making every p-value 0 or 1 depending on how the comparison against the NaN distribution resolves — a completely misleading result. Adding a np.isnan(X).any() check with an explicit ValueError or at minimum a warnings.warn(... RuntimeWarning) at the top of the affected functions (likely a shared utility so it covers the whole module) would catch this before any computation begins.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions