Skip to content

Commit 0efd8ad

Browse files
committed
unit test
1 parent 484fd3d commit 0efd8ad

2 files changed

Lines changed: 76 additions & 57 deletions

File tree

‎meegkit/utils/stats.py‎

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,25 @@
1111
mne = None
1212

1313

14-
def rms(x, axis=0):
14+
def rms(X, axis=0):
1515
"""Root-mean-square along given axis."""
16-
return np.sqrt(np.mean(x ** 2, axis=axis, keepdims=True))
16+
return np.sqrt(np.mean(X ** 2, axis=axis, keepdims=True))
1717

1818

19-
def robust_mean(x, axis=0, percentile=[5, 95]):
19+
def robust_mean(X, axis=0, percentile=[5, 95]):
2020
"""Do robust mean based on JR Kings implementation."""
21-
x = np.array(x)
21+
X = np.array(X)
2222
axis_ = axis
2323
# force axis to be 0 for facilitation
2424
if axis is not None and axis != 0:
25-
x = np.transpose(x, [axis] + range(0, axis) + range(axis + 1, x.ndim))
25+
X = np.transpose(X, [axis] + range(0, axis) + range(axis + 1, X.ndim))
2626
axis_ = 0
27-
mM = np.percentile(x, percentile, axis=axis_)
28-
indices_min = np.where((x - mM[0][np.newaxis, ...]) < 0)
29-
indices_max = np.where((x - mM[1][np.newaxis, ...]) > 0)
30-
x[indices_min] = np.nan
31-
x[indices_max] = np.nan
32-
m = np.nanmean(x, axis=axis_)
27+
mM = np.percentile(X, percentile, axis=axis_)
28+
indices_min = np.where((X - mM[0][np.newaxis, ...]) < 0)
29+
indices_max = np.where((X - mM[1][np.newaxis, ...]) > 0)
30+
X[indices_min] = np.nan
31+
X[indices_max] = np.nan
32+
m = np.nanmean(X, axis=axis_)
3333
return m
3434

3535

@@ -254,14 +254,14 @@ def cronbach(epochs, K=None, n_bootstrap=2000, tmin=None, tmax=None):
254254
Lower and higher bound of CI.
255255
256256
"""
257-
if isinstance(epochs, mne.BaseEpochs):
258-
erp = epochs.get_data()
259-
tmin = epochs.time_as_index(tmin)[0] if tmin else 0
260-
tmax = epochs.time_as_index(tmax)[0] if tmax else -1
261-
elif isinstance(epochs, np.ndarray):
257+
if isinstance(epochs, np.ndarray):
262258
erp = epochs
263259
tmin = tmin if tmin else 0
264260
tmax = tmax if tmax else -1
261+
elif isinstance(epochs, mne.BaseEpochs):
262+
erp = epochs.get_data()
263+
tmin = epochs.time_as_index(tmin)[0] if tmin else 0
264+
tmax = epochs.time_as_index(tmax)[0] if tmax else -1
265265
else:
266266
raise ValueError("epochs must be an mne.Epochs or numpy array.")
267267

@@ -273,20 +273,20 @@ def cronbach(epochs, K=None, n_bootstrap=2000, tmin=None, tmax=None):
273273
for b in np.arange(n_bootstrap): # take K trials randomly
274274
idx = np.random.choice(range(n_trials), K)
275275
X = erp[idx, :, tmin:tmax]
276-
sigmaY = X.var(axis=2) # var over time
276+
sigmaY = X.var(axis=2).sum(0) # var over time
277277
sigmaX = X.sum(axis=0).var(-1) # var of average
278-
alpha[b] = K / (K - 1) * (1 - sigmaY.sum(0) / sigmaX)
278+
alpha[b] = K / (K - 1) * (1 - sigmaY / sigmaX)
279279

280280
ci_lo, ci_hi = np.percentile(alpha, (10, 90), axis=0)
281281
return alpha.mean(0), ci_lo, ci_hi
282282

283283

284-
def snr_spectrum(data, freqs, n_avg=1, n_harm=1, skipbins=1):
284+
def snr_spectrum(X, freqs, n_avg=1, n_harm=1, skipbins=1):
285285
"""Compute Signal-to-Noise-corrected spectrum.
286286
287287
Parameters
288288
----------
289-
data : ndarray , shape=(n_freqs, n_chans,[ n_trials,])
289+
X : ndarray , shape=(n_freqs, n_chans,[ n_trials,])
290290
One-sided power spectral density estimate, specified as a real-valued,
291291
nonnegative array. The power spectral density must be expressed in
292292
linear units, not decibels.
@@ -318,21 +318,21 @@ def snr_spectrum(data, freqs, n_avg=1, n_harm=1, skipbins=1):
318318
natural face images in the infant right hemisphere. Elife, 4.
319319
320320
"""
321-
if data.ndim == 3:
322-
n_freqs = data.shape[0]
323-
n_chans = data.shape[1]
324-
n_trials = data.shape[-1]
325-
elif data.ndim == 2:
321+
if X.ndim == 3:
322+
n_freqs = X.shape[0]
323+
n_chans = X.shape[1]
324+
n_trials = X.shape[-1]
325+
elif X.ndim == 2:
326326
n_trials = 1
327-
n_freqs = data.shape[0]
328-
n_chans = data.shape[1]
327+
n_freqs = X.shape[0]
328+
n_chans = X.shape[1]
329329
else:
330330
raise ValueError('Data must have shape (n_freqs, n_chans, [n_trials,])'
331-
f', got {data.shape}')
331+
f', got {X.shape}')
332332

333333
# Number of points to get desired resolution
334-
data = np.reshape(data, (n_freqs, n_chans * n_trials))
335-
SNR = np.zeros_like(data)
334+
X = np.reshape(X, (n_freqs, n_chans * n_trials))
335+
SNR = np.zeros_like(X)
336336

337337
for i_bin in range(n_freqs):
338338

@@ -375,12 +375,12 @@ def snr_spectrum(data, freqs, n_avg=1, n_harm=1, skipbins=1):
375375
for i_trial in range(n_chans * n_trials):
376376

377377
# Mean of signal over fundamental+harmonics
378-
A = np.mean(data[bin_peaks, i_trial] ** 2)
378+
A = np.mean(X[bin_peaks, i_trial] ** 2)
379379

380380
# Noise around fundamental+harmonics
381381
B = np.zeros(len(bin_noise))
382382
for h in range(len(B)):
383-
B[h] = np.mean(data[bin_noise[h], i_trial].flatten() ** 2)
383+
B[h] = np.mean(X[bin_noise[h], i_trial].flatten() ** 2)
384384

385385
# Ratio
386386
with np.errstate(divide='ignore', invalid='ignore'):

‎tests/test_utils.py‎

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,33 @@
22
from meegkit.utils import (bootstrap_ci, demean, find_outlier_samples,
33
find_outlier_trials, fold, mean_over_trials,
44
multishift, multismooth, relshift, rms, shift,
5-
shiftnd, unfold, widen_mask)
5+
shiftnd, unfold, widen_mask, cronbach, robust_mean)
66
from numpy.testing import assert_almost_equal, assert_equal
77

88

9+
def _sim_data(n_times, n_chans, n_trials, noise_dim, SNR=1, t0=100):
10+
"""Create synthetic data."""
11+
# source
12+
source = np.sin(2 * np.pi * np.linspace(0, .5, n_times - t0))[np.newaxis].T
13+
s = source * np.random.randn(1, n_chans)
14+
s = s[:, :, np.newaxis]
15+
s = np.tile(s, (1, 1, n_trials))
16+
signal = np.zeros((n_times, n_chans, n_trials))
17+
signal[t0:, :, :] = s
18+
19+
# noise
20+
noise = np.dot(
21+
unfold(np.random.randn(n_times, noise_dim, n_trials)),
22+
np.random.randn(noise_dim, n_chans))
23+
noise = fold(noise, n_times)
24+
25+
# mix signal and noise
26+
signal = SNR * signal / rms(signal.flatten())
27+
noise = noise / rms(noise.flatten())
28+
noisy_data = signal + noise
29+
return noisy_data, signal
30+
31+
932
def test_multishift():
1033
"""Test matrix multi-shifting."""
1134
# multishift()
@@ -119,7 +142,7 @@ def test_demean(show=False):
119142
n_chans = 8
120143
n_times = 1000
121144
x = np.random.randn(n_times, n_chans, n_trials)
122-
x, s = _stim_data(n_times, n_chans, n_trials, 8, SNR=10)
145+
x, s = _sim_data(n_times, n_chans, n_trials, 8, SNR=10)
123146

124147
# 1. demean and check trial average is almost zero
125148
x1 = demean(x)
@@ -169,29 +192,6 @@ def test_demean(show=False):
169192
np.testing.assert_array_equal(x1, x3)
170193

171194

172-
def _stim_data(n_times, n_chans, n_trials, noise_dim, SNR=1, t0=100):
173-
"""Create synthetic data."""
174-
# source
175-
source = np.sin(2 * np.pi * np.linspace(0, .5, n_times - t0))[np.newaxis].T
176-
s = source * np.random.randn(1, n_chans)
177-
s = s[:, :, np.newaxis]
178-
s = np.tile(s, (1, 1, n_trials))
179-
signal = np.zeros((n_times, n_chans, n_trials))
180-
signal[t0:, :, :] = s
181-
182-
# noise
183-
noise = np.dot(
184-
unfold(np.random.randn(n_times, noise_dim, n_trials)),
185-
np.random.randn(noise_dim, n_chans))
186-
noise = fold(noise, n_times)
187-
188-
# mix signal and noise
189-
signal = SNR * signal / rms(signal.flatten())
190-
noise = noise / rms(noise.flatten())
191-
noisy_data = signal + noise
192-
return noisy_data, signal
193-
194-
195195
def test_computeci():
196196
"""Compute CI."""
197197
x = np.random.randn(1000, 8, 100)
@@ -231,6 +231,23 @@ def test_outliers(show=False):
231231
assert idx.shape == x.shape
232232

233233

234+
def test_cronbach():
235+
"""Test Cronbach's alpha."""
236+
X, _ = _sim_data(800, 8, 80, noise_dim=6, SNR=.2)
237+
X = X.transpose([2, 1, 0]) # trials, channels, samples
238+
alpha, lo, hi = cronbach(X, tmin=0, n_bootstrap=100)
239+
print(alpha)
240+
assert np.all(lo < hi)
241+
242+
X, _ = _sim_data(800, 8, 80, noise_dim=6, SNR=1)
243+
X = X.transpose([2, 1, 0])
244+
alpha2, lo, hi = cronbach(X, tmin=100, n_bootstrap=100)
245+
print(alpha2)
246+
assert np.sum(alpha2 > alpha) >= 6
247+
248+
m = robust_mean(X, axis=0)
249+
assert m.shape == (X.shape[1], X.shape[2])
250+
234251
if __name__ == '__main__':
235252
import pytest
236253
pytest.main([__file__])
@@ -241,3 +258,5 @@ def test_outliers(show=False):
241258
# y = multismooth(x, np.arange(1, 200, 4))
242259
# plt.imshow(y.T, aspect='auto')
243260
# plt.show()
261+
262+
# test_cronbach()

0 commit comments

Comments
 (0)