OSCR

Complexity of resting cortical activity predicts neurophysiological responses to theta-burst stimulation but fails to generalize: A rigorous machine-learning approach.

Code ↔ Paper

8 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 8 matches
  1. [1] § Methods › rsEEG entropy features ↔ src/features/calc_Entropy.py, lines 113–155 · score 0.81 · Lempel Ziv complexity, incremental entropy, permutation entropy, distribution entropy
  2. [2] § Methods › Classifiers ↔ src/mdls/getMdlsMd.py, lines 58–159 · score 0.78 · Ledoit Wolf, linear discriminant, logistic regression, Euclidean, Gaussian, centroid
  3. [3] § Methods › Classifiers ↔ src/mdls/_NSC.py, lines 32–154 · score 0.75 · shrunken centroid, distance metric, class priors, Euclidean, nearest, Manhattan
  4. [4] § Methods › Baseline rsEEG band powers features ↔ src/features/getSpecStatsMd.py, lines 551–645 · score 0.71 · Power spectral densities, band powers, EEG channel, beta, theta, delta
  5. [5] § Methods › Statistical tests for covariate shift and label shift ↔ src/pipelines/run_label_shift_analysis.py, lines 1–45 · score 0.57 · chi squared, shift, tailed, Fisher, ML, ratio
  6. [6] § Results › Cross-cohort experiment ↔ src/mdls/getMdlsMd.py, lines 600–666 · score 0.53 · LDA OA, linear discriminant, F1 score, shrinkage, metric, precision
  7. [7] § Methods › Reliability analysis ↔ src/pipelines/calc_Classification_Entropy_Bands_TStat_MEP.py, lines 1538–1608 · score 0.50 · left motor local, field power, band, visits, ratios, MEP
  8. [8] § Methods › Reliability analysis ↔ src/pipelines/calc_Classification_BP_Bands_Ratio.py, lines 1391–1453 · score 0.50 · left motor local, field power, band, ratios, visits

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 849 lines · 31 KB · BSD-3-Clause · 2 matches

  1. """
  2. Created on Jun 24, 2024
  3. @author: mning
  4. """
  5. """
  6. Created on May 22, 2023
  7. @author: mning
  8. """
  9. """
  10. Created on May 10, 2023
  11. @author: mning
  12. Note: all must accept same input arguments and yield same output types
  13. """
  14. import logging
  15. import numpy as np
  16. from sklearn.covariance import LedoitWolf
  17. from sklearn.covariance import OAS
  18. from sklearn.discriminant_analysis import (
  19. LinearDiscriminantAnalysis,
  20. )
  21. from sklearn.ensemble import AdaBoostClassifier
  22. from sklearn.ensemble import BaggingClassifier
  23. from sklearn.ensemble import ExtraTreesClassifier
  24. from sklearn.ensemble import GradientBoostingClassifier
  25. from sklearn.ensemble import RandomForestClassifier
  26. from sklearn.linear_model import (
  27. LogisticRegressionCV,
  28. )
  29. from sklearn.metrics import f1_score
  30. from sklearn.metrics import make_scorer
  31. from sklearn.metrics import roc_auc_score
  32. from sklearn.model_selection import GridSearchCV
  33. from sklearn.model_selection import RepeatedStratifiedKFold
  34. from sklearn.naive_bayes import GaussianNB
  35. from sklearn.neighbors import KNeighborsClassifier
  36. from sklearn.neighbors import (
  37. NearestCentroid,
  38. )
  39. from sklearn.neighbors import RadiusNeighborsClassifier
  40. from sklearn.svm import SVC
  41. from sklearn.tree import DecisionTreeClassifier
  42. from xgboost.sklearn import XGBClassifier
  43. from src.mdls import _NSC
  44. # import getMin
  45. # import getSTD
  46. logger = logging.getLogger("root")
  47. modelDict = dict
  48. def getMdlsFn(params: dict) -> modelDict:
  49. # params contain data-derived parameters
  50. if "groups" in params:
  51. params["groups"]
  52. modelList = {}
  53. # https://stats.stackexchange.com/questions/48786/algebra-of-lda-fisher-discrimination-power-of-a-variable-and-linear-discriminan/48859#48859
  54. # https://stats.stackexchange.com/questions/109747/why-is-pythons-scikit-learn-lda-not-working-correctly-and-how-does-it-compute-l/110697#110697
  55. # modelList.update({"LDA": LinearDiscriminantAnalysis(solver='svd',
  56. # shrinkage=None,
  57. # priors=[0.5,0.5])})
  58. #
  59. modelList.update(
  60. {
  61. "LDA CV": GridSearchCV(
  62. LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
  63. param_grid={"shrinkage": np.arange(0, 1, 0.05)},
  64. scoring="f1",
  65. cv=3,
  66. )
  67. }
  68. )
  69. LW = LedoitWolf(store_precision=False, assume_centered=False)
  70. modelList.update(
  71. {
  72. "LDA LW": LinearDiscriminantAnalysis(
  73. solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
  74. )
  75. }
  76. )
  77. # replace this with graphical_lasso
  78. # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
  79. # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
  80. # covariance_estimator=gl,
  81. # priors=[0.5,0.5])})
  82. # add priors as [0.5, 0.5]
  83. oa = OAS(store_precision=False, assume_centered=False)
  84. modelList.update(
  85. {
  86. "LDA OA": LinearDiscriminantAnalysis(
  87. solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
  88. )
  89. }
  90. )
  91. # remove QDA, never work well
  92. # modelList.update({"QDA": QuadraticDiscriminantAnalysis()})
  93. # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2', solver='lbfgs')})
  94. #
  95. # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1', solver='liblinear')})
  96. #
  97. # modelList.update({"Lgstic Rgrssn EN": LogisticRegression(penalty='elasticnet',
  98. # solver='saga',
  99. # l1_ratio=0.02,
  100. # max_iter=1500)})
  101. #
  102. # modelList.update({"GaussianNB": GaussianNB()})
  103. #
  104. modelList.update(
  105. {"NSC MH": NearestCentroid(metric="manhattan", shrink_threshold=0.2)}
  106. )
  107. modelList.update(
  108. {
  109. "NSC Custom EC": _NSC.NearestCentroid(
  110. metric="euclidean", shrink_threshold=0.2
  111. )
  112. }
  113. )
  114. #
  115. # modelList.update({"Lgstic Rgrssn GL": LogisticGroupLasso(
  116. # groups=groups,
  117. # group_reg=0.05,
  118. # l1_reg=0.05,
  119. # scale_reg="inverse_group_size",
  120. # supress_warning=True,
  121. # subsampling_scheme=0.1,
  122. # n_iter=1500
  123. # )})
  124. # TODO: add ths: https://github.com/glm-tools/pyglmnet
  125. # binomial distribution with logit link function for generalized linear model
  126. # compare different solvers for logistic regression
  127. # gl = GroupLasso(
  128. # groups=groups,
  129. # group_reg=5,
  130. # l1_reg=0,
  131. # frobenius_lipschitz=True,
  132. # scale_reg="inverse_group_size",
  133. # subsampling_scheme=1,
  134. # supress_warning=True,
  135. # n_iter=1000,
  136. # tol=1e-3,
  137. # )
  138. return modelList
  139. def getMdlNSCFn() -> modelDict:
  140. modelList = {}
  141. modelList.update(
  142. {
  143. "NSC MH": GridSearchCV(
  144. NearestCentroid(metric="manhattan"),
  145. param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
  146. cv=3,
  147. )
  148. }
  149. )
  150. return modelList
  151. def getMdlsCVFn() -> modelDict:
  152. # if 'groups' in params:
  153. # groups = params['groups']
  154. modelList = {}
  155. # skf = RepeatedStratifiedKFold(n_splits=3, n_repeats=2)
  156. # lw = LedoitWolf(store_precision=False, assume_centered=False)
  157. # oa = OAS(store_precision=False, assume_centered=False)
  158. # modelList.update({"Lasso Linear CV": LassoCV()})
  159. # modelList.update({"Lasso L1 CV": LogisticRegressionCV(penalty='l1',
  160. # solver='liblinear',
  161. # scoring=make_scorer(roc_auc_score),
  162. # cv=3,
  163. # max_iter = 500)})
  164. #
  165. modelList.update(
  166. {
  167. "Lasso L2 CV": LogisticRegressionCV(
  168. penalty="l2", scoring=make_scorer(roc_auc_score), cv=3, max_iter=500
  169. )
  170. }
  171. )
  172. #
  173. # modelList.update({"Lasso EN CV": LogisticRegressionCV(penalty='elasticnet',
  174. # solver='saga',
  175. # l1_ratios=np.arange(0.05,1,0.05),
  176. # scoring=make_scorer(roc_auc_score),
  177. # cv=3,
  178. # max_iter = 500)})
  179. # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2',
  180. # solver='lbfgs',
  181. # C=0.001)})
  182. # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1',
  183. # solver='liblinear',
  184. # C=0.5)})
  185. modelList.update(
  186. {
  187. "LDA CV": GridSearchCV(
  188. LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
  189. param_grid={"shrinkage": np.arange(0, 1, 0.05)},
  190. scoring="roc_auc",
  191. cv=3,
  192. )
  193. }
  194. )
  195. LW = LedoitWolf(store_precision=False, assume_centered=False)
  196. modelList.update(
  197. {
  198. "LDA LW": LinearDiscriminantAnalysis(
  199. solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
  200. )
  201. }
  202. )
  203. # replace this with graphical_lasso
  204. # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
  205. # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
  206. # covariance_estimator=gl,
  207. # priors=[0.5,0.5])})
  208. # add priors as [0.5, 0.5]
  209. oa = OAS(store_precision=False, assume_centered=False)
  210. modelList.update(
  211. {
  212. "LDA OA": LinearDiscriminantAnalysis(
  213. solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
  214. )
  215. }
  216. )
  217. # modelList.update({
  218. # "LgcRgs GL CV": GridSearchCV(
  219. # LogisticGroupLasso(groups=groups,
  220. # scale_reg="inverse_group_size",
  221. # supress_warning=True,
  222. # subsampling_scheme=0.1,
  223. # n_iter=1500
  224. # ),
  225. # param_grid = {'l1_reg': np.arange(0,1,0.05),
  226. # 'group_reg': np.arange(0,1,0.05)
  227. # )
  228. # })
  229. modelList.update(
  230. {
  231. "NSC MH": GridSearchCV(
  232. _NSC.NearestCentroid(metric="manhattan"),
  233. param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
  234. cv=3,
  235. )
  236. }
  237. )
  238. # modelList.update({"NSC EC": GridSearchCV(NearestCentroid(metric='euclidean'),
  239. # param_grid = {
  240. # 'shrink_threshold': np.arange(0.05,1,0.05)
  241. # },
  242. # cv=3)})
  243. modelList.update(
  244. {
  245. "NSC Custom EC": GridSearchCV(
  246. _NSC.NearestCentroid(metric="euclidean"),
  247. param_grid={"shrink_threshold": np.arange(0.05, 2, 0.05)},
  248. cv=3,
  249. )
  250. }
  251. )
  252. modelList.update(
  253. {
  254. "SVM Lin": GridSearchCV(
  255. SVC(kernel="linear"),
  256. param_grid={"C": np.logspace(-10, 3, base=2)},
  257. scoring="roc_auc",
  258. )
  259. }
  260. )
  261. modelList.update(
  262. {
  263. "SVM Poly2": GridSearchCV(
  264. SVC(kernel="poly", degree=2),
  265. param_grid={"C": np.logspace(-10, 3, base=2)},
  266. scoring="roc_auc",
  267. )
  268. }
  269. )
  270. # modelList.update(
  271. # {
  272. # "SVM Poly3": GridSearchCV(
  273. # SVC(kernel="poly", degree=3),
  274. # param_grid={"C": np.logspace(-10, 3, base=2)},
  275. # scoring="roc_auc",
  276. # )
  277. # }
  278. # )
  279. #
  280. # modelList.update({"SVM Poly4": GridSearchCV(SVC(kernel='poly', degree=4),
  281. # param_grid = {
  282. # 'C': np.logspace(-10,3,base=2)
  283. # })})
  284. #
  285. # modelList.update({"SVM RBF": GridSearchCV(SVC(kernel='rbf'),
  286. # param_grid = {
  287. # 'C': np.logspace(-10,3,base=2)
  288. # })})
  289. modelList.update({"GNB": GaussianNB()})
  290. modelList.update({"GNB Prior": GaussianNB(priors=[0.8, 0.20])})
  291. modelList.update({"Tree Max Depth 2": DecisionTreeClassifier()})
  292. # modelList.update({"CNB": ComplementNB()})
  293. # modelList.update({"KNN EC": GridSearchCV(KNeighborsClassifier(metric='euclidean'),
  294. # param_grid = {
  295. # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
  296. # 'weights': ['uniform','distance'],
  297. # },
  298. # scoring='roc_auc',
  299. # cv=3)})
  300. # modelList.update({"KNN MH": GridSearchCV(KNeighborsClassifier(metric='manhattan'),
  301. # param_grid = {
  302. # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
  303. # 'weights': ['uniform','distance'],
  304. # })})
  305. # modelList.update({"RNN EC": GridSearchCV(RadiusNeighborsClassifier(metric='euclidean'),
  306. # param_grid = {
  307. # 'radius': np.arange(0.05,1,0.05),
  308. # 'weights': ['uniform','distance'],
  309. # })})
  310. #
  311. # modelList.update({"RNN MH": GridSearchCV(RadiusNeighborsClassifier(metric='manhattan'),
  312. # param_grid = {
  313. # 'radius': np.arange(0.05,1,0.05),
  314. # 'weights': ['uniform','distance'],
  315. # })})
  316. return modelList
  317. def getNonLinearMdlsCVFn() -> modelDict:
  318. modelList = {}
  319. # modelList.update(
  320. # {
  321. # "Lasso L2 CV": LogisticRegressionCV(
  322. # penalty="l2", scoring=make_scorer(roc_auc_score), cv=3, max_iter=500
  323. # )
  324. # }
  325. # )
  326. #
  327. # modelList.update({"Lasso EN CV": LogisticRegressionCV(penalty='elasticnet',
  328. # solver='saga',
  329. # l1_ratios=np.arange(0.05,1,0.05),
  330. # scoring=make_scorer(roc_auc_score),
  331. # cv=3,
  332. # max_iter = 500)})
  333. # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2',
  334. # solver='lbfgs',
  335. # C=0.001)})
  336. # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1',
  337. # solver='liblinear',
  338. # C=0.5)})
  339. # modelList.update(
  340. # {
  341. # "LDA CV": GridSearchCV(
  342. # LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
  343. # param_grid={"shrinkage": np.arange(0, 1, 0.05)},
  344. # scoring="roc_auc",
  345. # cv=3,
  346. # )
  347. # }
  348. # )
  349. #
  350. # LW = LedoitWolf(store_precision=False, assume_centered=False)
  351. # modelList.update(
  352. # {
  353. # "LDA LW": LinearDiscriminantAnalysis(
  354. # solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
  355. # )
  356. # }
  357. # )
  358. # replace this with graphical_lasso
  359. # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
  360. # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
  361. # covariance_estimator=gl,
  362. # priors=[0.5,0.5])})
  363. # add priors as [0.5, 0.5]
  364. # oa = OAS(store_precision=False, assume_centered=False)
  365. # modelList.update(
  366. # {
  367. # "LDA OA": LinearDiscriminantAnalysis(
  368. # solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
  369. # )
  370. # }
  371. # )
  372. # modelList.update({
  373. # "LgcRgs GL CV": GridSearchCV(
  374. # LogisticGroupLasso(groups=groups,
  375. # scale_reg="inverse_group_size",
  376. # supress_warning=True,
  377. # subsampling_scheme=0.1,
  378. # n_iter=1500
  379. # ),
  380. # param_grid = {'l1_reg': np.arange(0,1,0.05),
  381. # 'group_reg': np.arange(0,1,0.05)
  382. # )
  383. # })
  384. # modelList.update(
  385. # {
  386. # "NSC MH": GridSearchCV(
  387. # _NSC.NearestCentroid(metric="manhattan"),
  388. # param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
  389. # cv=3,
  390. # )
  391. # }
  392. # )
  393. # modelList.update({"NSC EC": GridSearchCV(NearestCentroid(metric='euclidean'),
  394. # param_grid = {
  395. # 'shrink_threshold': np.arange(0.05,1,0.05)
  396. # },
  397. # cv=3)})
  398. # modelList.update(
  399. # {
  400. # "NSC Custom EC": GridSearchCV(
  401. # _NSC.NearestCentroid(metric="euclidean"),
  402. # param_grid={"shrink_threshold": np.arange(0.05, 2, 0.05)},
  403. # cv=3,
  404. # )
  405. # }
  406. # )
  407. modelList.update(
  408. {
  409. "SVM Lin": GridSearchCV(
  410. SVC(kernel="linear"),
  411. param_grid={"C": np.logspace(-10, 3, base=2)},
  412. scoring="roc_auc",
  413. )
  414. }
  415. )
  416. modelList.update(
  417. {
  418. "SVM Poly2": GridSearchCV(
  419. SVC(kernel="poly", degree=2),
  420. param_grid={"C": np.logspace(-10, 3, base=2)},
  421. scoring="roc_auc",
  422. )
  423. }
  424. )
  425. modelList.update(
  426. {
  427. "SVM Poly3": GridSearchCV(
  428. SVC(kernel="poly", degree=3),
  429. param_grid={"C": np.logspace(-10, 3, base=2)},
  430. scoring="roc_auc",
  431. )
  432. }
  433. )
  434. modelList.update(
  435. {
  436. "SVM Poly4": GridSearchCV(
  437. SVC(kernel="poly", degree=4),
  438. param_grid={"C": np.logspace(-10, 3, base=2)},
  439. )
  440. }
  441. )
  442. modelList.update(
  443. {
  444. "SVM RBF": GridSearchCV(
  445. SVC(kernel="rbf"), param_grid={"C": np.logspace(-10, 3, base=2)}
  446. )
  447. }
  448. )
  449. # modelList.update({"GNB": GaussianNB()})
  450. #
  451. # modelList.update({"GNB Prior": GaussianNB(priors=[0.8, 0.20])})
  452. #
  453. # modelList.update({"Tree": DecisionTreeClassifier()})
  454. # modelList.update({"CNB": ComplementNB()})
  455. modelList.update(
  456. {
  457. "KNN EC": GridSearchCV(
  458. KNeighborsClassifier(metric="euclidean"),
  459. param_grid={
  460. "n_neighbors": [2, 3, 4, 5, 6, 7, 8],
  461. "weights": ["uniform", "distance"],
  462. },
  463. scoring="roc_auc",
  464. cv=3,
  465. )
  466. }
  467. )
  468. modelList.update(
  469. {
  470. "KNN MH": GridSearchCV(
  471. KNeighborsClassifier(metric="manhattan"),
  472. param_grid={
  473. "n_neighbors": [2, 3, 4, 5, 6, 7, 8],
  474. "weights": ["uniform", "distance"],
  475. },
  476. cv=3,
  477. )
  478. }
  479. )
  480. # modelList.update({"RNN EC": GridSearchCV(RadiusNeighborsClassifier(metric='euclidean'),
  481. # param_grid = {
  482. # 'radius': np.arange(0.05,1,0.05),
  483. # 'weights': ['uniform','distance'],
  484. # },
  485. # cv=3)})
  486. #
  487. # modelList.update({"RNN MH": GridSearchCV(RadiusNeighborsClassifier(metric='manhattan'),
  488. # param_grid = {
  489. # 'radius': np.arange(0.05,1,0.05),
  490. # 'weights': ['uniform','distance'],
  491. # },
  492. # cv=3)})
  493. return modelList
  494. def getMdlsCVFn_3Clss() -> modelDict:
  495. # if 'groups' in params:
  496. # groups = params['groups']
  497. modelList = {}
  498. # skf = RepeatedStratifiedKFold(n_splits=3, n_repeats=2)
  499. # lw = LedoitWolf(store_precision=False, assume_centered=False)
  500. # oa = OAS(store_precision=False, assume_centered=False)
  501. # modelList.update({"Lasso Linear CV": LassoCV()})
  502. # modelList.update({"Lasso L1 CV": LogisticRegressionCV(penalty='l1',
  503. # solver='liblinear',
  504. # scoring=make_scorer(roc_auc_score),
  505. # cv=3,
  506. # max_iter = 500)})
  507. #
  508. modelList.update(
  509. {
  510. "Lasso L2 CV": LogisticRegressionCV(
  511. penalty="l2",
  512. scoring=make_scorer(f1_score, average="micro"),
  513. cv=3,
  514. max_iter=500,
  515. multi_class="multinomial",
  516. )
  517. }
  518. )
  519. #
  520. # modelList.update({"Lasso EN CV": LogisticRegressionCV(penalty='elasticnet',
  521. # solver='saga',
  522. # l1_ratios=np.arange(0.05,1,0.05),
  523. # scoring=make_scorer(roc_auc_score),
  524. # cv=3,
  525. # max_iter = 500)})
  526. # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2',
  527. # solver='lbfgs',
  528. # C=0.001)})
  529. # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1',
  530. # solver='liblinear',
  531. # C=0.5)})
  532. modelList.update(
  533. {
  534. "LDA CV": GridSearchCV(
  535. LinearDiscriminantAnalysis(solver="lsqr"),
  536. param_grid={"shrinkage": np.arange(0, 1, 0.05)},
  537. scoring=make_scorer(f1_score, average="micro"),
  538. cv=3,
  539. )
  540. }
  541. )
  542. LW = LedoitWolf(store_precision=False, assume_centered=False)
  543. modelList.update(
  544. {"LDA LW": LinearDiscriminantAnalysis(solver="lsqr", covariance_estimator=LW)}
  545. )
  546. # replace this with graphical_lasso
  547. # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
  548. # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
  549. # covariance_estimator=gl,
  550. # priors=[0.5,0.5])})
  551. # add priors as [0.5, 0.5]
  552. oa = OAS(store_precision=False, assume_centered=False)
  553. modelList.update(
  554. {"LDA OA": LinearDiscriminantAnalysis(solver="lsqr", covariance_estimator=oa)}
  555. )
  556. # modelList.update({
  557. # "LgcRgs GL CV": GridSearchCV(
  558. # LogisticGroupLasso(groups=groups,
  559. # scale_reg="inverse_group_size",
  560. # supress_warning=True,
  561. # subsampling_scheme=0.1,
  562. # n_iter=1500
  563. # ),
  564. # param_grid = {'l1_reg': np.arange(0,1,0.05),
  565. # 'group_reg': np.arange(0,1,0.05)
  566. # )
  567. # })
  568. modelList.update(
  569. {
  570. "NSC MH": GridSearchCV(
  571. _NSC.NearestCentroid(metric="manhattan"),
  572. param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
  573. cv=3,
  574. )
  575. }
  576. )
  577. # modelList.update({"NSC EC": GridSearchCV(NearestCentroid(metric='euclidean'),
  578. # param_grid = {
  579. # 'shrink_threshold': np.arange(0.05,1,0.05)
  580. # },
  581. # cv=3)})
  582. modelList.update(
  583. {
  584. "NSC Custom EC": GridSearchCV(
  585. _NSC.NearestCentroid(metric="euclidean"),
  586. param_grid={"shrink_threshold": np.arange(0.05, 2, 0.05)},
  587. cv=3,
  588. )
  589. }
  590. )
  591. # modelList.update({"SVM Lin": GridSearchCV(SVC(kernel='linear'),
  592. # param_grid = {
  593. # 'C': np.logspace(-10,3,base=2)
  594. # },
  595. # scoring='roc_auc')})
  596. #
  597. # modelList.update({"SVM Poly2": GridSearchCV(SVC(kernel='poly', degree=2),
  598. # param_grid = {
  599. # 'C': np.logspace(-10,3,base=2)
  600. # },
  601. # scoring='roc_auc')})
  602. #
  603. # modelList.update({"SVM Poly3": GridSearchCV(SVC(kernel='poly', degree=3),
  604. # param_grid = {
  605. # 'C': np.logspace(-10,3,base=2)
  606. # },
  607. # scoring='roc_auc')})
  608. #
  609. # modelList.update({"SVM Poly4": GridSearchCV(SVC(kernel='poly', degree=4),
  610. # param_grid = {
  611. # 'C': np.logspace(-10,3,base=2)
  612. # })})
  613. #
  614. # modelList.update({"SVM RBF": GridSearchCV(SVC(kernel='rbf'),
  615. # param_grid = {
  616. # 'C': np.logspace(-10,3,base=2)
  617. # })})
  618. modelList.update({"GNB": GaussianNB()})
  619. modelList.update({"GNB Prior": GaussianNB()})
  620. modelList.update({"Tree": DecisionTreeClassifier()})
  621. # modelList.update({"CNB": ComplementNB()})
  622. # modelList.update({"KNN EC": GridSearchCV(KNeighborsClassifier(metric='euclidean'),
  623. # param_grid = {
  624. # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
  625. # 'weights': ['uniform','distance'],
  626. # },
  627. # scoring='roc_auc',
  628. # cv=3)})
  629. # modelList.update({"KNN MH": GridSearchCV(KNeighborsClassifier(metric='manhattan'),
  630. # param_grid = {
  631. # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
  632. # 'weights': ['uniform','distance'],
  633. # })})
  634. # modelList.update({"RNN EC": GridSearchCV(RadiusNeighborsClassifier(metric='euclidean'),
  635. # param_grid = {
  636. # 'radius': np.arange(0.05,1,0.05),
  637. # 'weights': ['uniform','distance'],
  638. # })})
  639. #
  640. # modelList.update({"RNN MH": GridSearchCV(RadiusNeighborsClassifier(metric='manhattan'),
  641. # param_grid = {
  642. # 'radius': np.arange(0.05,1,0.05),
  643. # 'weights': ['uniform','distance'],
  644. # })})
  645. return modelList
  646. ################################ NEW ################################
  647. def getMdlsCSPFn() -> modelDict:
  648. modelList = {}
  649. modelList.update(
  650. {
  651. "LDA CV": GridSearchCV(
  652. LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
  653. param_grid={"shrinkage": np.arange(0, 1, 0.05)},
  654. scoring="roc_auc",
  655. cv=3,
  656. )
  657. }
  658. )
  659. LW = LedoitWolf(store_precision=False, assume_centered=False)
  660. modelList.update(
  661. {
  662. "LDA LW": LinearDiscriminantAnalysis(
  663. solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
  664. )
  665. }
  666. )
  667. # replace this with graphical_lasso
  668. # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
  669. # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
  670. # covariance_estimator=gl,
  671. # priors=[0.5,0.5])})
  672. # add priors as [0.5, 0.5]
  673. oa = OAS(store_precision=False, assume_centered=False)
  674. modelList.update(
  675. {
  676. "LDA OA": LinearDiscriminantAnalysis(
  677. solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
  678. )
  679. }
  680. )
  681. return modelList
  682. def getMdlsCSPFn_3Clss() -> modelDict:
  683. modelList = {}
  684. modelList.update(
  685. {
  686. "LDA CV": GridSearchCV(
  687. LinearDiscriminantAnalysis(solver="lsqr"),
  688. param_grid={"shrinkage": np.arange(0, 1, 0.05)},
  689. scoring="roc_auc",
  690. cv=3,
  691. )
  692. }
  693. )
  694. LW = LedoitWolf(store_precision=False, assume_centered=False)
  695. modelList.update(
  696. {
  697. "LDA LW": LinearDiscriminantAnalysis(
  698. solver="lsqr",
  699. covariance_estimator=LW,
  700. )
  701. }
  702. )
  703. # replace this with graphical_lasso
  704. # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
  705. # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
  706. # covariance_estimator=gl,
  707. # priors=[0.5,0.5])})
  708. # add priors as [0.5, 0.5]
  709. oa = OAS(store_precision=False, assume_centered=False)
  710. modelList.update(
  711. {
  712. "LDA OA": LinearDiscriminantAnalysis(
  713. solver="lsqr",
  714. covariance_estimator=oa,
  715. )
  716. }
  717. )
  718. return modelList
  719. def getEnsmblsFn() -> modelDict:
  720. modelList = {}
  721. modelList.update(
  722. {"RF": RandomForestClassifier(class_weight="balanced", max_depth=5)}
  723. )
  724. modelList.update({"Bagging": BaggingClassifier()})
  725. modelList.update({"Extra RF": ExtraTreesClassifier(class_weight="balanced")})
  726. modelList.update({"Ada": AdaBoostClassifier()})
  727. # what parameters should I vary? for xgboost
  728. # n_estimators, subsample, min_samples_leafs,
  729. modelList.update({"GB": GradientBoostingClassifier()})
  730. hyperparameter_grid = {
  731. "n_estimators": [100, 400],
  732. "max_depth": [3, 6, 9],
  733. "learning_rate": [0.05, 0.1, 0.20],
  734. "min_child_weight": [1, 10, 100],
  735. "gamma": [0, 0.1],
  736. "reg_alpha": [1e-2, 0.1, 10],
  737. }
  738. modelList.update(
  739. {
  740. "XGB": GridSearchCV(
  741. XGBClassifier(booster="gbtree"),
  742. param_grid=hyperparameter_grid,
  743. cv=3,
  744. scoring="roc_auc",
  745. )
  746. }
  747. )
  748. return modelList

getMdlsMd.py at commit fd85423, under BSD-3-Clause · at the source

Overview

Authors: Matthew Herbert Ning1, Haoqi Sun2, Brice Passera1, Duygu Bagci Das1, Brandon Westover2, Alvaro Pascual-Leone3, Emiliano Santarnecchi4, Mouhsin M Shafi1, Recep A Ozdemir1
  1. Berenson-Allen Center for Noninvasive Brain Stimulation, Beth Israel Deaconess Medical Center, Boston, Massachusetts, United States of America
  2. Neurology Department, Beth Israel Deaconess Medical Center, Boston, Massachusetts, United States of America
  3. Hebrew SeniorLife Center, Boston, Massachusetts, United States of America
  4. Radiology Department, Massachusetts General Hospital, Boston, Massachusetts, United States of America
Journal: PLoS computational biology, volume 22, issue 4, article e1014154
Dates: received 7 October 2025; accepted 23 March 2026; published online 30 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1371/journal.pcbi.1014154 · PMID 42060616 · PMCID PMC13132237 · OpenAlex W4414482556
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), other (modality), human (organism), systems (subfield)
Methods: Statistics, Machine learning, Complexity, Preprocessing, Spectral & time-frequency, Evoked potentials, fMRI & imaging, Physiology & signal measures
MeSH: Machine Learning*, Motor Cortex*, Transcranial Magnetic Stimulation*, Adult, Computational Biology, Electroencephalography, Evoked Potentials, Motor, Female, Humans, Male, Models, Neurological, Predictive Learning Models, Reproducibility of Results, Theta Rhythm, Young Adult (* major topic)
Topic: Transcranial Magnetic Stimulation Studies (Neurology, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 80 references in the paper

Abstract

Background: Substantial variability in individual responses to intermittent theta-burst stimulation (iTBS) limits its clinical efficacy, yet neurophysiological mechanisms underlying this variability remain unclear. While most machine-learning studies have focused on modeling behavioral or clinical effects of repetitive transcranial magnetic stimulation (rTMS), the few studies examining neurophysiological outcomes utilized limited feature sets in single-visit settings, which captured only inter-subject variability and most importantly lacked independent validation sets.

Methods: To address these gaps, we employed supervised machine learning models that integrated baseline resting-state EEG (rsEEG) features and baseline transcranial magnetic stimulation (TMS)-evoked measures, including motor-evoked potentials (MEPs) and TMS-evoked potentials (TEPs), to predict neurophysiological responses to a single iTBS session applied over the primary motor cortex in two independent test-retest studies of healthy adults. We also employed statistical and reliability analysis to understand the statistical relationship between resting state EEG and responses to iTBS.

Results: Internal cross-validation within the training cohort yielded promising binary classification performance (accuracy: 81%), identifying coarse-grained multiscale distribution entropy of rsEEG as the most robust predictor of local cortical excitability changes indexed by the 100–131ms window of TEPs. However, predictive performance markedly declined upon external validation (accuracy: 69%), reflecting unstable relationships between predictors and outcomes likely driven by substantial intra- and inter-individual variability of iTBS-induced changes in neurophysiological outcomes.

Conclusions: These findings emphasize that while EEG complexity measures can capture baseline brain states relevant for neuromodulation to a certain degree, the inherent instability of single-session iTBS effects significantly constrains model generalizability and underscores the necessity of test-retest paradigm to avoid overly optimistic performance estimates. Future studies with multi-session and individualized stimulation protocols are urgently needed to better characterize neurophysiological mechanisms underlying rTMS effects and ultimately enhance its therapeutic potential.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 8 matches between paragraphs and lines of code.

NoPenguinsLand/TMS-EEG-Machine-Learning-PLOS-CompBio

License: BSD-3-Clause
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: fd85423d60ca015f731c57f0769968d72f7d4fe7, 19 February 2026
Languages: Python (61)
Size: 65 files, 61 scripts
Software Heritage: not archived
Found in: “Data Availability”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (38 files), Matplotlib (24 files), pandas (20 files), SciPy (20 files), scikit-learn (19 files), MNE-Python (13 files), imbalanced-learn (8 files), statsmodels (4 files), specparam (formerly FOOOF) (1 file), XGBoost (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
63 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 61 scripts, each with its path and the digest of its content;
  • 8 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data Availability

The source code used to produce the results and analyses presented in this manuscript are available from GitHub repository: https://github.com/NoPenguinsLand/TMS-EEG-Machine-Learning-PLOS-CompBio. All relevant data (including resting state EEG features, baseline TMS-EEG, baseline MEPs and outcome measures) are available at https://zenodo.org/records/19238430.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 9 authors, 15 MeSH terms, 3 funders, 80 references.

Cite

This paper

Ning, M. H., Sun, H., Passera, B., Das, D. B., Westover, B., Pascual-Leone, A., Santarnecchi, E., Shafi, M. M., & Ozdemir, R. A. (2026). Complexity of resting cortical activity predicts neurophysiological responses to theta-burst stimulation but fails to generalize: A rigorous machine-learning approach. PLoS computational biology, 22(4), e1014154. https://doi.org/10.1371/journal.pcbi.1014154

BibTeX

@article{ning2026complexity,
author = {Ning, Matthew Herbert and Sun, Haoqi and Passera, Brice and Das, Duygu Bagci and Westover, Brandon and Pascual-Leone, Alvaro and Santarnecchi, Emiliano and Shafi, Mouhsin M and Ozdemir, Recep A},
title = {{Complexity of resting cortical activity predicts neurophysiological responses to theta-burst stimulation but fails to generalize: A rigorous machine-learning approach}},
journal = {PLoS computational biology},
year = {2026},
month = apr,
volume = {22},
number = {4},
pages = {e1014154},
publisher = {PLOS},
issn = {1553-734X},
doi = {10.1371/journal.pcbi.1014154},
url = {https://doi.org/10.1371/journal.pcbi.1014154},
pmid = {42060616},
pmcid = {PMC13132237}
}

RIS

TY - JOUR
AU - Ning, Matthew Herbert
AU - Sun, Haoqi
AU - Passera, Brice
AU - Das, Duygu Bagci
AU - Westover, Brandon
AU - Pascual-Leone, Alvaro
AU - Santarnecchi, Emiliano
AU - Shafi, Mouhsin M
AU - Ozdemir, Recep A
TI - Complexity of resting cortical activity predicts neurophysiological responses to theta-burst stimulation but fails to generalize: A rigorous machine-learning approach
T2 - PLoS computational biology
J2 - PLoS Comput Biol
PY - 2026
DA - 2026/04/30
VL - 22
IS - 4
SP - e1014154
SN - 1553-734X
PB - PLOS
DO - 10.1371/journal.pcbi.1014154
UR - https://doi.org/10.1371/journal.pcbi.1014154
LA - en
ER -

CSL-JSON

{
"id": "10.1371/journal.pcbi.1014154",
"type": "article-journal",
"title": "Complexity of resting cortical activity predicts neurophysiological responses to theta-burst stimulation but fails to generalize: A rigorous machine-learning approach",
"container-title": "PLoS computational biology",
"author": [
{
"family": "Ning",
"given": "Matthew Herbert"
},
{
"family": "Sun",
"given": "Haoqi"
},
{
"family": "Passera",
"given": "Brice"
},
{
"family": "Das",
"given": "Duygu Bagci"
},
{
"family": "Westover",
"given": "Brandon"
},
{
"family": "Pascual-Leone",
"given": "Alvaro"
},
{
"family": "Santarnecchi",
"given": "Emiliano"
},
{
"family": "Shafi",
"given": "Mouhsin M"
},
{
"family": "Ozdemir",
"given": "Recep A"
}
],
"container-title-short": "PLoS Comput Biol",
"volume": "22",
"issue": "4",
"page": "e1014154",
"DOI": "10.1371/journal.pcbi.1014154",
"PMID": "42060616",
"PMCID": "PMC13132237",
"ISSN": "1553-734X",
"publisher": "PLOS",
"URL": "https://doi.org/10.1371/journal.pcbi.1014154",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
30
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.nicl.2026.104012 [code]
Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.
Journal: NeuroImage. Clinical
In common: imbalanced-learn, specparam (formerly FOOOF), XGBoost, 6 other tools, other, 1 reference
[2] doi:10.1002/hbm.70628 [code]
EEG Biomarkers for Affective Disorders Diagnosis: An Evaluation and Validation Study.
Journal: Human brain mapping
In common: MNE-Python, statsmodels, scikit-learn, 4 other tools, EEG, 4 references
[3] doi:10.1186/s12916-026-04978-7 [code]
Mapping shared and specific cortical after-effects of repetitive TMS on brain function.
Journal: BMC medicine
In common: scikit-learn, pandas, SciPy, 2 other tools, other, 5 references
[4] doi:10.3390/s26175327 [code]
Subject Identity Confounds qEEG Emotion Recognition on DEAP and DREAMER.
Journal: Sensors (Basel, Switzerland)
In common: imbalanced-learn, XGBoost, MNE-Python, 6 other tools, EEG
[5] doi:10.1002/mds.70348 [code]
Electroencephalography-Based Clustering Reveals Robust Neurophysiological Subtypes in Parkinson's Disease.
Journal: Movement disorders : official journal of the Movement Disorder Society
In common: specparam (formerly FOOOF), MNE-Python, statsmodels, 5 other tools, EEG, 1 reference
[6] doi:10.1093/brain/awaf412 [code]
Multimodal multicentre investigation of diagnostic and prognostic markers in disorders of consciousness.
Journal: Brain : a journal of neurology
In common: specparam (formerly FOOOF), XGBoost, MNE-Python, 5 other tools, EEG
[7] doi:10.3389/fncom.2026.1786996 [code]
Schumann-anchored golden ratio organization of human neural oscillations.
Journal: Frontiers in computational neuroscience
In common: specparam (formerly FOOOF), MNE-Python, statsmodels, 5 other tools, systems, EEG
[8] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: imbalanced-learn, XGBoost, statsmodels, 5 other tools, systems
[9] doi:10.1093/cercor/bhag113 [code]
Long-term reliability and stability of parameterized resting state EEG: evidence from a five-year follow-up.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: specparam (formerly FOOOF), MNE-Python, scikit-learn, 4 other tools, EEG, 1 reference
[10] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: specparam (formerly FOOOF), MNE-Python, statsmodels, 5 other tools, other

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.