Complexity of resting cortical activity predicts neurophysiological responses to theta-burst stimulation but fails to generalize: A rigorous machine-learning approach.
The 8 matches
- [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] § Methods › Classifiers ↔ src/mdls/getMdlsMd.py, lines 58–159 · score 0.78 · Ledoit Wolf, linear discriminant, logistic regression, Euclidean, Gaussian, centroid
- [3] § Methods › Classifiers ↔ src/mdls/_NSC.py, lines 32–154 · score 0.75 · shrunken centroid, distance metric, class priors, Euclidean, nearest, Manhattan
- [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] § 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] § Results › Cross-cohort experiment ↔ src/mdls/getMdlsMd.py, lines 600–666 · score 0.53 · LDA OA, linear discriminant, F1 score, shrinkage, metric, precision
- [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] § 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
- """
- Created on Jun 24, 2024
- @author: mning
- """
- """
- Created on May 22, 2023
- @author: mning
- """
- """
- Created on May 10, 2023
- @author: mning
- Note: all must accept same input arguments and yield same output types
- """
- import logging
- import numpy as np
- from sklearn.covariance import LedoitWolf
- from sklearn.covariance import OAS
- from sklearn.discriminant_analysis import (
- LinearDiscriminantAnalysis,
- )
- from sklearn.ensemble import AdaBoostClassifier
- from sklearn.ensemble import BaggingClassifier
- from sklearn.ensemble import ExtraTreesClassifier
- from sklearn.ensemble import GradientBoostingClassifier
- from sklearn.ensemble import RandomForestClassifier
- from sklearn.linear_model import (
- LogisticRegressionCV,
- )
- from sklearn.metrics import f1_score
- from sklearn.metrics import make_scorer
- from sklearn.metrics import roc_auc_score
- from sklearn.model_selection import GridSearchCV
- from sklearn.model_selection import RepeatedStratifiedKFold
- from sklearn.naive_bayes import GaussianNB
- from sklearn.neighbors import KNeighborsClassifier
- from sklearn.neighbors import (
- NearestCentroid,
- )
- from sklearn.neighbors import RadiusNeighborsClassifier
- from sklearn.svm import SVC
- from sklearn.tree import DecisionTreeClassifier
- from xgboost.sklearn import XGBClassifier
- from src.mdls import _NSC
- # import getMin
- # import getSTD
- logger = logging.getLogger("root")
- modelDict = dict
- def getMdlsFn(params: dict) -> modelDict:
- # params contain data-derived parameters
- if "groups" in params:
- params["groups"]
- modelList = {}
- # https://stats.stackexchange.com/questions/48786/algebra-of-lda-fisher-discrimination-power-of-a-variable-and-linear-discriminan/48859#48859
- # https://stats.stackexchange.com/questions/109747/why-is-pythons-scikit-learn-lda-not-working-correctly-and-how-does-it-compute-l/110697#110697
- # modelList.update({"LDA": LinearDiscriminantAnalysis(solver='svd',
- # shrinkage=None,
- # priors=[0.5,0.5])})
- #
- modelList.update(
- {
- "LDA CV": GridSearchCV(
- LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
- param_grid={"shrinkage": np.arange(0, 1, 0.05)},
- scoring="f1",
- cv=3,
- )
- }
- )
- LW = LedoitWolf(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA LW": LinearDiscriminantAnalysis(
- solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
- )
- }
- )
- # replace this with graphical_lasso
- # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
- # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
- # covariance_estimator=gl,
- # priors=[0.5,0.5])})
- # add priors as [0.5, 0.5]
- oa = OAS(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA OA": LinearDiscriminantAnalysis(
- solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
- )
- }
- )
- # remove QDA, never work well
- # modelList.update({"QDA": QuadraticDiscriminantAnalysis()})
- # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2', solver='lbfgs')})
- #
- # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1', solver='liblinear')})
- #
- # modelList.update({"Lgstic Rgrssn EN": LogisticRegression(penalty='elasticnet',
- # solver='saga',
- # l1_ratio=0.02,
- # max_iter=1500)})
- #
- # modelList.update({"GaussianNB": GaussianNB()})
- #
- modelList.update(
- {"NSC MH": NearestCentroid(metric="manhattan", shrink_threshold=0.2)}
- )
- modelList.update(
- {
- "NSC Custom EC": _NSC.NearestCentroid(
- metric="euclidean", shrink_threshold=0.2
- )
- }
- )
- #
- # modelList.update({"Lgstic Rgrssn GL": LogisticGroupLasso(
- # groups=groups,
- # group_reg=0.05,
- # l1_reg=0.05,
- # scale_reg="inverse_group_size",
- # supress_warning=True,
- # subsampling_scheme=0.1,
- # n_iter=1500
- # )})
- # TODO: add ths: https://github.com/glm-tools/pyglmnet
- # binomial distribution with logit link function for generalized linear model
- # compare different solvers for logistic regression
- # gl = GroupLasso(
- # groups=groups,
- # group_reg=5,
- # l1_reg=0,
- # frobenius_lipschitz=True,
- # scale_reg="inverse_group_size",
- # subsampling_scheme=1,
- # supress_warning=True,
- # n_iter=1000,
- # tol=1e-3,
- # )
- return modelList
- def getMdlNSCFn() -> modelDict:
- modelList = {}
- modelList.update(
- {
- "NSC MH": GridSearchCV(
- NearestCentroid(metric="manhattan"),
- param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
- cv=3,
- )
- }
- )
- return modelList
- def getMdlsCVFn() -> modelDict:
- # if 'groups' in params:
- # groups = params['groups']
- modelList = {}
- # skf = RepeatedStratifiedKFold(n_splits=3, n_repeats=2)
- # lw = LedoitWolf(store_precision=False, assume_centered=False)
- # oa = OAS(store_precision=False, assume_centered=False)
- # modelList.update({"Lasso Linear CV": LassoCV()})
- # modelList.update({"Lasso L1 CV": LogisticRegressionCV(penalty='l1',
- # solver='liblinear',
- # scoring=make_scorer(roc_auc_score),
- # cv=3,
- # max_iter = 500)})
- #
- modelList.update(
- {
- "Lasso L2 CV": LogisticRegressionCV(
- penalty="l2", scoring=make_scorer(roc_auc_score), cv=3, max_iter=500
- )
- }
- )
- #
- # modelList.update({"Lasso EN CV": LogisticRegressionCV(penalty='elasticnet',
- # solver='saga',
- # l1_ratios=np.arange(0.05,1,0.05),
- # scoring=make_scorer(roc_auc_score),
- # cv=3,
- # max_iter = 500)})
- # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2',
- # solver='lbfgs',
- # C=0.001)})
- # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1',
- # solver='liblinear',
- # C=0.5)})
- modelList.update(
- {
- "LDA CV": GridSearchCV(
- LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
- param_grid={"shrinkage": np.arange(0, 1, 0.05)},
- scoring="roc_auc",
- cv=3,
- )
- }
- )
- LW = LedoitWolf(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA LW": LinearDiscriminantAnalysis(
- solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
- )
- }
- )
- # replace this with graphical_lasso
- # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
- # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
- # covariance_estimator=gl,
- # priors=[0.5,0.5])})
- # add priors as [0.5, 0.5]
- oa = OAS(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA OA": LinearDiscriminantAnalysis(
- solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
- )
- }
- )
- # modelList.update({
- # "LgcRgs GL CV": GridSearchCV(
- # LogisticGroupLasso(groups=groups,
- # scale_reg="inverse_group_size",
- # supress_warning=True,
- # subsampling_scheme=0.1,
- # n_iter=1500
- # ),
- # param_grid = {'l1_reg': np.arange(0,1,0.05),
- # 'group_reg': np.arange(0,1,0.05)
- # )
- # })
- modelList.update(
- {
- "NSC MH": GridSearchCV(
- _NSC.NearestCentroid(metric="manhattan"),
- param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
- cv=3,
- )
- }
- )
- # modelList.update({"NSC EC": GridSearchCV(NearestCentroid(metric='euclidean'),
- # param_grid = {
- # 'shrink_threshold': np.arange(0.05,1,0.05)
- # },
- # cv=3)})
- modelList.update(
- {
- "NSC Custom EC": GridSearchCV(
- _NSC.NearestCentroid(metric="euclidean"),
- param_grid={"shrink_threshold": np.arange(0.05, 2, 0.05)},
- cv=3,
- )
- }
- )
- modelList.update(
- {
- "SVM Lin": GridSearchCV(
- SVC(kernel="linear"),
- param_grid={"C": np.logspace(-10, 3, base=2)},
- scoring="roc_auc",
- )
- }
- )
- modelList.update(
- {
- "SVM Poly2": GridSearchCV(
- SVC(kernel="poly", degree=2),
- param_grid={"C": np.logspace(-10, 3, base=2)},
- scoring="roc_auc",
- )
- }
- )
- # modelList.update(
- # {
- # "SVM Poly3": GridSearchCV(
- # SVC(kernel="poly", degree=3),
- # param_grid={"C": np.logspace(-10, 3, base=2)},
- # scoring="roc_auc",
- # )
- # }
- # )
- #
- # modelList.update({"SVM Poly4": GridSearchCV(SVC(kernel='poly', degree=4),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # })})
- #
- # modelList.update({"SVM RBF": GridSearchCV(SVC(kernel='rbf'),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # })})
- modelList.update({"GNB": GaussianNB()})
- modelList.update({"GNB Prior": GaussianNB(priors=[0.8, 0.20])})
- modelList.update({"Tree Max Depth 2": DecisionTreeClassifier()})
- # modelList.update({"CNB": ComplementNB()})
- # modelList.update({"KNN EC": GridSearchCV(KNeighborsClassifier(metric='euclidean'),
- # param_grid = {
- # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
- # 'weights': ['uniform','distance'],
- # },
- # scoring='roc_auc',
- # cv=3)})
- # modelList.update({"KNN MH": GridSearchCV(KNeighborsClassifier(metric='manhattan'),
- # param_grid = {
- # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
- # 'weights': ['uniform','distance'],
- # })})
- # modelList.update({"RNN EC": GridSearchCV(RadiusNeighborsClassifier(metric='euclidean'),
- # param_grid = {
- # 'radius': np.arange(0.05,1,0.05),
- # 'weights': ['uniform','distance'],
- # })})
- #
- # modelList.update({"RNN MH": GridSearchCV(RadiusNeighborsClassifier(metric='manhattan'),
- # param_grid = {
- # 'radius': np.arange(0.05,1,0.05),
- # 'weights': ['uniform','distance'],
- # })})
- return modelList
- def getNonLinearMdlsCVFn() -> modelDict:
- modelList = {}
- # modelList.update(
- # {
- # "Lasso L2 CV": LogisticRegressionCV(
- # penalty="l2", scoring=make_scorer(roc_auc_score), cv=3, max_iter=500
- # )
- # }
- # )
- #
- # modelList.update({"Lasso EN CV": LogisticRegressionCV(penalty='elasticnet',
- # solver='saga',
- # l1_ratios=np.arange(0.05,1,0.05),
- # scoring=make_scorer(roc_auc_score),
- # cv=3,
- # max_iter = 500)})
- # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2',
- # solver='lbfgs',
- # C=0.001)})
- # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1',
- # solver='liblinear',
- # C=0.5)})
- # modelList.update(
- # {
- # "LDA CV": GridSearchCV(
- # LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
- # param_grid={"shrinkage": np.arange(0, 1, 0.05)},
- # scoring="roc_auc",
- # cv=3,
- # )
- # }
- # )
- #
- # LW = LedoitWolf(store_precision=False, assume_centered=False)
- # modelList.update(
- # {
- # "LDA LW": LinearDiscriminantAnalysis(
- # solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
- # )
- # }
- # )
- # replace this with graphical_lasso
- # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
- # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
- # covariance_estimator=gl,
- # priors=[0.5,0.5])})
- # add priors as [0.5, 0.5]
- # oa = OAS(store_precision=False, assume_centered=False)
- # modelList.update(
- # {
- # "LDA OA": LinearDiscriminantAnalysis(
- # solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
- # )
- # }
- # )
- # modelList.update({
- # "LgcRgs GL CV": GridSearchCV(
- # LogisticGroupLasso(groups=groups,
- # scale_reg="inverse_group_size",
- # supress_warning=True,
- # subsampling_scheme=0.1,
- # n_iter=1500
- # ),
- # param_grid = {'l1_reg': np.arange(0,1,0.05),
- # 'group_reg': np.arange(0,1,0.05)
- # )
- # })
- # modelList.update(
- # {
- # "NSC MH": GridSearchCV(
- # _NSC.NearestCentroid(metric="manhattan"),
- # param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
- # cv=3,
- # )
- # }
- # )
- # modelList.update({"NSC EC": GridSearchCV(NearestCentroid(metric='euclidean'),
- # param_grid = {
- # 'shrink_threshold': np.arange(0.05,1,0.05)
- # },
- # cv=3)})
- # modelList.update(
- # {
- # "NSC Custom EC": GridSearchCV(
- # _NSC.NearestCentroid(metric="euclidean"),
- # param_grid={"shrink_threshold": np.arange(0.05, 2, 0.05)},
- # cv=3,
- # )
- # }
- # )
- modelList.update(
- {
- "SVM Lin": GridSearchCV(
- SVC(kernel="linear"),
- param_grid={"C": np.logspace(-10, 3, base=2)},
- scoring="roc_auc",
- )
- }
- )
- modelList.update(
- {
- "SVM Poly2": GridSearchCV(
- SVC(kernel="poly", degree=2),
- param_grid={"C": np.logspace(-10, 3, base=2)},
- scoring="roc_auc",
- )
- }
- )
- modelList.update(
- {
- "SVM Poly3": GridSearchCV(
- SVC(kernel="poly", degree=3),
- param_grid={"C": np.logspace(-10, 3, base=2)},
- scoring="roc_auc",
- )
- }
- )
- modelList.update(
- {
- "SVM Poly4": GridSearchCV(
- SVC(kernel="poly", degree=4),
- param_grid={"C": np.logspace(-10, 3, base=2)},
- )
- }
- )
- modelList.update(
- {
- "SVM RBF": GridSearchCV(
- SVC(kernel="rbf"), param_grid={"C": np.logspace(-10, 3, base=2)}
- )
- }
- )
- # modelList.update({"GNB": GaussianNB()})
- #
- # modelList.update({"GNB Prior": GaussianNB(priors=[0.8, 0.20])})
- #
- # modelList.update({"Tree": DecisionTreeClassifier()})
- # modelList.update({"CNB": ComplementNB()})
- modelList.update(
- {
- "KNN EC": GridSearchCV(
- KNeighborsClassifier(metric="euclidean"),
- param_grid={
- "n_neighbors": [2, 3, 4, 5, 6, 7, 8],
- "weights": ["uniform", "distance"],
- },
- scoring="roc_auc",
- cv=3,
- )
- }
- )
- modelList.update(
- {
- "KNN MH": GridSearchCV(
- KNeighborsClassifier(metric="manhattan"),
- param_grid={
- "n_neighbors": [2, 3, 4, 5, 6, 7, 8],
- "weights": ["uniform", "distance"],
- },
- cv=3,
- )
- }
- )
- # modelList.update({"RNN EC": GridSearchCV(RadiusNeighborsClassifier(metric='euclidean'),
- # param_grid = {
- # 'radius': np.arange(0.05,1,0.05),
- # 'weights': ['uniform','distance'],
- # },
- # cv=3)})
- #
- # modelList.update({"RNN MH": GridSearchCV(RadiusNeighborsClassifier(metric='manhattan'),
- # param_grid = {
- # 'radius': np.arange(0.05,1,0.05),
- # 'weights': ['uniform','distance'],
- # },
- # cv=3)})
- return modelList
- def getMdlsCVFn_3Clss() -> modelDict:
- # if 'groups' in params:
- # groups = params['groups']
- modelList = {}
- # skf = RepeatedStratifiedKFold(n_splits=3, n_repeats=2)
- # lw = LedoitWolf(store_precision=False, assume_centered=False)
- # oa = OAS(store_precision=False, assume_centered=False)
- # modelList.update({"Lasso Linear CV": LassoCV()})
- # modelList.update({"Lasso L1 CV": LogisticRegressionCV(penalty='l1',
- # solver='liblinear',
- # scoring=make_scorer(roc_auc_score),
- # cv=3,
- # max_iter = 500)})
- #
- modelList.update(
- {
- "Lasso L2 CV": LogisticRegressionCV(
- penalty="l2",
- scoring=make_scorer(f1_score, average="micro"),
- cv=3,
- max_iter=500,
- multi_class="multinomial",
- )
- }
- )
- #
- # modelList.update({"Lasso EN CV": LogisticRegressionCV(penalty='elasticnet',
- # solver='saga',
- # l1_ratios=np.arange(0.05,1,0.05),
- # scoring=make_scorer(roc_auc_score),
- # cv=3,
- # max_iter = 500)})
- # modelList.update({"Lgstic Rgrssn L2": LogisticRegression(penalty='l2',
- # solver='lbfgs',
- # C=0.001)})
- # modelList.update({"Lgstic Rgrssn L1": LogisticRegression(penalty='l1',
- # solver='liblinear',
- # C=0.5)})
- modelList.update(
- {
- "LDA CV": GridSearchCV(
- LinearDiscriminantAnalysis(solver="lsqr"),
- param_grid={"shrinkage": np.arange(0, 1, 0.05)},
- scoring=make_scorer(f1_score, average="micro"),
- cv=3,
- )
- }
- )
- LW = LedoitWolf(store_precision=False, assume_centered=False)
- modelList.update(
- {"LDA LW": LinearDiscriminantAnalysis(solver="lsqr", covariance_estimator=LW)}
- )
- # replace this with graphical_lasso
- # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
- # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
- # covariance_estimator=gl,
- # priors=[0.5,0.5])})
- # add priors as [0.5, 0.5]
- oa = OAS(store_precision=False, assume_centered=False)
- modelList.update(
- {"LDA OA": LinearDiscriminantAnalysis(solver="lsqr", covariance_estimator=oa)}
- )
- # modelList.update({
- # "LgcRgs GL CV": GridSearchCV(
- # LogisticGroupLasso(groups=groups,
- # scale_reg="inverse_group_size",
- # supress_warning=True,
- # subsampling_scheme=0.1,
- # n_iter=1500
- # ),
- # param_grid = {'l1_reg': np.arange(0,1,0.05),
- # 'group_reg': np.arange(0,1,0.05)
- # )
- # })
- modelList.update(
- {
- "NSC MH": GridSearchCV(
- _NSC.NearestCentroid(metric="manhattan"),
- param_grid={"shrink_threshold": np.arange(0.05, 1, 0.05)},
- cv=3,
- )
- }
- )
- # modelList.update({"NSC EC": GridSearchCV(NearestCentroid(metric='euclidean'),
- # param_grid = {
- # 'shrink_threshold': np.arange(0.05,1,0.05)
- # },
- # cv=3)})
- modelList.update(
- {
- "NSC Custom EC": GridSearchCV(
- _NSC.NearestCentroid(metric="euclidean"),
- param_grid={"shrink_threshold": np.arange(0.05, 2, 0.05)},
- cv=3,
- )
- }
- )
- # modelList.update({"SVM Lin": GridSearchCV(SVC(kernel='linear'),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # },
- # scoring='roc_auc')})
- #
- # modelList.update({"SVM Poly2": GridSearchCV(SVC(kernel='poly', degree=2),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # },
- # scoring='roc_auc')})
- #
- # modelList.update({"SVM Poly3": GridSearchCV(SVC(kernel='poly', degree=3),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # },
- # scoring='roc_auc')})
- #
- # modelList.update({"SVM Poly4": GridSearchCV(SVC(kernel='poly', degree=4),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # })})
- #
- # modelList.update({"SVM RBF": GridSearchCV(SVC(kernel='rbf'),
- # param_grid = {
- # 'C': np.logspace(-10,3,base=2)
- # })})
- modelList.update({"GNB": GaussianNB()})
- modelList.update({"GNB Prior": GaussianNB()})
- modelList.update({"Tree": DecisionTreeClassifier()})
- # modelList.update({"CNB": ComplementNB()})
- # modelList.update({"KNN EC": GridSearchCV(KNeighborsClassifier(metric='euclidean'),
- # param_grid = {
- # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
- # 'weights': ['uniform','distance'],
- # },
- # scoring='roc_auc',
- # cv=3)})
- # modelList.update({"KNN MH": GridSearchCV(KNeighborsClassifier(metric='manhattan'),
- # param_grid = {
- # 'n_neighbors': [2, 3, 4, 5, 6, 7, 8],
- # 'weights': ['uniform','distance'],
- # })})
- # modelList.update({"RNN EC": GridSearchCV(RadiusNeighborsClassifier(metric='euclidean'),
- # param_grid = {
- # 'radius': np.arange(0.05,1,0.05),
- # 'weights': ['uniform','distance'],
- # })})
- #
- # modelList.update({"RNN MH": GridSearchCV(RadiusNeighborsClassifier(metric='manhattan'),
- # param_grid = {
- # 'radius': np.arange(0.05,1,0.05),
- # 'weights': ['uniform','distance'],
- # })})
- return modelList
- ################################ NEW ################################
- def getMdlsCSPFn() -> modelDict:
- modelList = {}
- modelList.update(
- {
- "LDA CV": GridSearchCV(
- LinearDiscriminantAnalysis(solver="lsqr", priors=[0.75, 0.25]),
- param_grid={"shrinkage": np.arange(0, 1, 0.05)},
- scoring="roc_auc",
- cv=3,
- )
- }
- )
- LW = LedoitWolf(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA LW": LinearDiscriminantAnalysis(
- solver="lsqr", covariance_estimator=LW, priors=[0.75, 0.25]
- )
- }
- )
- # replace this with graphical_lasso
- # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
- # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
- # covariance_estimator=gl,
- # priors=[0.5,0.5])})
- # add priors as [0.5, 0.5]
- oa = OAS(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA OA": LinearDiscriminantAnalysis(
- solver="lsqr", covariance_estimator=oa, priors=[0.75, 0.25]
- )
- }
- )
- return modelList
- def getMdlsCSPFn_3Clss() -> modelDict:
- modelList = {}
- modelList.update(
- {
- "LDA CV": GridSearchCV(
- LinearDiscriminantAnalysis(solver="lsqr"),
- param_grid={"shrinkage": np.arange(0, 1, 0.05)},
- scoring="roc_auc",
- cv=3,
- )
- }
- )
- LW = LedoitWolf(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA LW": LinearDiscriminantAnalysis(
- solver="lsqr",
- covariance_estimator=LW,
- )
- }
- )
- # replace this with graphical_lasso
- # gl = GraphicalLasso(alpha=0.2, mode='lars', max_iter = 1000)
- # modelList.update({"LDA GL": LinearDiscriminantAnalysis(solver='lsqr',
- # covariance_estimator=gl,
- # priors=[0.5,0.5])})
- # add priors as [0.5, 0.5]
- oa = OAS(store_precision=False, assume_centered=False)
- modelList.update(
- {
- "LDA OA": LinearDiscriminantAnalysis(
- solver="lsqr",
- covariance_estimator=oa,
- )
- }
- )
- return modelList
- def getEnsmblsFn() -> modelDict:
- modelList = {}
- modelList.update(
- {"RF": RandomForestClassifier(class_weight="balanced", max_depth=5)}
- )
- modelList.update({"Bagging": BaggingClassifier()})
- modelList.update({"Extra RF": ExtraTreesClassifier(class_weight="balanced")})
- modelList.update({"Ada": AdaBoostClassifier()})
- # what parameters should I vary? for xgboost
- # n_estimators, subsample, min_samples_leafs,
- modelList.update({"GB": GradientBoostingClassifier()})
- hyperparameter_grid = {
- "n_estimators": [100, 400],
- "max_depth": [3, 6, 9],
- "learning_rate": [0.05, 0.1, 0.20],
- "min_child_weight": [1, 10, 100],
- "gamma": [0, 0.1],
- "reg_alpha": [1e-2, 0.1, 10],
- }
- modelList.update(
- {
- "XGB": GridSearchCV(
- XGBClassifier(booster="gbtree"),
- param_grid=hyperparameter_grid,
- cv=3,
- scoring="roc_auc",
- )
- }
- )
- return modelList
getMdlsMd.py at commit fd85423, under BSD-3-Clause · at the source
Overview
- Berenson-Allen Center for Noninvasive Brain Stimulation, Beth Israel Deaconess Medical Center, Boston, Massachusetts, United States of America
- Neurology Department, Beth Israel Deaconess Medical Center, Boston, Massachusetts, United States of America
- Hebrew SeniorLife Center, Boston, Massachusetts, United States of America
- Radiology Department, Massachusetts General Hospital, Boston, Massachusetts, United States of America
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
fd85423d60ca015f731c57f0769968d72f7d4fe7, 19 February 2026Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
63 files
- src/
__init__.py , Python, 1 line - src/
features/ , Python, 1 line__init__.py - src/
features/ , Python, 49 linescalcAUCMD.py - src/
features/ , Python, 114 linescalc_Class_Ratios_TEP_AU C_Delta.py - src/
features/ , Python, 209 lines, 1 matchcalc_Entropy.py - src/
features/ , Python, 552 linescalc_TEP_AUC_Delta.py - src/
features/ , Python, 363 linescalc_TEP_AUC_PVal.py - src/
features/ , Python, 629 linescalc_TEP_Blocks.py - src/
features/ , Python, 7 linescalc_TEP_RegrssnScr.py - src/
features/ , Python, 325 linescalc_TEP_Variability.py - src/
features/ , Python, 280 linesextract_BP_Bands.py - src/
features/ , Python, 290 linesextract_FOOOF.py - src/
features/ , Python, 216 linesextract_MFP_AUC.py - src/
features/ , Python, 235 linesextract_PSD.py - src/
features/ , Python, 335 linesextract_RS.py - src/
features/ , Python, 263 linesextract_entropy.py - src/
features/ , Python, 45 linesget1020Md.py - src/
features/ , Python, 325 linesgetChnsMd.py - src/
features/ , Python, 92 linesgetROIMd.py - src/
features/ , Python, 2,127 lines, 1 matchgetSpecStatsMd.py - src/
features/ , Python, 307 linesindividual_reliability_a nalysis_Exp1.py - src/
features/ , Python, 323 linesindividual_reliability_a nalysis_Exp2.py - src/
mdls/ , Python, 1,114 lines_DA.py - src/
mdls/ , Python, 373 lines, 1 match_NSC.py - src/
mdls/ , Python, 849 lines, 2 matchesgetMdlsMd.py - src/
mdls/ , Python, 40 linesglm_Estimator.py - src/
mdls/ , Python, 3,250 linesglm_MEPs.py - src/
mdls/ , Python, 3,382 linesglm_TEPs.py - src/
mdls/ , Python, 28 linesglm_scikit.py - src/
pipeline_utils/ , Python, 239 linesget_Pipeline_Md.py - src/
pipeline_utils/ , Python, 56 linessave_Perf_CSV_Md.py - src/
pipelines/ , Python, 4,036 lines, 1 matchcalc_Classification_BP_B ands_Ratio.py - src/
pipelines/ , Python, 3,930 linescalc_Classification_BP_B ands_TStat_MEP.py - src/
pipelines/ , Python, 4,166 linescalc_Classification_Entr opy_Bands_Ratio.py - src/
pipelines/ , Python, 4,026 lines, 1 matchcalc_Classification_Entr opy_Bands_TStat_MEP.py - src/
pipelines/ , Python, 4,059 linescalc_Classification_Entr opy_Ratio_2.py - src/
pipelines/ , Python, 3,831 linescalc_Classification_Entr opy_TStat_MEP.py - src/
pipelines/ , Python, 943 linesplot_Perf_Models.py - src/
pipelines/ , Python, 64 linesrun_Classification_BP_Ba nds.py - src/
pipelines/ , Python, 137 linesrun_Classification_BP_Ba nds_TStat.py - src/
pipelines/ , Python, 64 linesrun_Classification_Entro py_Bands_Ratio.py - src/
pipelines/ , Python, 145 linesrun_Classification_Entro py_Bands_TStat_MEP.py - src/
pipelines/ , Python, 219 linesrun_Classification_Entro py_Ratio.py - src/
pipelines/ , Python, 265 linesrun_Classification_Entro py_Ratio_MEP.py - src/
pipelines/ , Python, 12 linesrun_Entropy_Extraction.p y - src/
pipelines/ , Python, 275 lines, 1 matchrun_label_shift_analysis .py - src/
plttng_fncs/ , Python, 105 linesplot_BS_Hist_Md.py - src/
plttng_fncs/ , Python, 80 linesplot_Histograms.py - src/
plttng_fncs/ , Python, 370 linesplot_Prfrmnc_Bars_Md.py - src/
scorers/ , Python, 211 linesscores.py - src/
utils/ , Python, 1 line__init__.py - src/
utils/ , Python, 74 lineschc_lvl_simulation_Md.py - src/
utils/ , Python, 347 linesgenFeatMeta.py - src/
utils/ , Python, 267 linesgetCSVFiles.py - src/
utils/ , Python, 214 linesgetEEGFiles.py - src/
utils/ , Python, 34 linesgetFeatFnMd.py - src/
utils/ , Python, 509 linesgetTBSPairs.py - src/
utils/ , Python, 56 linesget_Clrs_Md.py - src/
utils/ , Python, 29 lineslog.py - src/
utils/ , Python, 14 lineslogFilter.py - src/
utils/ , Python, 14 linespandasUserFuncs.py - LICENSE, License, 28 lines
- README.md, Text, 1 line
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
- zenodo:19238430, at Zenodo; found in “Data Availability”
Data Availability
The source code used to produce the results and analyses presented in this manuscript are available from GitHub repository: https://
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://
BibTeX
@article{ning2026complex
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/
url = {https://
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/
VL - 22
IS - 4
SP - e1014154
SN - 1553-734X
PB - PLOS
DO - 10.1371/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1371/
"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":
"volume": "22",
"issue": "4",
"page": "e1014154",
"DOI": "10.1371/
"PMID": "42060616",
"PMCID": "PMC13132237",
"ISSN": "1553-734X",
"publisher": "PLOS",
"URL": "https://
"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. ClinicalIn 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 mappingIn 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 medicineIn 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-B
ased Clustering Reveals Robust Neurophysiological Subtypes in Parkinson's Disease. Journal: Movement disorders : official journal of the Movement Disorder SocietyIn 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 neurologyIn 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 neuroscienceIn 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: iScienceIn 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/aIn 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 61 scripts, and 8 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:624e94d02aa5d7f0…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
