Skip to content

Commit 091515b

Browse files
committed
make ClusterResult.stat_obs shape (n_channels, n_times)
1 parent ba12c9b commit 091515b

2 files changed

Lines changed: 19 additions & 9 deletions

File tree

mne/stats/cluster_level.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1983,6 +1983,7 @@ def cluster_test(
19831983
iv_names = iv_name.split(":")
19841984
groups = df[[dv_name, *iv_names, within_id]].groupby([*iv_names, within_id])
19851985
elem = df[dv_name].iloc[0]
1986+
# TODO: Support this for other input types e.g. array, epochs, TFR, etc.
19861987
if isinstance(elem, Evoked):
19871988
reduce = set(df.columns) - set([*iv_names, within_id, dv_name])
19881989
if reduce:
@@ -2105,6 +2106,14 @@ def func_mne(series):
21052106
within_subject=kind == "within_rm",
21062107
)
21072108

2109+
stat_obs = stat_obs.T
2110+
if out_type == "mask":
2111+
if isinstance(clusters[0], np.ndarray) and clusters[0].dtype == "bool":
2112+
clusters = [cl.T for cl in clusters]
2113+
elif isinstance(clusters[0], tuple) and isinstance(clusters[0][0], slice):
2114+
clusters = [tuple(reversed(cluster)) for cluster in clusters]
2115+
elif out_type == "indices":
2116+
clusters = [tuple(reversed(cluster)) for cluster in clusters]
21082117
return ClusterResult(
21092118
stat_obs=stat_obs,
21102119
clusters=clusters,

mne/stats/tests/test_cluster_level_modern.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,13 @@ def test_new_cluster_api(Inst):
117117
df = pd.DataFrame(dict(data=insts, condition=conds))
118118
kwargs = dict(n_permutations=100, rng=42, tail=1, buffer_size=None, out_type="mask")
119119
result_new_api = cluster_test(df, "data~condition", **kwargs)
120-
121120
# make sure channels are last dimension for old API
122121
if is_epo:
122+
assert result_new_api.stat_obs.shape == df["data"][0].get_data()[0, ...].shape
123123
axes = (0, 3, 2, 1) if is_tfr else (0, 2, 1)
124124
X = [cond_a.get_data().transpose(*axes), cond_b.get_data().transpose(*axes)]
125125
else:
126+
assert result_new_api.stat_obs.shape == df["data"][0].get_data().shape
126127
axes = (2, 1, 0) if is_tfr else (1, 0)
127128
Xa = list()
128129
Xb = list()
@@ -133,11 +134,11 @@ def test_new_cluster_api(Inst):
133134

134135
F_obs, clusters, cluster_pvals, H0 = permutation_cluster_test(X, **kwargs)
135136
assert_array_almost_equal(result_new_api.H0, H0)
136-
assert_array_almost_equal(result_new_api.stat_obs, F_obs)
137+
assert_array_almost_equal(result_new_api.stat_obs, F_obs.T)
137138
assert_array_almost_equal(result_new_api.cluster_p_values, cluster_pvals)
138139
assert len(result_new_api.clusters) == len(clusters)
139140
for clu1, clu2 in zip(result_new_api.clusters, clusters):
140-
assert_array_equal(clu1, clu2)
141+
assert_array_equal(clu1, clu2.T)
141142

142143

143144
@pytest.mark.filterwarnings('ignore:Ignoring argument "tail":RuntimeWarning')
@@ -177,7 +178,7 @@ def stat_fun(*args):
177178
tail=1,
178179
rng=3,
179180
buffer_size=None,
180-
out_type="mask",
181+
out_type="indices",
181182
threshold=f_thresh,
182183
)
183184
F_obs, clusters, cluster_pvals, H0 = permutation_cluster_test(
@@ -201,10 +202,10 @@ def stat_fun(*args):
201202
result = cluster_test(df, "data ~ modality:location", within_id="subject", **kwargs)
202203

203204
assert result.stat_name == "F-statistic (repeated-measures ANOVA)"
204-
assert_array_almost_equal(result.stat_obs, F_obs)
205+
assert_array_almost_equal(result.stat_obs, F_obs.T)
205206
assert len(result.clusters) == len(clusters)
206207
for clu1, clu2 in zip(result.clusters, clusters):
207-
assert_array_equal(clu1, clu2)
208+
assert_array_equal(clu1, tuple(reversed(clu2)))
208209
# the observed stat and clusters match the legacy API, but the null differs
209210
# by design: cluster_test permutes repeated-measures data within subject
210211
# only, whereas the legacy API shuffles rows across the whole design
@@ -249,20 +250,20 @@ def test_cluster_test_formula_validation(stat_conditions):
249250
@pytest.mark.filterwarnings("ignore:No clusters found:RuntimeWarning")
250251
def test_cluster_test_reduce(stat_conditions):
251252
"""Reduce multiple observations for paired t-test."""
252-
import mne
253+
# TODO: parametrize this test for Epochs, AveragedTFR etc.
253254

254255
condition1_1d, _, _, _ = stat_conditions
255256
# For this test we need equal sized arrays
256257
condition2_1d = condition1_1d.copy()
257258
rng = np.random.default_rng(0)
258259
rng.shuffle(condition2_1d)
259260

260-
info = mne.create_info(
261+
info = create_info(
261262
ch_names=[f"ch_{ii}" for ii in range(condition1_1d.shape[0])],
262263
sfreq=10,
263264
ch_types="eeg",
264265
)
265-
data = [mne.EvokedArray(arr, info) for arr in [condition1_1d, condition2_1d]]
266+
data = [EvokedArray(arr, info) for arr in [condition1_1d, condition2_1d]]
266267
df = pd.DataFrame(dict(data=data, a=["x", "y"]))
267268
df["b"] = 1
268269

0 commit comments

Comments
 (0)