I've been picking away at refactoring the NoisyChannels unit tests so that each 'bad-by' type is covered by a separate test. In the process, though, I think I've discovered a weak spot in the original PREP logic: if I use a different test file, the 'bad-by-low-deviation test' below stops working:
|
# Test for high and low deviations in EEG data |
|
raw_tmp = raw.copy() |
|
m, n = raw_tmp._data.shape |
|
# Now insert one random channel with very low deviations |
|
rand_chn_idx = int(rng.randint(0, m, 1)) |
|
rand_chn_lab = raw_tmp.ch_names[rand_chn_idx] |
|
raw_tmp._data[rand_chn_idx, :] = raw_tmp._data[rand_chn_idx, :] / 10 |
|
nd = NoisyChannels(raw_tmp, random_state=rng) |
|
nd.find_bad_by_deviation() |
|
assert rand_chn_lab in nd.bad_by_deviation |
This holds true even if I replace the "divide by 10" with a "divide by 100,000": the "robust channel deviation" z-score seems to plateau at around -4, never reaching the +/- 5 threshold for being bad by deviation.
Looking at the actual math, this makes sense:
- PREP calculates the variance within each channel using 0.7413 * the interquartile range for the signal.
- PREP calculates the median and variance (again, 0.7413 * the IQR) of those values and uses them to do a non-traditional Z-score of the channel variances (i.e.,
(variance - median_variance) / variance_of_the_variance).
- PREP compares the absolute values of those Z-scores to a threshold (default = 5) and flags any channels that exceed it as "bad-by-deviation"
The problem here is that in step 2, the variances calculated in step 1 have a minimum value of zero. As such, even a channel with an amplitude of zero won't be detected as bad-by-deviation if the median isn't at least 5x the variance of the variances.
A quick-and-dirty fix is to z-score the log of the channel variances, which makes the variances more normally-distributed and thus makes the detection of low-amplitude channels much easier. However, this also increases the threshold for detecting high-amplitude bad-by-deviation channels, with the required multiplication factor going from 2.4 to 3.9 for my test channel.
Any ideas on how to better handle this?
I've been picking away at refactoring the
NoisyChannelsunit tests so that each 'bad-by' type is covered by a separate test. In the process, though, I think I've discovered a weak spot in the original PREP logic: if I use a different test file, the 'bad-by-low-deviation test' below stops working:pyprep/tests/test_find_noisy_channels.py
Lines 51 to 60 in bc46978
This holds true even if I replace the "divide by 10" with a "divide by 100,000": the "robust channel deviation" z-score seems to plateau at around -4, never reaching the +/- 5 threshold for being bad by deviation.
Looking at the actual math, this makes sense:
(variance - median_variance) / variance_of_the_variance).The problem here is that in step 2, the variances calculated in step 1 have a minimum value of zero. As such, even a channel with an amplitude of zero won't be detected as bad-by-deviation if the median isn't at least 5x the variance of the variances.
A quick-and-dirty fix is to z-score the log of the channel variances, which makes the variances more normally-distributed and thus makes the detection of low-amplitude channels much easier. However, this also increases the threshold for detecting high-amplitude bad-by-deviation channels, with the required multiplication factor going from 2.4 to 3.9 for my test channel.
Any ideas on how to better handle this?