py-neuromodulation 0.0.4__py3-none-any.whl → 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -34
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -106
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -119
- py_neuromodulation/FieldTrip.py +589 -589
- py_neuromodulation/__init__.py +74 -13
- py_neuromodulation/_write_example_dataset_helper.py +83 -65
- py_neuromodulation/data/README +6 -6
- py_neuromodulation/data/dataset_description.json +8 -8
- py_neuromodulation/data/participants.json +32 -32
- py_neuromodulation/data/participants.tsv +2 -2
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
- py_neuromodulation/grid_cortex.tsv +40 -40
- py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
- py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
- py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
- py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/nm_IO.py +413 -417
- py_neuromodulation/nm_RMAP.py +496 -531
- py_neuromodulation/nm_analysis.py +993 -1074
- py_neuromodulation/nm_artifacts.py +30 -25
- py_neuromodulation/nm_bispectra.py +154 -168
- py_neuromodulation/nm_bursts.py +292 -198
- py_neuromodulation/nm_coherence.py +251 -205
- py_neuromodulation/nm_database.py +149 -0
- py_neuromodulation/nm_decode.py +918 -992
- py_neuromodulation/nm_define_nmchannels.py +300 -302
- py_neuromodulation/nm_features.py +144 -116
- py_neuromodulation/nm_filter.py +219 -219
- py_neuromodulation/nm_filter_preprocessing.py +79 -91
- py_neuromodulation/nm_fooof.py +139 -159
- py_neuromodulation/nm_generator.py +45 -37
- py_neuromodulation/nm_hjorth_raw.py +52 -73
- py_neuromodulation/nm_kalmanfilter.py +71 -58
- py_neuromodulation/nm_linelength.py +21 -33
- py_neuromodulation/nm_logger.py +66 -0
- py_neuromodulation/nm_mne_connectivity.py +149 -112
- py_neuromodulation/nm_mnelsl_generator.py +90 -0
- py_neuromodulation/nm_mnelsl_stream.py +116 -0
- py_neuromodulation/nm_nolds.py +96 -93
- py_neuromodulation/nm_normalization.py +173 -214
- py_neuromodulation/nm_oscillatory.py +423 -448
- py_neuromodulation/nm_plots.py +585 -612
- py_neuromodulation/nm_preprocessing.py +83 -0
- py_neuromodulation/nm_projection.py +370 -394
- py_neuromodulation/nm_rereference.py +97 -95
- py_neuromodulation/nm_resample.py +59 -50
- py_neuromodulation/nm_run_analysis.py +325 -435
- py_neuromodulation/nm_settings.py +289 -68
- py_neuromodulation/nm_settings.yaml +244 -0
- py_neuromodulation/nm_sharpwaves.py +423 -401
- py_neuromodulation/nm_stats.py +464 -480
- py_neuromodulation/nm_stream.py +398 -0
- py_neuromodulation/nm_stream_abc.py +166 -218
- py_neuromodulation/nm_types.py +193 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/METADATA +29 -26
- py_neuromodulation-0.0.5.dist-info/RECORD +83 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/WHEEL +1 -1
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/licenses/LICENSE +21 -21
- py_neuromodulation/nm_EpochStream.py +0 -92
- py_neuromodulation/nm_across_patient_decoding.py +0 -927
- py_neuromodulation/nm_cohortwrapper.py +0 -435
- py_neuromodulation/nm_eval_timing.py +0 -239
- py_neuromodulation/nm_features_abc.py +0 -39
- py_neuromodulation/nm_settings.json +0 -338
- py_neuromodulation/nm_stream_offline.py +0 -359
- py_neuromodulation/utils/_logging.py +0 -24
- py_neuromodulation-0.0.4.dist-info/RECORD +0 -72
py_neuromodulation/nm_stats.py
CHANGED
|
@@ -1,480 +1,464 @@
|
|
|
1
|
-
import random
|
|
2
|
-
import copy
|
|
3
|
-
|
|
4
|
-
import matplotlib.pyplot as plt
|
|
5
|
-
|
|
6
|
-
# from numba import njit
|
|
7
|
-
import numpy as np
|
|
8
|
-
import pandas as pd
|
|
9
|
-
import scipy.stats as stats
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if isinstance(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
p_val = len(np.where(pD
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
plt.
|
|
86
|
-
plt.
|
|
87
|
-
plt.
|
|
88
|
-
plt.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
p_val = len(np.where(pD
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
plt.
|
|
139
|
-
plt.
|
|
140
|
-
plt.
|
|
141
|
-
plt.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
l_.append((
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
plt.
|
|
185
|
-
plt.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
labels, num_clusters = cluster(p_arr <= p_sig)
|
|
466
|
-
|
|
467
|
-
null_distr = calculate_null_distribution(p_arr, p_sig, n_perm)
|
|
468
|
-
# Loop through clusters of p_val series or image
|
|
469
|
-
clusters = []
|
|
470
|
-
p_vals = [np.float64(x) for x in range(0)]
|
|
471
|
-
# Cluster labels start at 1
|
|
472
|
-
for cluster_i in range(num_clusters):
|
|
473
|
-
index_cluster = np.where(labels == cluster_i + 1)[0]
|
|
474
|
-
p_cluster_sum = np.sum(np.asarray(1 - p_arr)[index_cluster])
|
|
475
|
-
p_val = 1 - np.sum(p_cluster_sum >= null_distr) / n_perm
|
|
476
|
-
if p_val <= p_sig:
|
|
477
|
-
clusters.append(index_cluster)
|
|
478
|
-
p_vals.append(p_val)
|
|
479
|
-
|
|
480
|
-
return p_vals, clusters
|
|
1
|
+
import random
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
# from numba import njit
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import scipy.stats as stats
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def fitlm(x, y):
|
|
13
|
+
import statsmodels.api as sm
|
|
14
|
+
return sm.OLS(y, sm.add_constant(x)).fit()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fitlm_kfold(x, y, kfold_splits=5):
|
|
18
|
+
from sklearn.linear_model import LinearRegression
|
|
19
|
+
from sklearn.model_selection import KFold
|
|
20
|
+
|
|
21
|
+
model = LinearRegression()
|
|
22
|
+
if isinstance(x, type(np.array([]))) or isinstance(x, type([])):
|
|
23
|
+
x = pd.DataFrame(x)
|
|
24
|
+
if isinstance(y, type(np.array([]))) or isinstance(y, type([])):
|
|
25
|
+
y = pd.DataFrame(y)
|
|
26
|
+
scores, coeffs = [], np.zeros(x.shape[1])
|
|
27
|
+
kfold = KFold(n_splits=kfold_splits, shuffle=True, random_state=42)
|
|
28
|
+
for i, (train, test) in enumerate(kfold.split(x, y)):
|
|
29
|
+
model.fit(x.iloc[train, :], y.iloc[train, :])
|
|
30
|
+
score = model.score(x.iloc[test, :], y.iloc[test, :])
|
|
31
|
+
# mdl = fitlm(np.squeeze(y.iloc[test,:].transpose()), np.squeeze(model.predict(x.iloc[test, :])))
|
|
32
|
+
scores.append(score)
|
|
33
|
+
coeffs = np.vstack((coeffs, model.coef_))
|
|
34
|
+
coeffs = list(np.delete(coeffs, 0))
|
|
35
|
+
return scores, coeffs, model, ["scores", "coeffs", "model"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def zscore(data):
|
|
39
|
+
return (data - data.mean()) / data.std()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def permutationTestSpearmansRho(x, y, plot_distr=True, x_unit=None, p=5000):
|
|
43
|
+
"""
|
|
44
|
+
Calculate permutation test for multiple repetitions of Spearmans Rho
|
|
45
|
+
https://towardsdatascience.com/how-to-assess-statistical-significance-in-your-data-with-permutation-tests-8bb925b2113d
|
|
46
|
+
|
|
47
|
+
x (np array) : first distibution e.g. R^2
|
|
48
|
+
y (np array) : second distribution e.g. UPDRS
|
|
49
|
+
plot_distr (boolean) : if True: permutation histplot and ground truth will be
|
|
50
|
+
plotted
|
|
51
|
+
x_unit (str) : histplot xlabel
|
|
52
|
+
p (int): number of permutations
|
|
53
|
+
|
|
54
|
+
returns:
|
|
55
|
+
gT (float) : estimated ground truth, here spearman's rho
|
|
56
|
+
p (float) : p value of permutation test
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# compute ground truth difference
|
|
60
|
+
gT = stats.spearmanr(x, y)[0]
|
|
61
|
+
#
|
|
62
|
+
pV = np.array((x, y))
|
|
63
|
+
# Initialize permutation:
|
|
64
|
+
pD = []
|
|
65
|
+
# Permutation loop:
|
|
66
|
+
args_order = np.arange(0, pV.shape[1], 1)
|
|
67
|
+
args_order_2 = np.arange(0, pV.shape[1], 1)
|
|
68
|
+
for i in range(0, p):
|
|
69
|
+
# Shuffle the data:
|
|
70
|
+
random.shuffle(args_order)
|
|
71
|
+
random.shuffle(args_order_2)
|
|
72
|
+
# Compute permuted absolute difference of your two sampled
|
|
73
|
+
# distributions and store it in pD:
|
|
74
|
+
pD.append(stats.spearmanr(pV[0, args_order], pV[1, args_order_2])[0])
|
|
75
|
+
|
|
76
|
+
# calculate p value
|
|
77
|
+
if gT < 0:
|
|
78
|
+
p_val = len(np.where(pD <= gT)[0]) / p
|
|
79
|
+
else:
|
|
80
|
+
p_val = len(np.where(pD >= gT)[0]) / p
|
|
81
|
+
|
|
82
|
+
if plot_distr:
|
|
83
|
+
plt.hist(pD, bins=30, label="permutation results")
|
|
84
|
+
plt.axvline(gT, color="orange", label="ground truth")
|
|
85
|
+
plt.title("ground truth " + x_unit + "=" + str(gT) + " p=" + str(p_val))
|
|
86
|
+
plt.xlabel(x_unit)
|
|
87
|
+
plt.legend()
|
|
88
|
+
plt.show()
|
|
89
|
+
return gT, p_val
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def permutationTest(x, y, plot_distr=True, x_unit=None, p=5000):
|
|
93
|
+
"""
|
|
94
|
+
Calculate permutation test
|
|
95
|
+
https://towardsdatascience.com/how-to-assess-statistical-significance-in-your-data-with-permutation-tests-8bb925b2113d
|
|
96
|
+
|
|
97
|
+
x (np array) : first distr.
|
|
98
|
+
y (np array) : first distr.
|
|
99
|
+
plot_distr (boolean) : if True: plot permutation histplot and ground truth
|
|
100
|
+
x_unit (str) : histplot xlabel
|
|
101
|
+
p (int): number of permutations
|
|
102
|
+
|
|
103
|
+
returns:
|
|
104
|
+
gT (float) : estimated ground truth, here absolute difference of
|
|
105
|
+
distribution means
|
|
106
|
+
p (float) : p value of permutation test
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
# Compute ground truth difference
|
|
110
|
+
gT = np.abs(np.average(x) - np.average(y))
|
|
111
|
+
|
|
112
|
+
pV = np.concatenate((x, y), axis=0)
|
|
113
|
+
pS = copy.copy(pV)
|
|
114
|
+
# Initialize permutation:
|
|
115
|
+
pD = []
|
|
116
|
+
# Permutation loop:
|
|
117
|
+
for i in range(0, p):
|
|
118
|
+
# Shuffle the data:
|
|
119
|
+
random.shuffle(pS)
|
|
120
|
+
# Compute permuted absolute difference of your two sampled
|
|
121
|
+
# distributions and store it in pD:
|
|
122
|
+
pD.append(
|
|
123
|
+
np.abs(
|
|
124
|
+
np.average(pS[0 : int(len(pS) / 2)])
|
|
125
|
+
- np.average(pS[int(len(pS) / 2) :])
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Calculate p-value
|
|
130
|
+
if gT < 0:
|
|
131
|
+
p_val = len(np.where(pD <= gT)[0]) / p
|
|
132
|
+
else:
|
|
133
|
+
p_val = len(np.where(pD >= gT)[0]) / p
|
|
134
|
+
|
|
135
|
+
if plot_distr:
|
|
136
|
+
plt.hist(pD, bins=30, label="permutation results")
|
|
137
|
+
plt.axvline(gT, color="orange", label="ground truth")
|
|
138
|
+
plt.title("ground truth " + x_unit + "=" + str(gT) + " p=" + str(p_val))
|
|
139
|
+
plt.xlabel(x_unit)
|
|
140
|
+
plt.legend()
|
|
141
|
+
plt.show()
|
|
142
|
+
return gT, p_val
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def permutationTest_relative(x, y, plot_distr=True, x_unit=None, p=5000):
|
|
146
|
+
"""
|
|
147
|
+
Calculate permutation test
|
|
148
|
+
https://towardsdatascience.com/how-to-assess-statistical-significance-in-your-data-with-permutation-tests-8bb925b2113d
|
|
149
|
+
|
|
150
|
+
x (np array) : first distr.
|
|
151
|
+
y (np array) : first distr.
|
|
152
|
+
plot_distr (boolean) : if True: plot permutation histplot and ground truth
|
|
153
|
+
x_unit (str) : histplot xlabel
|
|
154
|
+
p (int): number of permutations
|
|
155
|
+
|
|
156
|
+
returns:
|
|
157
|
+
gT (float) : estimated ground truth, here absolute difference of
|
|
158
|
+
distribution means
|
|
159
|
+
p (float) : p value of permutation test
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
gT = np.abs(np.average(x) - np.average(y))
|
|
163
|
+
pD = []
|
|
164
|
+
for i in range(0, p):
|
|
165
|
+
l_ = []
|
|
166
|
+
for i in range(x.shape[0]):
|
|
167
|
+
if random.randint(0, 1) == 1:
|
|
168
|
+
l_.append((x[i], y[i]))
|
|
169
|
+
else:
|
|
170
|
+
l_.append((y[i], x[i]))
|
|
171
|
+
pD.append(
|
|
172
|
+
np.abs(np.average(np.array(l_)[:, 0]) - np.average(np.array(l_)[:, 1]))
|
|
173
|
+
)
|
|
174
|
+
if gT < 0:
|
|
175
|
+
p_val = len(np.where(pD <= gT)[0]) / p
|
|
176
|
+
else:
|
|
177
|
+
p_val = len(np.where(pD >= gT)[0]) / p
|
|
178
|
+
|
|
179
|
+
if plot_distr:
|
|
180
|
+
plt.hist(pD, bins=30, label="permutation results")
|
|
181
|
+
plt.axvline(gT, color="orange", label="ground truth")
|
|
182
|
+
plt.title("ground truth " + x_unit + "=" + str(gT) + " p=" + str(p_val))
|
|
183
|
+
plt.xlabel(x_unit)
|
|
184
|
+
plt.legend()
|
|
185
|
+
plt.show()
|
|
186
|
+
|
|
187
|
+
return gT, p_val
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# @njit
|
|
191
|
+
def permutation_numba_onesample(x, y, n_perm, two_tailed=True):
|
|
192
|
+
"""Perform permutation test with one-sample distribution.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
x : array_like
|
|
197
|
+
First distribution
|
|
198
|
+
y : int or float
|
|
199
|
+
Baseline against which to check for statistical significane
|
|
200
|
+
n_perm : int
|
|
201
|
+
Number of permutations
|
|
202
|
+
two_tailed : bool, default: True
|
|
203
|
+
Set to False if you would like to perform a one-sampled permutation
|
|
204
|
+
test, else True
|
|
205
|
+
two_tailed : bool, default: True
|
|
206
|
+
Set to False if you would like to perform a one-tailed permutation
|
|
207
|
+
test, else True
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
float
|
|
212
|
+
Estimated difference of distribution from baseline
|
|
213
|
+
float
|
|
214
|
+
P-value of permutation test
|
|
215
|
+
"""
|
|
216
|
+
if two_tailed:
|
|
217
|
+
zeroed = x - y
|
|
218
|
+
z = np.abs(np.mean(zeroed))
|
|
219
|
+
p = np.empty(n_perm)
|
|
220
|
+
# Run the simulation n_perm times
|
|
221
|
+
for i in np.arange(n_perm):
|
|
222
|
+
sign = np.random.choice(a=np.array([-1.0, 1.0]), size=len(x), replace=True)
|
|
223
|
+
p[i] = np.abs(np.mean(zeroed * sign))
|
|
224
|
+
else:
|
|
225
|
+
zeroed = x - y
|
|
226
|
+
z = np.mean(zeroed)
|
|
227
|
+
p = np.empty(n_perm)
|
|
228
|
+
# Run the simulation n_perm times
|
|
229
|
+
for i in np.arange(n_perm):
|
|
230
|
+
sign = np.random.choice(a=np.array([-1.0, 1.0]), size=len(x), replace=True)
|
|
231
|
+
p[i] = np.mean(zeroed * sign)
|
|
232
|
+
# Return p-value
|
|
233
|
+
return z, (np.sum(p >= z)) / n_perm
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# @njit
|
|
237
|
+
def permutation_numba_twosample(x, y, n_perm, two_tailed=True):
|
|
238
|
+
"""Perform permutation test.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
x : array_like
|
|
243
|
+
First distribution
|
|
244
|
+
y : array_like
|
|
245
|
+
Second distribution
|
|
246
|
+
n_perm : int
|
|
247
|
+
Number of permutations
|
|
248
|
+
two_tailed : bool, default: True
|
|
249
|
+
Set to False if you would like to perform a one-sampled permutation
|
|
250
|
+
test, else True
|
|
251
|
+
two_tailed : bool, default: True
|
|
252
|
+
Set to False if you would like to perform a one-tailed permutation
|
|
253
|
+
test, else True
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
float
|
|
258
|
+
Estimated difference of distribution means
|
|
259
|
+
float
|
|
260
|
+
P-value of permutation test
|
|
261
|
+
"""
|
|
262
|
+
if two_tailed:
|
|
263
|
+
z = np.abs(np.mean(x) - np.mean(y))
|
|
264
|
+
pS = np.concatenate((x, y), axis=0)
|
|
265
|
+
half = int(len(pS) / 2)
|
|
266
|
+
p = np.empty(n_perm)
|
|
267
|
+
# Run the simulation n_perm times
|
|
268
|
+
for i in np.arange(0, n_perm):
|
|
269
|
+
# Shuffle the data
|
|
270
|
+
np.random.shuffle(pS)
|
|
271
|
+
# Compute permuted absolute difference of the two sampled
|
|
272
|
+
# distributions
|
|
273
|
+
p[i] = np.abs(np.mean(pS[:half]) - np.mean(pS[half:]))
|
|
274
|
+
else:
|
|
275
|
+
z = np.mean(x) - np.mean(y)
|
|
276
|
+
pS = np.concatenate((x, y), axis=0)
|
|
277
|
+
half = int(len(pS) / 2)
|
|
278
|
+
p = np.empty(n_perm)
|
|
279
|
+
# Run the simulation n_perm times
|
|
280
|
+
for i in np.arange(0, n_perm):
|
|
281
|
+
# Shuffle the data
|
|
282
|
+
np.random.shuffle(pS)
|
|
283
|
+
# Compute permuted absolute difference of the two sampled
|
|
284
|
+
# distributions
|
|
285
|
+
p[i] = np.mean(pS[:half]) - np.mean(pS[half:])
|
|
286
|
+
return z, (np.sum(p >= z)) / n_perm
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def cluster_wise_p_val_correction(p_arr, p_sig=0.05, num_permutations=10000):
|
|
290
|
+
"""Obtain cluster-wise corrected p values.
|
|
291
|
+
|
|
292
|
+
Based on: https://github.com/neuromodulation/wjn_toolbox/blob/4745557040ad26f3b8498ca5d0c5d5dece2d3ba1/mypcluster.m
|
|
293
|
+
https://garstats.wordpress.com/2018/09/06/cluster/
|
|
294
|
+
|
|
295
|
+
Arguments
|
|
296
|
+
---------
|
|
297
|
+
p_arr (np.array) : ndim, can be time series or image
|
|
298
|
+
p_sig (float) : significance level
|
|
299
|
+
num_permutations (int) : no. of random permutations of cluster comparisons
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
p (float) : significance level of highest cluster
|
|
304
|
+
p_min_index : indices of significant samples
|
|
305
|
+
"""
|
|
306
|
+
from skimage.measure import label as measure_label
|
|
307
|
+
|
|
308
|
+
labels, num_clusters = measure_label(p_arr <= p_sig, return_num=True)
|
|
309
|
+
|
|
310
|
+
# loop through clusters of p_val series or image
|
|
311
|
+
index_cluster = {}
|
|
312
|
+
p_cluster_sum = np.zeros(num_clusters)
|
|
313
|
+
for cluster_i in np.arange(num_clusters):
|
|
314
|
+
# first cluster is assigned to be 1 from measure.label
|
|
315
|
+
index_cluster[cluster_i] = np.where(labels == cluster_i + 1)[0]
|
|
316
|
+
p_cluster_sum[cluster_i] = np.sum(np.array(1 - p_arr)[index_cluster[cluster_i]])
|
|
317
|
+
# p_min corresponds to the most unlikely cluster
|
|
318
|
+
p_min = np.max(p_cluster_sum)
|
|
319
|
+
|
|
320
|
+
p_min_index = index_cluster[np.argmax(p_cluster_sum)]
|
|
321
|
+
|
|
322
|
+
# loop through random permutation cycles
|
|
323
|
+
r_per_arr = np.zeros(num_permutations)
|
|
324
|
+
for r in range(num_permutations):
|
|
325
|
+
r_per = np.random.randint(low=0, high=p_arr.shape[0], size=p_arr.shape[0])
|
|
326
|
+
|
|
327
|
+
labels, num_clusters = measure_label(p_arr[r_per] <= p_sig, return_num=True)
|
|
328
|
+
|
|
329
|
+
index_cluster = {}
|
|
330
|
+
if num_clusters == 0:
|
|
331
|
+
r_per_arr[r] = 0
|
|
332
|
+
else:
|
|
333
|
+
p_cluster_sum = np.zeros(num_clusters)
|
|
334
|
+
for cluster_i in np.arange(num_clusters):
|
|
335
|
+
index_cluster[cluster_i] = np.where(labels == cluster_i + 1)[
|
|
336
|
+
0
|
|
337
|
+
] # first cluster is assigned to be 1 from measure.label
|
|
338
|
+
p_cluster_sum[cluster_i] = np.sum(
|
|
339
|
+
np.array(1 - p_arr[r_per])[index_cluster[cluster_i]]
|
|
340
|
+
)
|
|
341
|
+
# corresponds to the most unlikely cluster
|
|
342
|
+
r_per_arr[r] = np.max(p_cluster_sum)
|
|
343
|
+
|
|
344
|
+
sorted_r = np.sort(r_per_arr)
|
|
345
|
+
|
|
346
|
+
def find_arg_nearest(array, value):
|
|
347
|
+
array = np.asarray(array)
|
|
348
|
+
idx = (np.abs(array - value)).argmin()
|
|
349
|
+
return idx
|
|
350
|
+
|
|
351
|
+
p = 1 - find_arg_nearest(sorted_r, p_min) / num_permutations
|
|
352
|
+
|
|
353
|
+
return p, p_min_index
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# @njit
|
|
357
|
+
def cluster_wise_p_val_correction_numba(p_arr, p_sig, n_perm):
|
|
358
|
+
"""Calculate significant clusters and their corresponding p-values.
|
|
359
|
+
|
|
360
|
+
Based on:
|
|
361
|
+
https://github.com/neuromodulation/wjn_toolbox/blob/4745557040ad26f3b8498ca5d0c5d5dece2d3ba1/mypcluster.m
|
|
362
|
+
https://garstats.wordpress.com/2018/09/06/cluster/
|
|
363
|
+
|
|
364
|
+
Arguments
|
|
365
|
+
---------
|
|
366
|
+
p_arr : array-like
|
|
367
|
+
Array of p-values. WARNING: MUST be one-dimensional
|
|
368
|
+
p_sig : float
|
|
369
|
+
Significance level
|
|
370
|
+
n_perm : int
|
|
371
|
+
No. of random permutations for building cluster null-distribution
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
p : list of floats
|
|
376
|
+
List of p-values for each cluster
|
|
377
|
+
p_min_index : list of numpy array
|
|
378
|
+
List of indices of each significant cluster
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
def cluster(iterable):
|
|
382
|
+
"""Cluster 1-D array of boolean values.
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
iterable : array-like of bool
|
|
387
|
+
Array to be clustered.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
cluster_labels : np.ndarray
|
|
392
|
+
Array of shape (len(iterable), 1), where each value indicates the
|
|
393
|
+
number of the cluster. Values are 0 if the item does not belong to
|
|
394
|
+
a cluster
|
|
395
|
+
cluster_count : int
|
|
396
|
+
Number of detected cluster. Corresponds to the highest value in
|
|
397
|
+
cluster_labels
|
|
398
|
+
"""
|
|
399
|
+
cluster_labels = np.zeros((len(iterable), 1))
|
|
400
|
+
cluster_count = 0
|
|
401
|
+
cluster_len = 0
|
|
402
|
+
for idx, item in enumerate(iterable):
|
|
403
|
+
if item:
|
|
404
|
+
cluster_labels[idx] = cluster_count + 1
|
|
405
|
+
cluster_len += 1
|
|
406
|
+
elif cluster_len == 0:
|
|
407
|
+
pass
|
|
408
|
+
else:
|
|
409
|
+
cluster_len = 0
|
|
410
|
+
cluster_count += 1
|
|
411
|
+
if cluster_len >= 1:
|
|
412
|
+
cluster_count += 1
|
|
413
|
+
return cluster_labels, cluster_count
|
|
414
|
+
|
|
415
|
+
def calculate_null_distribution(p_arr_, p_sig_, n_perm_):
|
|
416
|
+
"""Calculate null distribution of clusters.
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
p_arr_ : numpy array
|
|
421
|
+
Array of p-values
|
|
422
|
+
p_sig_ : float
|
|
423
|
+
Significance level (p-value)
|
|
424
|
+
n_perm_ : int
|
|
425
|
+
No. of random permutations
|
|
426
|
+
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
r_per_arr : numpy array
|
|
430
|
+
Null distribution of shape (n_perm_)
|
|
431
|
+
"""
|
|
432
|
+
# loop through random permutation cycles
|
|
433
|
+
r_per_arr = np.zeros(n_perm_)
|
|
434
|
+
for r in range(n_perm_):
|
|
435
|
+
r_per = np.random.randint(low=0, high=p_arr_.shape[0], size=p_arr_.shape[0])
|
|
436
|
+
labels_, n_clusters = cluster(p_arr_[r_per] <= p_sig_)
|
|
437
|
+
|
|
438
|
+
cluster_ind = {}
|
|
439
|
+
if n_clusters == 0:
|
|
440
|
+
r_per_arr[r] = 0
|
|
441
|
+
else:
|
|
442
|
+
p_sum = np.zeros(n_clusters)
|
|
443
|
+
for ind in range(n_clusters):
|
|
444
|
+
cluster_ind[ind] = np.where(labels_ == ind + 1)[0]
|
|
445
|
+
p_sum[ind] = np.sum(np.asarray(1 - p_arr_[r_per])[cluster_ind[ind]])
|
|
446
|
+
r_per_arr[r] = np.max(p_sum)
|
|
447
|
+
return r_per_arr
|
|
448
|
+
|
|
449
|
+
labels, num_clusters = cluster(p_arr <= p_sig)
|
|
450
|
+
|
|
451
|
+
null_distr = calculate_null_distribution(p_arr, p_sig, n_perm)
|
|
452
|
+
# Loop through clusters of p_val series or image
|
|
453
|
+
clusters = []
|
|
454
|
+
p_vals = [np.float64(x) for x in range(0)]
|
|
455
|
+
# Cluster labels start at 1
|
|
456
|
+
for cluster_i in range(num_clusters):
|
|
457
|
+
index_cluster = np.where(labels == cluster_i + 1)[0]
|
|
458
|
+
p_cluster_sum = np.sum(np.asarray(1 - p_arr)[index_cluster])
|
|
459
|
+
p_val = 1 - np.sum(p_cluster_sum >= null_distr) / n_perm
|
|
460
|
+
if p_val <= p_sig:
|
|
461
|
+
clusters.append(index_cluster)
|
|
462
|
+
p_vals.append(p_val)
|
|
463
|
+
|
|
464
|
+
return p_vals, clusters
|