|
11 | 11 | from numpy.testing import assert_allclose, assert_array_almost_equal, assert_array_less |
12 | 12 |
|
13 | 13 | import mne |
14 | | -from mne.stats.parametric import _map_effects, f_mway_rm, f_threshold_mway_rm |
| 14 | +from mne.stats.parametric import _map_effects, f_mway_rm, f_oneway, f_threshold_mway_rm |
15 | 15 |
|
16 | 16 | # hardcoded external test results, manually transferred |
17 | 17 | test_external = { |
@@ -175,3 +175,53 @@ def theirs(*a, **kw): |
175 | 175 | # something to the divisor (var) |
176 | 176 | assert_allclose(got, want, rtol=2e-1, atol=1e-2) |
177 | 177 | assert_array_less(np.abs(got), np.abs(want)) |
| 178 | + |
| 179 | + |
| 180 | +@pytest.mark.parametrize("sigma", [0.0, 1e-3]) |
| 181 | +@pytest.mark.parametrize("method", ["absolute", "relative"]) |
| 182 | +@pytest.mark.parametrize("seed", [0, 42, 1337]) |
| 183 | +def test_f_oneway_hat(sigma, method, seed): |
| 184 | + """Test f_oneway hat (low-variance) regularization.""" |
| 185 | + rng = np.random.default_rng(seed) |
| 186 | + X1 = rng.standard_normal(size=(10, 50)) |
| 187 | + X2 = rng.standard_normal(size=(10, 50)) |
| 188 | + |
| 189 | + f_ours = f_oneway(X1, X2, sigma=0.0, method=method) |
| 190 | + f_scipy = scipy.stats.f_oneway(X1, X2)[0] |
| 191 | + assert_allclose(f_ours, f_scipy, rtol=1e-7, atol=1e-6) |
| 192 | + |
| 193 | + if sigma > 0: |
| 194 | + f_reg = f_oneway(X1, X2, sigma=sigma, method=method) |
| 195 | + f_unreg = f_oneway(X1, X2, sigma=0.0) |
| 196 | + pos = f_unreg > 0 |
| 197 | + assert_array_less(f_reg[pos], f_unreg[pos]) |
| 198 | + |
| 199 | + |
| 200 | +def test_f_oneway_hat_small_variance(): |
| 201 | + """Test that f_oneway hat stabilizes F-values for near-zero variance.""" |
| 202 | + rng = np.random.RandomState(0) |
| 203 | + X1 = rng.normal(0, 1e-6, (10, 100)) |
| 204 | + X2 = rng.normal(1, 1e-6, (10, 100)) |
| 205 | + |
| 206 | + f_unreg = f_oneway(X1, X2, sigma=0.0) |
| 207 | + f_abs = f_oneway(X1, X2, sigma=1e-3, method="absolute") |
| 208 | + f_rel = f_oneway(X1, X2, sigma=1e-3, method="relative") |
| 209 | + |
| 210 | + assert np.median(f_unreg) > 1e6 |
| 211 | + assert np.median(f_abs) < np.median(f_unreg) |
| 212 | + assert np.median(f_rel) < np.median(f_unreg) |
| 213 | + assert np.all(np.isfinite(f_abs)) |
| 214 | + assert np.all(np.isfinite(f_rel)) |
| 215 | + |
| 216 | + |
| 217 | +def test_f_oneway_hat_input_validation(): |
| 218 | + """Test f_oneway input validation for sigma and method.""" |
| 219 | + rng = np.random.RandomState(0) |
| 220 | + X1 = rng.randn(5, 10) |
| 221 | + X2 = rng.randn(5, 10) |
| 222 | + |
| 223 | + with pytest.raises(ValueError, match="sigma must be >= 0"): |
| 224 | + f_oneway(X1, X2, sigma=-0.1) |
| 225 | + |
| 226 | + with pytest.raises(ValueError, match="method"): |
| 227 | + f_oneway(X1, X2, sigma=1e-3, method="invalid") |
0 commit comments