py2ls 0.2.4.24__py3-none-any.whl → 0.2.4.26__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.
- py2ls/.DS_Store +0 -0
- py2ls/.git/index +0 -0
- py2ls/corr.py +475 -0
- py2ls/data/.DS_Store +0 -0
- py2ls/data/hyper_param_autogluon_zeroshot2024.json +2383 -0
- py2ls/data/styles/.DS_Store +0 -0
- py2ls/data/styles/example/.DS_Store +0 -0
- py2ls/data/usages_sns.json +6 -1
- py2ls/ec2ls.py +61 -0
- py2ls/ips.py +496 -138
- py2ls/ml2ls.py +994 -288
- py2ls/netfinder.py +16 -20
- py2ls/nl2ls.py +283 -0
- py2ls/plot.py +1244 -158
- {py2ls-0.2.4.24.dist-info → py2ls-0.2.4.26.dist-info}/METADATA +5 -1
- {py2ls-0.2.4.24.dist-info → py2ls-0.2.4.26.dist-info}/RECORD +17 -14
- py2ls/data/usages_pd copy.json +0 -1105
- py2ls/ml2ls copy.py +0 -2906
- {py2ls-0.2.4.24.dist-info → py2ls-0.2.4.26.dist-info}/WHEEL +0 -0
py2ls/ml2ls copy.py
DELETED
@@ -1,2906 +0,0 @@
|
|
1
|
-
from sklearn.ensemble import (
|
2
|
-
RandomForestClassifier,
|
3
|
-
GradientBoostingClassifier,
|
4
|
-
AdaBoostClassifier,
|
5
|
-
BaggingClassifier,
|
6
|
-
)
|
7
|
-
from sklearn.svm import SVC, SVR
|
8
|
-
from sklearn.calibration import CalibratedClassifierCV
|
9
|
-
from sklearn.model_selection import GridSearchCV, StratifiedKFold
|
10
|
-
from sklearn.linear_model import (
|
11
|
-
LassoCV,
|
12
|
-
LogisticRegression,
|
13
|
-
LinearRegression,
|
14
|
-
Lasso,
|
15
|
-
Ridge,
|
16
|
-
RidgeClassifierCV,
|
17
|
-
ElasticNet,
|
18
|
-
)
|
19
|
-
from sklearn.feature_selection import RFE
|
20
|
-
from sklearn.naive_bayes import GaussianNB
|
21
|
-
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
|
22
|
-
import xgboost as xgb # Make sure you have xgboost installed
|
23
|
-
|
24
|
-
from sklearn.model_selection import train_test_split, cross_val_score
|
25
|
-
from sklearn.metrics import (
|
26
|
-
accuracy_score,
|
27
|
-
precision_score,
|
28
|
-
recall_score,
|
29
|
-
f1_score,
|
30
|
-
roc_auc_score,
|
31
|
-
confusion_matrix,
|
32
|
-
matthews_corrcoef,
|
33
|
-
roc_curve,
|
34
|
-
auc,
|
35
|
-
balanced_accuracy_score,
|
36
|
-
precision_recall_curve,
|
37
|
-
average_precision_score,
|
38
|
-
)
|
39
|
-
from imblearn.over_sampling import SMOTE
|
40
|
-
from sklearn.pipeline import Pipeline
|
41
|
-
from collections import defaultdict
|
42
|
-
from sklearn.preprocessing import StandardScaler, OneHotEncoder
|
43
|
-
from typing import Dict, Any, Optional, List, Union
|
44
|
-
import numpy as np
|
45
|
-
import pandas as pd
|
46
|
-
from . import ips
|
47
|
-
from . import plot
|
48
|
-
import matplotlib.pyplot as plt
|
49
|
-
import seaborn as sns
|
50
|
-
|
51
|
-
plt.style.use(str(ips.get_cwd()) + "/data/styles/stylelib/paper.mplstyle")
|
52
|
-
import logging
|
53
|
-
import warnings
|
54
|
-
|
55
|
-
logging.basicConfig(
|
56
|
-
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
57
|
-
)
|
58
|
-
logger = logging.getLogger()
|
59
|
-
|
60
|
-
# Ignore specific warnings (UserWarning in this case)
|
61
|
-
warnings.filterwarnings("ignore", category=UserWarning)
|
62
|
-
from sklearn.tree import DecisionTreeClassifier
|
63
|
-
from sklearn.neighbors import KNeighborsClassifier
|
64
|
-
|
65
|
-
|
66
|
-
def features_knn(
|
67
|
-
x_train: pd.DataFrame, y_train: pd.Series, knn_params: dict
|
68
|
-
) -> pd.DataFrame:
|
69
|
-
"""
|
70
|
-
A distance-based classifier that assigns labels based on the majority label of nearest neighbors.
|
71
|
-
when to use:
|
72
|
-
Effective for small to medium datasets with a low number of features.
|
73
|
-
It does not directly provide feature importances but can be assessed through feature permutation or similar methods.
|
74
|
-
Recommended Use: Effective for datasets with low feature dimensionality and well-separated clusters.
|
75
|
-
|
76
|
-
Fits KNeighborsClassifier and approximates feature influence using permutation importance.
|
77
|
-
"""
|
78
|
-
knn = KNeighborsClassifier(**knn_params)
|
79
|
-
knn.fit(x_train, y_train)
|
80
|
-
importances = permutation_importance(
|
81
|
-
knn, x_train, y_train, n_repeats=30, random_state=1, scoring="accuracy"
|
82
|
-
)
|
83
|
-
return pd.DataFrame(
|
84
|
-
{"feature": x_train.columns, "importance": importances.importances_mean}
|
85
|
-
).sort_values(by="importance", ascending=False)
|
86
|
-
|
87
|
-
|
88
|
-
#! 1. Linear and Regularized Regression Methods
|
89
|
-
# 1.1 Lasso
|
90
|
-
def features_lasso(
|
91
|
-
x_train: pd.DataFrame, y_train: pd.Series, lasso_params: dict
|
92
|
-
) -> np.ndarray:
|
93
|
-
"""
|
94
|
-
Lasso (Least Absolute Shrinkage and Selection Operator):
|
95
|
-
A regularized linear regression method that uses L1 penalty to shrink coefficients, effectively
|
96
|
-
performing feature selection by zeroing out less important ones.
|
97
|
-
"""
|
98
|
-
lasso = LassoCV(**lasso_params)
|
99
|
-
lasso.fit(x_train, y_train)
|
100
|
-
# Get non-zero coefficients and their corresponding features
|
101
|
-
coefficients = lasso.coef_
|
102
|
-
importance_df = pd.DataFrame(
|
103
|
-
{"feature": x_train.columns, "importance": np.abs(coefficients)}
|
104
|
-
)
|
105
|
-
return importance_df[importance_df["importance"] > 0].sort_values(
|
106
|
-
by="importance", ascending=False
|
107
|
-
)
|
108
|
-
|
109
|
-
|
110
|
-
# 1.2 Ridge regression
|
111
|
-
def features_ridge(
|
112
|
-
x_train: pd.DataFrame, y_train: pd.Series, ridge_params: dict
|
113
|
-
) -> np.ndarray:
|
114
|
-
"""
|
115
|
-
Ridge Regression: A linear regression technique that applies L2 regularization, reducing coefficient
|
116
|
-
magnitudes to avoid overfitting, especially with multicollinearity among features.
|
117
|
-
"""
|
118
|
-
from sklearn.linear_model import RidgeCV
|
119
|
-
|
120
|
-
ridge = RidgeCV(**ridge_params)
|
121
|
-
ridge.fit(x_train, y_train)
|
122
|
-
|
123
|
-
# Get the coefficients
|
124
|
-
coefficients = ridge.coef_
|
125
|
-
|
126
|
-
# Create a DataFrame to hold feature importance
|
127
|
-
importance_df = pd.DataFrame(
|
128
|
-
{"feature": x_train.columns, "importance": np.abs(coefficients)}
|
129
|
-
)
|
130
|
-
return importance_df[importance_df["importance"] > 0].sort_values(
|
131
|
-
by="importance", ascending=False
|
132
|
-
)
|
133
|
-
|
134
|
-
|
135
|
-
# 1.3 Elastic Net(Enet)
|
136
|
-
def features_enet(
|
137
|
-
x_train: pd.DataFrame, y_train: pd.Series, enet_params: dict
|
138
|
-
) -> np.ndarray:
|
139
|
-
"""
|
140
|
-
Elastic Net (Enet): Combines L1 and L2 penalties (lasso and ridge) in a linear model, beneficial
|
141
|
-
when features are highly correlated or for datasets with more features than samples.
|
142
|
-
"""
|
143
|
-
from sklearn.linear_model import ElasticNetCV
|
144
|
-
|
145
|
-
enet = ElasticNetCV(**enet_params)
|
146
|
-
enet.fit(x_train, y_train)
|
147
|
-
# Get the coefficients
|
148
|
-
coefficients = enet.coef_
|
149
|
-
# Create a DataFrame to hold feature importance
|
150
|
-
importance_df = pd.DataFrame(
|
151
|
-
{"feature": x_train.columns, "importance": np.abs(coefficients)}
|
152
|
-
)
|
153
|
-
return importance_df[importance_df["importance"] > 0].sort_values(
|
154
|
-
by="importance", ascending=False
|
155
|
-
)
|
156
|
-
|
157
|
-
|
158
|
-
# 1.4 Partial Least Squares Regression for Generalized Linear Models (plsRglm): Combines regression and
|
159
|
-
# feature reduction, useful for high-dimensional data with correlated features, such as genomics.
|
160
|
-
|
161
|
-
#! 2.Generalized Linear Models and Extensions
|
162
|
-
# 2.1
|
163
|
-
|
164
|
-
|
165
|
-
#!3.Tree-Based and Ensemble Methods
|
166
|
-
# 3.1 Random Forest(RF)
|
167
|
-
def features_rf(
|
168
|
-
x_train: pd.DataFrame, y_train: pd.Series, rf_params: dict
|
169
|
-
) -> np.ndarray:
|
170
|
-
"""
|
171
|
-
An ensemble of decision trees that combines predictions from multiple trees for classification or
|
172
|
-
regression, effective with high-dimensional, complex datasets.
|
173
|
-
when to use:
|
174
|
-
Handles high-dimensional data well.
|
175
|
-
Robust to overfitting due to averaging of multiple trees.
|
176
|
-
Provides feature importance, which can help in understanding the influence of different genes.
|
177
|
-
Fit Random Forest and return sorted feature importances.
|
178
|
-
Recommended Use: Great for classification problems, especially when you have many features (genes).
|
179
|
-
"""
|
180
|
-
rf = RandomForestClassifier(**rf_params)
|
181
|
-
rf.fit(x_train, y_train)
|
182
|
-
return pd.DataFrame(
|
183
|
-
{"feature": x_train.columns, "importance": rf.featuress_}
|
184
|
-
).sort_values(by="importance", ascending=False)
|
185
|
-
|
186
|
-
|
187
|
-
# 3.2 Gradient Boosting Trees
|
188
|
-
def features_gradient_boosting(
|
189
|
-
x_train: pd.DataFrame, y_train: pd.Series, gb_params: dict
|
190
|
-
) -> pd.DataFrame:
|
191
|
-
"""
|
192
|
-
An ensemble of decision trees that combines predictions from multiple trees for classification or regression, effective with
|
193
|
-
high-dimensional, complex datasets.
|
194
|
-
Gradient Boosting
|
195
|
-
Strengths:
|
196
|
-
High predictive accuracy and works well for both classification and regression.
|
197
|
-
Can handle a mixture of numerical and categorical features.
|
198
|
-
Recommended Use:
|
199
|
-
Effective for complex relationships and when you need a powerful predictive model.
|
200
|
-
Fit Gradient Boosting classifier and return sorted feature importances.
|
201
|
-
Recommended Use: Effective for complex datasets with many features (genes).
|
202
|
-
"""
|
203
|
-
gb = GradientBoostingClassifier(**gb_params)
|
204
|
-
gb.fit(x_train, y_train)
|
205
|
-
return pd.DataFrame(
|
206
|
-
{"feature": x_train.columns, "importance": gb.feature_importances_}
|
207
|
-
).sort_values(by="importance", ascending=False)
|
208
|
-
|
209
|
-
|
210
|
-
# 3.3 XGBoost
|
211
|
-
def features_xgb(
|
212
|
-
x_train: pd.DataFrame, y_train: pd.Series, xgb_params: dict
|
213
|
-
) -> pd.DataFrame:
|
214
|
-
"""
|
215
|
-
XGBoost: An advanced gradient boosting technique, faster and more efficient than GBM, with excellent predictive performance on structured data.
|
216
|
-
"""
|
217
|
-
import xgboost as xgb
|
218
|
-
|
219
|
-
xgb_model = xgb.XGBClassifier(**xgb_params)
|
220
|
-
xgb_model.fit(x_train, y_train)
|
221
|
-
return pd.DataFrame(
|
222
|
-
{"feature": x_train.columns, "importance": xgb_model.feature_importances_}
|
223
|
-
).sort_values(by="importance", ascending=False)
|
224
|
-
|
225
|
-
|
226
|
-
# 3.4.decision tree
|
227
|
-
def features_decision_tree(
|
228
|
-
x_train: pd.DataFrame, y_train: pd.Series, dt_params: dict
|
229
|
-
) -> pd.DataFrame:
|
230
|
-
"""
|
231
|
-
A single decision tree classifier effective for identifying key decision boundaries in data.
|
232
|
-
when to use:
|
233
|
-
Good for capturing non-linear patterns.
|
234
|
-
Provides feature importance scores for each feature, though it may overfit on small datasets.
|
235
|
-
Efficient for low to medium-sized datasets, where interpretability of decisions is key.
|
236
|
-
Recommended Use: Useful for interpretable feature importance analysis in smaller or balanced datasets.
|
237
|
-
|
238
|
-
Fits DecisionTreeClassifier and returns sorted feature importances.
|
239
|
-
"""
|
240
|
-
dt = DecisionTreeClassifier(**dt_params)
|
241
|
-
dt.fit(x_train, y_train)
|
242
|
-
return pd.DataFrame(
|
243
|
-
{"feature": x_train.columns, "importance": dt.feature_importances_}
|
244
|
-
).sort_values(by="importance", ascending=False)
|
245
|
-
|
246
|
-
|
247
|
-
# 3.5 bagging
|
248
|
-
def features_bagging(
|
249
|
-
x_train: pd.DataFrame, y_train: pd.Series, bagging_params: dict
|
250
|
-
) -> pd.DataFrame:
|
251
|
-
"""
|
252
|
-
A bagging ensemble of models, often used with weak learners like decision trees, to reduce variance.
|
253
|
-
when to use:
|
254
|
-
Helps reduce overfitting, especially on high-variance models.
|
255
|
-
Effective when the dataset has numerous features and may benefit from ensemble stability.
|
256
|
-
Recommended Use: Beneficial for high-dimensional or noisy datasets needing ensemble stability.
|
257
|
-
|
258
|
-
Fits BaggingClassifier and returns averaged feature importances from underlying estimators if available.
|
259
|
-
"""
|
260
|
-
bagging = BaggingClassifier(**bagging_params)
|
261
|
-
bagging.fit(x_train, y_train)
|
262
|
-
|
263
|
-
# Calculate feature importance by averaging importances across estimators, if feature_importances_ is available.
|
264
|
-
if hasattr(bagging.estimators_[0], "feature_importances_"):
|
265
|
-
importances = np.mean(
|
266
|
-
[estimator.feature_importances_ for estimator in bagging.estimators_],
|
267
|
-
axis=0,
|
268
|
-
)
|
269
|
-
return pd.DataFrame(
|
270
|
-
{"feature": x_train.columns, "importance": importances}
|
271
|
-
).sort_values(by="importance", ascending=False)
|
272
|
-
else:
|
273
|
-
# If the base estimator does not support feature importances, fallback to permutation importance.
|
274
|
-
importances = permutation_importance(
|
275
|
-
bagging, x_train, y_train, n_repeats=30, random_state=1, scoring="accuracy"
|
276
|
-
)
|
277
|
-
return pd.DataFrame(
|
278
|
-
{"feature": x_train.columns, "importance": importances.importances_mean}
|
279
|
-
).sort_values(by="importance", ascending=False)
|
280
|
-
|
281
|
-
|
282
|
-
#! 4.Support Vector Machines
|
283
|
-
def features_svm(
|
284
|
-
x_train: pd.DataFrame, y_train: pd.Series, rfe_params: dict
|
285
|
-
) -> np.ndarray:
|
286
|
-
"""
|
287
|
-
Suitable for classification tasks where the number of features is much larger than the number of samples.
|
288
|
-
1. Effective in high-dimensional spaces and with clear margin of separation.
|
289
|
-
2. Works well for both linear and non-linear classification (using kernel functions).
|
290
|
-
Select features using RFE with SVM.When combined with SVM, RFE selects features that are most critical for the decision boundary,
|
291
|
-
helping reduce the dataset to a more manageable size without losing much predictive power.
|
292
|
-
SVM (Support Vector Machines),supports various kernels (linear, rbf, poly, and sigmoid), is good at handling high-dimensional
|
293
|
-
data and finding an optimal decision boundary between classes, especially when using the right kernel.
|
294
|
-
kernel: ["linear", "rbf", "poly", "sigmoid"]
|
295
|
-
'linear': simplest kernel that attempts to separate data by drawing a straight line (or hyperplane) between classes. It is effective
|
296
|
-
when the data is linearly separable, meaning the classes can be well divided by a straight boundary.
|
297
|
-
Advantages:
|
298
|
-
- Computationally efficient for large datasets.
|
299
|
-
- Works well when the number of features is high, which is common in genomic data where you may have thousands of genes
|
300
|
-
as features.
|
301
|
-
'rbf': a nonlinear kernel that maps the input data into a higher-dimensional space to find a decision boundary. It works well for
|
302
|
-
data that is not linearly separable in its original space.
|
303
|
-
Advantages:
|
304
|
-
- Handles nonlinear relationships between features and classes
|
305
|
-
- Often better than a linear kernel when there is no clear linear decision boundary in the data.
|
306
|
-
'poly': Polynomial Kernel: computes similarity between data points based on polynomial functions of the input features. It can model
|
307
|
-
interactions between features to a certain degree, depending on the polynomial degree chosen.
|
308
|
-
Advantages:
|
309
|
-
- Allows modeling of feature interactions.
|
310
|
-
- Can fit more complex relationships compared to linear models.
|
311
|
-
'sigmoid': similar to the activation function in neural networks, and it works well when the data follows an S-shaped decision boundary.
|
312
|
-
Advantages:
|
313
|
-
- Can approximate the behavior of neural networks.
|
314
|
-
- Use case: It’s not as widely used as the RBF or linear kernel but can be explored when there is some evidence of non-linear
|
315
|
-
S-shaped relationships.
|
316
|
-
"""
|
317
|
-
# SVM (Support Vector Machines)
|
318
|
-
svc = SVC(kernel=rfe_params["kernel"]) # ["linear", "rbf", "poly", "sigmoid"]
|
319
|
-
# RFE(Recursive Feature Elimination)
|
320
|
-
selector = RFE(svc, n_features_to_select=rfe_params["n_features_to_select"])
|
321
|
-
selector.fit(x_train, y_train)
|
322
|
-
return x_train.columns[selector.support_]
|
323
|
-
|
324
|
-
|
325
|
-
#! 5.Bayesian and Probabilistic Methods
|
326
|
-
def features_naive_bayes(x_train: pd.DataFrame, y_train: pd.Series) -> list:
|
327
|
-
"""
|
328
|
-
Naive Bayes: A probabilistic classifier based on Bayes' theorem, assuming independence between features, simple and fast, especially
|
329
|
-
effective for text classification and other high-dimensional data.
|
330
|
-
"""
|
331
|
-
from sklearn.naive_bayes import GaussianNB
|
332
|
-
|
333
|
-
nb = GaussianNB()
|
334
|
-
nb.fit(x_train, y_train)
|
335
|
-
probabilities = nb.predict_proba(x_train)
|
336
|
-
# Limit the number of features safely, choosing the lesser of half the features or all columns
|
337
|
-
n_features = min(x_train.shape[1] // 2, len(x_train.columns))
|
338
|
-
|
339
|
-
# Sort probabilities, then map to valid column indices
|
340
|
-
sorted_indices = np.argsort(probabilities.max(axis=1))[:n_features]
|
341
|
-
|
342
|
-
# Ensure indices are within the column bounds of x_train
|
343
|
-
valid_indices = sorted_indices[sorted_indices < len(x_train.columns)]
|
344
|
-
|
345
|
-
return x_train.columns[valid_indices]
|
346
|
-
|
347
|
-
|
348
|
-
#! 6.Linear Discriminant Analysis (LDA)
|
349
|
-
def features_lda(x_train: pd.DataFrame, y_train: pd.Series) -> list:
|
350
|
-
"""
|
351
|
-
Linear Discriminant Analysis (LDA): Projects data onto a lower-dimensional space to maximize class separability, often used as a dimensionality
|
352
|
-
reduction technique before classification on high-dimensional data.
|
353
|
-
"""
|
354
|
-
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
|
355
|
-
|
356
|
-
lda = LinearDiscriminantAnalysis()
|
357
|
-
lda.fit(x_train, y_train)
|
358
|
-
coef = lda.coef_.flatten()
|
359
|
-
# Create a DataFrame to hold feature importance
|
360
|
-
importance_df = pd.DataFrame(
|
361
|
-
{"feature": x_train.columns, "importance": np.abs(coef)}
|
362
|
-
)
|
363
|
-
|
364
|
-
return importance_df[importance_df["importance"] > 0].sort_values(
|
365
|
-
by="importance", ascending=False
|
366
|
-
)
|
367
|
-
|
368
|
-
|
369
|
-
def features_adaboost(
|
370
|
-
x_train: pd.DataFrame, y_train: pd.Series, adaboost_params: dict
|
371
|
-
) -> pd.DataFrame:
|
372
|
-
"""
|
373
|
-
AdaBoost
|
374
|
-
Strengths:
|
375
|
-
Combines multiple weak learners to create a strong classifier.
|
376
|
-
Focuses on examples that are hard to classify, improving overall performance.
|
377
|
-
Recommended Use:
|
378
|
-
Can be effective for boosting weak models in a genomics context.
|
379
|
-
Fit AdaBoost classifier and return sorted feature importances.
|
380
|
-
Recommended Use: Great for classification problems with a large number of features (genes).
|
381
|
-
"""
|
382
|
-
ada = AdaBoostClassifier(**adaboost_params)
|
383
|
-
ada.fit(x_train, y_train)
|
384
|
-
return pd.DataFrame(
|
385
|
-
{"feature": x_train.columns, "importance": ada.feature_importances_}
|
386
|
-
).sort_values(by="importance", ascending=False)
|
387
|
-
|
388
|
-
|
389
|
-
import torch
|
390
|
-
import torch.nn as nn
|
391
|
-
import torch.optim as optim
|
392
|
-
from torch.utils.data import DataLoader, TensorDataset
|
393
|
-
from skorch import NeuralNetClassifier # sklearn compatible
|
394
|
-
|
395
|
-
|
396
|
-
class DNNClassifier(nn.Module):
|
397
|
-
def __init__(self, input_dim, hidden_dim=128, output_dim=2, dropout_rate=0.5):
|
398
|
-
super(DNNClassifier, self).__init__()
|
399
|
-
|
400
|
-
self.hidden_layer1 = nn.Sequential(
|
401
|
-
nn.Linear(input_dim, hidden_dim),
|
402
|
-
nn.ReLU(),
|
403
|
-
nn.Dropout(dropout_rate),
|
404
|
-
nn.Linear(hidden_dim, hidden_dim),
|
405
|
-
nn.ReLU(),
|
406
|
-
)
|
407
|
-
|
408
|
-
self.hidden_layer2 = nn.Sequential(
|
409
|
-
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Dropout(dropout_rate)
|
410
|
-
)
|
411
|
-
|
412
|
-
# Adding a residual connection between hidden layers
|
413
|
-
self.residual = nn.Linear(input_dim, hidden_dim)
|
414
|
-
|
415
|
-
self.output_layer = nn.Sequential(
|
416
|
-
nn.Linear(hidden_dim, output_dim), nn.Softmax(dim=1)
|
417
|
-
)
|
418
|
-
|
419
|
-
def forward(self, x):
|
420
|
-
residual = self.residual(x)
|
421
|
-
x = self.hidden_layer1(x)
|
422
|
-
x = x + residual # Residual connection
|
423
|
-
x = self.hidden_layer2(x)
|
424
|
-
x = self.output_layer(x)
|
425
|
-
return x
|
426
|
-
|
427
|
-
|
428
|
-
def validate_classifier(
|
429
|
-
clf,
|
430
|
-
x_train: pd.DataFrame,
|
431
|
-
y_train: pd.Series,
|
432
|
-
x_test: pd.DataFrame,
|
433
|
-
y_test: pd.Series,
|
434
|
-
metrics: list = ["accuracy", "precision", "recall", "f1", "roc_auc"],
|
435
|
-
cv_folds: int = 5,
|
436
|
-
) -> dict:
|
437
|
-
"""
|
438
|
-
Perform cross-validation for a given classifier and return average scores for specified metrics on training data.
|
439
|
-
Then fit the best model on the full training data and evaluate it on the test set.
|
440
|
-
|
441
|
-
Parameters:
|
442
|
-
- clf: The classifier to be validated.
|
443
|
-
- x_train: Training features.
|
444
|
-
- y_train: Training labels.
|
445
|
-
- x_test: Test features.
|
446
|
-
- y_test: Test labels.
|
447
|
-
- metrics: List of metrics to evaluate (e.g., ['accuracy', 'roc_auc']).
|
448
|
-
- cv_folds: Number of cross-validation folds.
|
449
|
-
|
450
|
-
Returns:
|
451
|
-
- results: Dictionary containing average cv_train_scores and cv_test_scores.
|
452
|
-
"""
|
453
|
-
cv_train_scores = {metric: [] for metric in metrics}
|
454
|
-
skf = StratifiedKFold(n_splits=cv_folds)
|
455
|
-
# Perform cross-validation
|
456
|
-
for metric in metrics:
|
457
|
-
try:
|
458
|
-
if metric == "roc_auc" and len(set(y_train)) == 2:
|
459
|
-
scores = cross_val_score(
|
460
|
-
clf, x_train, y_train, cv=skf, scoring="roc_auc"
|
461
|
-
)
|
462
|
-
cv_train_scores[metric] = (
|
463
|
-
np.nanmean(scores) if not np.isnan(scores).all() else float("nan")
|
464
|
-
)
|
465
|
-
else:
|
466
|
-
score = cross_val_score(clf, x_train, y_train, cv=skf, scoring=metric)
|
467
|
-
cv_train_scores[metric] = score.mean()
|
468
|
-
except Exception as e:
|
469
|
-
cv_train_scores[metric] = float("nan")
|
470
|
-
clf.fit(x_train, y_train)
|
471
|
-
|
472
|
-
# Evaluate on the test set
|
473
|
-
cv_test_scores = {}
|
474
|
-
for metric in metrics:
|
475
|
-
if metric == "roc_auc" and len(set(y_test)) == 2:
|
476
|
-
try:
|
477
|
-
y_prob = clf.predict_proba(x_test)[:, 1]
|
478
|
-
cv_test_scores[metric] = roc_auc_score(y_test, y_prob)
|
479
|
-
except AttributeError:
|
480
|
-
cv_test_scores[metric] = float("nan")
|
481
|
-
else:
|
482
|
-
score_func = globals().get(
|
483
|
-
f"{metric}_score"
|
484
|
-
) # Fetching the appropriate scoring function
|
485
|
-
if score_func:
|
486
|
-
try:
|
487
|
-
y_pred = clf.predict(x_test)
|
488
|
-
cv_test_scores[metric] = score_func(y_test, y_pred)
|
489
|
-
except Exception as e:
|
490
|
-
cv_test_scores[metric] = float("nan")
|
491
|
-
|
492
|
-
# Combine results
|
493
|
-
results = {"cv_train_scores": cv_train_scores, "cv_test_scores": cv_test_scores}
|
494
|
-
return results
|
495
|
-
|
496
|
-
|
497
|
-
def get_models(
|
498
|
-
random_state=1,
|
499
|
-
cls=[
|
500
|
-
"lasso",
|
501
|
-
"ridge",
|
502
|
-
"Elastic Net(Enet)",
|
503
|
-
"gradient Boosting",
|
504
|
-
"Random forest (rf)",
|
505
|
-
"XGBoost (xgb)",
|
506
|
-
"Support Vector Machine(svm)",
|
507
|
-
"naive bayes",
|
508
|
-
"Linear Discriminant Analysis (lda)",
|
509
|
-
"adaboost",
|
510
|
-
"DecisionTree",
|
511
|
-
"KNeighbors",
|
512
|
-
"Bagging",
|
513
|
-
],
|
514
|
-
):
|
515
|
-
from sklearn.ensemble import (
|
516
|
-
RandomForestClassifier,
|
517
|
-
GradientBoostingClassifier,
|
518
|
-
AdaBoostClassifier,
|
519
|
-
BaggingClassifier,
|
520
|
-
)
|
521
|
-
from sklearn.svm import SVC
|
522
|
-
from sklearn.linear_model import (
|
523
|
-
LogisticRegression,
|
524
|
-
Lasso,
|
525
|
-
RidgeClassifierCV,
|
526
|
-
ElasticNet,
|
527
|
-
)
|
528
|
-
from sklearn.naive_bayes import GaussianNB
|
529
|
-
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
|
530
|
-
import xgboost as xgb
|
531
|
-
from sklearn.tree import DecisionTreeClassifier
|
532
|
-
from sklearn.neighbors import KNeighborsClassifier
|
533
|
-
|
534
|
-
res_cls = {}
|
535
|
-
model_all = {
|
536
|
-
"Lasso": LogisticRegression(
|
537
|
-
penalty="l1", solver="saga", random_state=random_state
|
538
|
-
),
|
539
|
-
"Ridge": RidgeClassifierCV(),
|
540
|
-
"Elastic Net (Enet)": ElasticNet(random_state=random_state),
|
541
|
-
"Gradient Boosting": GradientBoostingClassifier(random_state=random_state),
|
542
|
-
"Random Forest (RF)": RandomForestClassifier(random_state=random_state),
|
543
|
-
"XGBoost (XGB)": xgb.XGBClassifier(random_state=random_state),
|
544
|
-
"Support Vector Machine (SVM)": SVC(kernel="rbf", probability=True),
|
545
|
-
"Naive Bayes": GaussianNB(),
|
546
|
-
"Linear Discriminant Analysis (LDA)": LinearDiscriminantAnalysis(),
|
547
|
-
"AdaBoost": AdaBoostClassifier(random_state=random_state, algorithm="SAMME"),
|
548
|
-
"DecisionTree": DecisionTreeClassifier(),
|
549
|
-
"KNeighbors": KNeighborsClassifier(n_neighbors=5),
|
550
|
-
"Bagging": BaggingClassifier(),
|
551
|
-
}
|
552
|
-
print("Using default models:")
|
553
|
-
for cls_name in cls:
|
554
|
-
cls_name = ips.strcmp(cls_name, list(model_all.keys()))[0]
|
555
|
-
res_cls[cls_name] = model_all[cls_name]
|
556
|
-
print(f"- {cls_name}")
|
557
|
-
return res_cls
|
558
|
-
|
559
|
-
|
560
|
-
def get_features(
|
561
|
-
X: Union[pd.DataFrame, np.ndarray], # n_samples X n_features
|
562
|
-
y: Union[pd.Series, np.ndarray, list], # n_samples X n_features
|
563
|
-
test_size: float = 0.2,
|
564
|
-
random_state: int = 1,
|
565
|
-
n_features: int = 10,
|
566
|
-
fill_missing=True,
|
567
|
-
rf_params: Optional[Dict] = None,
|
568
|
-
rfe_params: Optional[Dict] = None,
|
569
|
-
lasso_params: Optional[Dict] = None,
|
570
|
-
ridge_params: Optional[Dict] = None,
|
571
|
-
enet_params: Optional[Dict] = None,
|
572
|
-
gb_params: Optional[Dict] = None,
|
573
|
-
adaboost_params: Optional[Dict] = None,
|
574
|
-
xgb_params: Optional[Dict] = None,
|
575
|
-
dt_params: Optional[Dict] = None,
|
576
|
-
bagging_params: Optional[Dict] = None,
|
577
|
-
knn_params: Optional[Dict] = None,
|
578
|
-
cls: list = [
|
579
|
-
"lasso",
|
580
|
-
"ridge",
|
581
|
-
"Elastic Net(Enet)",
|
582
|
-
"gradient Boosting",
|
583
|
-
"Random forest (rf)",
|
584
|
-
"XGBoost (xgb)",
|
585
|
-
"Support Vector Machine(svm)",
|
586
|
-
"naive bayes",
|
587
|
-
"Linear Discriminant Analysis (lda)",
|
588
|
-
"adaboost",
|
589
|
-
"DecisionTree",
|
590
|
-
"KNeighbors",
|
591
|
-
"Bagging",
|
592
|
-
],
|
593
|
-
metrics: Optional[List[str]] = None,
|
594
|
-
cv_folds: int = 5,
|
595
|
-
strict: bool = False,
|
596
|
-
n_shared: int = 2, # 只要有两个方法有重合,就纳入common genes
|
597
|
-
use_selected_features: bool = True,
|
598
|
-
plot_: bool = True,
|
599
|
-
dir_save: str = "./",
|
600
|
-
) -> dict:
|
601
|
-
"""
|
602
|
-
Master function to perform feature selection and validate models.
|
603
|
-
"""
|
604
|
-
from sklearn.compose import ColumnTransformer
|
605
|
-
from sklearn.preprocessing import StandardScaler, OneHotEncoder
|
606
|
-
|
607
|
-
# Ensure X and y are DataFrames/Series for consistency
|
608
|
-
if isinstance(X, np.ndarray):
|
609
|
-
X = pd.DataFrame(X)
|
610
|
-
if isinstance(y, (np.ndarray, list)):
|
611
|
-
y = pd.Series(y)
|
612
|
-
|
613
|
-
# fill na
|
614
|
-
if fill_missing:
|
615
|
-
ips.df_fillna(data=X, method="knn", inplace=True, axis=0)
|
616
|
-
if isinstance(y, str) and y in X.columns:
|
617
|
-
y_col_name = y
|
618
|
-
y = X[y]
|
619
|
-
y = ips.df_encoder(pd.DataFrame(y), method="dummy")
|
620
|
-
X = X.drop(y_col_name, axis=1)
|
621
|
-
else:
|
622
|
-
y = ips.df_encoder(pd.DataFrame(y), method="dummy").values.ravel()
|
623
|
-
y = y.loc[X.index] # Align y with X after dropping rows with missing values in X
|
624
|
-
y = y.ravel() if isinstance(y, np.ndarray) else y.values.ravel()
|
625
|
-
|
626
|
-
if X.shape[0] != len(y):
|
627
|
-
raise ValueError("X and y must have the same number of samples (rows).")
|
628
|
-
|
629
|
-
# #! # Check for non-numeric columns in X and apply one-hot encoding if needed
|
630
|
-
# Check if any column in X is non-numeric
|
631
|
-
if any(not np.issubdtype(dtype, np.number) for dtype in X.dtypes):
|
632
|
-
X = pd.get_dummies(X, drop_first=True)
|
633
|
-
print(X.shape)
|
634
|
-
|
635
|
-
# #!alternative: # Identify categorical and numerical columns
|
636
|
-
# categorical_cols = X.select_dtypes(include=["object", "category"]).columns
|
637
|
-
# numerical_cols = X.select_dtypes(include=["number"]).columns
|
638
|
-
|
639
|
-
# # Define preprocessing pipeline
|
640
|
-
# preprocessor = ColumnTransformer(
|
641
|
-
# transformers=[
|
642
|
-
# ("num", StandardScaler(), numerical_cols),
|
643
|
-
# ("cat", OneHotEncoder(drop="first", handle_unknown="ignore"), categorical_cols),
|
644
|
-
# ]
|
645
|
-
# )
|
646
|
-
# # Preprocess the data
|
647
|
-
# X = preprocessor.fit_transform(X)
|
648
|
-
|
649
|
-
# Split data into training and test sets
|
650
|
-
x_train, x_test, y_train, y_test = train_test_split(
|
651
|
-
X, y, test_size=test_size, random_state=random_state
|
652
|
-
)
|
653
|
-
# Standardize features
|
654
|
-
scaler = StandardScaler()
|
655
|
-
x_train_scaled = scaler.fit_transform(x_train)
|
656
|
-
x_test_scaled = scaler.transform(x_test)
|
657
|
-
|
658
|
-
# Convert back to DataFrame for consistency
|
659
|
-
x_train = pd.DataFrame(x_train_scaled, columns=x_train.columns)
|
660
|
-
x_test = pd.DataFrame(x_test_scaled, columns=x_test.columns)
|
661
|
-
|
662
|
-
rf_defaults = {"n_estimators": 100, "random_state": random_state}
|
663
|
-
rfe_defaults = {"kernel": "linear", "n_features_to_select": n_features}
|
664
|
-
lasso_defaults = {"alphas": np.logspace(-4, 4, 100), "cv": 10}
|
665
|
-
ridge_defaults = {"alphas": np.logspace(-4, 4, 100), "cv": 10}
|
666
|
-
enet_defaults = {"alphas": np.logspace(-4, 4, 100), "cv": 10}
|
667
|
-
xgb_defaults = {
|
668
|
-
"n_estimators": 100,
|
669
|
-
"use_label_encoder": False,
|
670
|
-
"eval_metric": "logloss",
|
671
|
-
"random_state": random_state,
|
672
|
-
}
|
673
|
-
gb_defaults = {"n_estimators": 100, "random_state": random_state}
|
674
|
-
adaboost_defaults = {"n_estimators": 50, "random_state": random_state}
|
675
|
-
dt_defaults = {"max_depth": None, "random_state": random_state}
|
676
|
-
bagging_defaults = {"n_estimators": 50, "random_state": random_state}
|
677
|
-
knn_defaults = {"n_neighbors": 5}
|
678
|
-
rf_params, rfe_params = rf_params or rf_defaults, rfe_params or rfe_defaults
|
679
|
-
lasso_params, ridge_params = (
|
680
|
-
lasso_params or lasso_defaults,
|
681
|
-
ridge_params or ridge_defaults,
|
682
|
-
)
|
683
|
-
enet_params, xgb_params = enet_params or enet_defaults, xgb_params or xgb_defaults
|
684
|
-
gb_params, adaboost_params = (
|
685
|
-
gb_params or gb_defaults,
|
686
|
-
adaboost_params or adaboost_defaults,
|
687
|
-
)
|
688
|
-
dt_params = dt_params or dt_defaults
|
689
|
-
bagging_params = bagging_params or bagging_defaults
|
690
|
-
knn_params = knn_params or knn_defaults
|
691
|
-
|
692
|
-
cls_ = [
|
693
|
-
"lasso",
|
694
|
-
"ridge",
|
695
|
-
"Elastic Net(Enet)",
|
696
|
-
"Gradient Boosting",
|
697
|
-
"Random Forest (rf)",
|
698
|
-
"XGBoost (xgb)",
|
699
|
-
"Support Vector Machine(svm)",
|
700
|
-
"Naive Bayes",
|
701
|
-
"Linear Discriminant Analysis (lda)",
|
702
|
-
"adaboost",
|
703
|
-
]
|
704
|
-
cls = [ips.strcmp(i, cls_)[0] for i in cls]
|
705
|
-
|
706
|
-
# Lasso Feature Selection
|
707
|
-
lasso_importances = (
|
708
|
-
features_lasso(x_train, y_train, lasso_params)
|
709
|
-
if "lasso" in cls
|
710
|
-
else pd.DataFrame()
|
711
|
-
)
|
712
|
-
lasso_selected_features = (
|
713
|
-
lasso_importances.head(n_features)["feature"].values if "lasso" in cls else []
|
714
|
-
)
|
715
|
-
# Ridge
|
716
|
-
ridge_importances = (
|
717
|
-
features_ridge(x_train, y_train, ridge_params)
|
718
|
-
if "ridge" in cls
|
719
|
-
else pd.DataFrame()
|
720
|
-
)
|
721
|
-
selected_ridge_features = (
|
722
|
-
ridge_importances.head(n_features)["feature"].values if "ridge" in cls else []
|
723
|
-
)
|
724
|
-
# Elastic Net
|
725
|
-
enet_importances = (
|
726
|
-
features_enet(x_train, y_train, enet_params)
|
727
|
-
if "Enet" in cls
|
728
|
-
else pd.DataFrame()
|
729
|
-
)
|
730
|
-
selected_enet_features = (
|
731
|
-
enet_importances.head(n_features)["feature"].values if "Enet" in cls else []
|
732
|
-
)
|
733
|
-
# Random Forest Feature Importance
|
734
|
-
rf_importances = (
|
735
|
-
features_rf(x_train, y_train, rf_params)
|
736
|
-
if "Random Forest" in cls
|
737
|
-
else pd.DataFrame()
|
738
|
-
)
|
739
|
-
top_rf_features = (
|
740
|
-
rf_importances.head(n_features)["feature"].values
|
741
|
-
if "Random Forest" in cls
|
742
|
-
else []
|
743
|
-
)
|
744
|
-
# Gradient Boosting Feature Importance
|
745
|
-
gb_importances = (
|
746
|
-
features_gradient_boosting(x_train, y_train, gb_params)
|
747
|
-
if "Gradient Boosting" in cls
|
748
|
-
else pd.DataFrame()
|
749
|
-
)
|
750
|
-
top_gb_features = (
|
751
|
-
gb_importances.head(n_features)["feature"].values
|
752
|
-
if "Gradient Boosting" in cls
|
753
|
-
else []
|
754
|
-
)
|
755
|
-
# xgb
|
756
|
-
xgb_importances = (
|
757
|
-
features_xgb(x_train, y_train, xgb_params) if "xgb" in cls else pd.DataFrame()
|
758
|
-
)
|
759
|
-
top_xgb_features = (
|
760
|
-
xgb_importances.head(n_features)["feature"].values if "xgb" in cls else []
|
761
|
-
)
|
762
|
-
|
763
|
-
# SVM with RFE
|
764
|
-
selected_svm_features = (
|
765
|
-
features_svm(x_train, y_train, rfe_params) if "svm" in cls else []
|
766
|
-
)
|
767
|
-
# Naive Bayes
|
768
|
-
selected_naive_bayes_features = (
|
769
|
-
features_naive_bayes(x_train, y_train) if "Naive Bayes" in cls else []
|
770
|
-
)
|
771
|
-
# lda: linear discriminant analysis
|
772
|
-
lda_importances = features_lda(x_train, y_train) if "lda" in cls else pd.DataFrame()
|
773
|
-
selected_lda_features = (
|
774
|
-
lda_importances.head(n_features)["feature"].values if "lda" in cls else []
|
775
|
-
)
|
776
|
-
# AdaBoost Feature Importance
|
777
|
-
adaboost_importances = (
|
778
|
-
features_adaboost(x_train, y_train, adaboost_params)
|
779
|
-
if "AdaBoost" in cls
|
780
|
-
else pd.DataFrame()
|
781
|
-
)
|
782
|
-
top_adaboost_features = (
|
783
|
-
adaboost_importances.head(n_features)["feature"].values
|
784
|
-
if "AdaBoost" in cls
|
785
|
-
else []
|
786
|
-
)
|
787
|
-
# Decision Tree Feature Importance
|
788
|
-
dt_importances = (
|
789
|
-
features_decision_tree(x_train, y_train, dt_params)
|
790
|
-
if "Decision Tree" in cls
|
791
|
-
else pd.DataFrame()
|
792
|
-
)
|
793
|
-
top_dt_features = (
|
794
|
-
dt_importances.head(n_features)["feature"].values
|
795
|
-
if "Decision Tree" in cls
|
796
|
-
else []
|
797
|
-
)
|
798
|
-
# Bagging Feature Importance
|
799
|
-
bagging_importances = (
|
800
|
-
features_bagging(x_train, y_train, bagging_params)
|
801
|
-
if "Bagging" in cls
|
802
|
-
else pd.DataFrame()
|
803
|
-
)
|
804
|
-
top_bagging_features = (
|
805
|
-
bagging_importances.head(n_features)["feature"].values
|
806
|
-
if "Bagging" in cls
|
807
|
-
else []
|
808
|
-
)
|
809
|
-
# KNN Feature Importance via Permutation
|
810
|
-
knn_importances = (
|
811
|
-
features_knn(x_train, y_train, knn_params) if "KNN" in cls else pd.DataFrame()
|
812
|
-
)
|
813
|
-
top_knn_features = (
|
814
|
-
knn_importances.head(n_features)["feature"].values if "KNN" in cls else []
|
815
|
-
)
|
816
|
-
|
817
|
-
#! Find common features
|
818
|
-
common_features = ips.shared(
|
819
|
-
lasso_selected_features,
|
820
|
-
selected_ridge_features,
|
821
|
-
selected_enet_features,
|
822
|
-
top_rf_features,
|
823
|
-
top_gb_features,
|
824
|
-
top_xgb_features,
|
825
|
-
selected_svm_features,
|
826
|
-
selected_naive_bayes_features,
|
827
|
-
selected_lda_features,
|
828
|
-
top_adaboost_features,
|
829
|
-
top_dt_features,
|
830
|
-
top_bagging_features,
|
831
|
-
top_knn_features,
|
832
|
-
strict=strict,
|
833
|
-
n_shared=n_shared,
|
834
|
-
verbose=False,
|
835
|
-
)
|
836
|
-
|
837
|
-
# Use selected features or all features for model validation
|
838
|
-
x_train_selected = (
|
839
|
-
x_train[list(common_features)] if use_selected_features else x_train
|
840
|
-
)
|
841
|
-
x_test_selected = x_test[list(common_features)] if use_selected_features else x_test
|
842
|
-
|
843
|
-
if metrics is None:
|
844
|
-
metrics = ["accuracy", "precision", "recall", "f1", "roc_auc"]
|
845
|
-
|
846
|
-
# Prepare results DataFrame for selected features
|
847
|
-
features_df = pd.DataFrame(
|
848
|
-
{
|
849
|
-
"type": ["Lasso"] * len(lasso_selected_features)
|
850
|
-
+ ["Ridge"] * len(selected_ridge_features)
|
851
|
-
+ ["Random Forest"] * len(top_rf_features)
|
852
|
-
+ ["Gradient Boosting"] * len(top_gb_features)
|
853
|
-
+ ["Enet"] * len(selected_enet_features)
|
854
|
-
+ ["xgb"] * len(top_xgb_features)
|
855
|
-
+ ["SVM"] * len(selected_svm_features)
|
856
|
-
+ ["Naive Bayes"] * len(selected_naive_bayes_features)
|
857
|
-
+ ["Linear Discriminant Analysis"] * len(selected_lda_features)
|
858
|
-
+ ["AdaBoost"] * len(top_adaboost_features)
|
859
|
-
+ ["Decision Tree"] * len(top_dt_features)
|
860
|
-
+ ["Bagging"] * len(top_bagging_features)
|
861
|
-
+ ["KNN"] * len(top_knn_features),
|
862
|
-
"feature": np.concatenate(
|
863
|
-
[
|
864
|
-
lasso_selected_features,
|
865
|
-
selected_ridge_features,
|
866
|
-
top_rf_features,
|
867
|
-
top_gb_features,
|
868
|
-
selected_enet_features,
|
869
|
-
top_xgb_features,
|
870
|
-
selected_svm_features,
|
871
|
-
selected_naive_bayes_features,
|
872
|
-
selected_lda_features,
|
873
|
-
top_adaboost_features,
|
874
|
-
top_dt_features,
|
875
|
-
top_bagging_features,
|
876
|
-
top_knn_features,
|
877
|
-
]
|
878
|
-
),
|
879
|
-
}
|
880
|
-
)
|
881
|
-
|
882
|
-
#! Validate trained each classifier
|
883
|
-
models = get_models(random_state=random_state, cls=cls)
|
884
|
-
cv_train_results, cv_test_results = [], []
|
885
|
-
for name, clf in models.items():
|
886
|
-
if not x_train_selected.empty:
|
887
|
-
cv_scores = validate_classifier(
|
888
|
-
clf,
|
889
|
-
x_train_selected,
|
890
|
-
y_train,
|
891
|
-
x_test_selected,
|
892
|
-
y_test,
|
893
|
-
metrics=metrics,
|
894
|
-
cv_folds=cv_folds,
|
895
|
-
)
|
896
|
-
|
897
|
-
cv_train_score_df = pd.DataFrame(cv_scores["cv_train_scores"], index=[name])
|
898
|
-
cv_test_score_df = pd.DataFrame(cv_scores["cv_test_scores"], index=[name])
|
899
|
-
cv_train_results.append(cv_train_score_df)
|
900
|
-
cv_test_results.append(cv_test_score_df)
|
901
|
-
if all([cv_train_results, cv_test_results]):
|
902
|
-
cv_train_results_df = (
|
903
|
-
pd.concat(cv_train_results)
|
904
|
-
.reset_index()
|
905
|
-
.rename(columns={"index": "Classifier"})
|
906
|
-
)
|
907
|
-
cv_test_results_df = (
|
908
|
-
pd.concat(cv_test_results)
|
909
|
-
.reset_index()
|
910
|
-
.rename(columns={"index": "Classifier"})
|
911
|
-
)
|
912
|
-
#! Store results in the main results dictionary
|
913
|
-
results = {
|
914
|
-
"selected_features": features_df,
|
915
|
-
"cv_train_scores": cv_train_results_df,
|
916
|
-
"cv_test_scores": rank_models(cv_test_results_df, plot_=plot_),
|
917
|
-
"common_features": list(common_features),
|
918
|
-
}
|
919
|
-
if all([plot_, dir_save]):
|
920
|
-
from datetime import datetime
|
921
|
-
|
922
|
-
now_ = datetime.now().strftime("%y%m%d_%H%M%S")
|
923
|
-
ips.figsave(dir_save + f"features{now_}.pdf")
|
924
|
-
else:
|
925
|
-
results = {
|
926
|
-
"selected_features": pd.DataFrame(),
|
927
|
-
"cv_train_scores": pd.DataFrame(),
|
928
|
-
"cv_test_scores": pd.DataFrame(),
|
929
|
-
"common_features": [],
|
930
|
-
}
|
931
|
-
print(f"Warning: 没有找到共同的genes, when n_shared={n_shared}")
|
932
|
-
return results
|
933
|
-
|
934
|
-
|
935
|
-
#! # usage:
|
936
|
-
# # Get features and common features
|
937
|
-
# results = get_features(X, y)
|
938
|
-
# common_features = results["common_features"]
|
939
|
-
def validate_features(
|
940
|
-
x_train: pd.DataFrame,
|
941
|
-
y_train: pd.Series,
|
942
|
-
x_true: pd.DataFrame,
|
943
|
-
y_true: pd.Series,
|
944
|
-
common_features: set = None,
|
945
|
-
models: Optional[Dict[str, Any]] = None,
|
946
|
-
metrics: Optional[list] = None,
|
947
|
-
random_state: int = 1,
|
948
|
-
smote: bool = False,
|
949
|
-
n_jobs: int = -1,
|
950
|
-
plot_: bool = True,
|
951
|
-
class_weight: str = "balanced",
|
952
|
-
) -> dict:
|
953
|
-
"""
|
954
|
-
Validate models using selected features on the validation dataset.
|
955
|
-
|
956
|
-
Parameters:
|
957
|
-
- x_train (pd.DataFrame): Training feature dataset.
|
958
|
-
- y_train (pd.Series): Training target variable.
|
959
|
-
- x_true (pd.DataFrame): Validation feature dataset.
|
960
|
-
- y_true (pd.Series): Validation target variable.
|
961
|
-
- common_features (set): Set of common features to use for validation.
|
962
|
-
- models (dict, optional): Dictionary of models to validate.
|
963
|
-
- metrics (list, optional): List of metrics to compute.
|
964
|
-
- random_state (int): Random state for reproducibility.
|
965
|
-
- plot_ (bool): Option to plot metrics (to be implemented if needed).
|
966
|
-
- class_weight (str or dict): Class weights to handle imbalance.
|
967
|
-
|
968
|
-
"""
|
969
|
-
from tqdm import tqdm
|
970
|
-
|
971
|
-
# Ensure common features are selected
|
972
|
-
common_features = ips.shared(
|
973
|
-
common_features, x_train.columns, x_true.columns, strict=True, verbose=False
|
974
|
-
)
|
975
|
-
|
976
|
-
# Filter the training and validation datasets for the common features
|
977
|
-
x_train_selected = x_train[common_features]
|
978
|
-
x_true_selected = x_true[common_features]
|
979
|
-
|
980
|
-
if not x_true_selected.index.equals(y_true.index):
|
981
|
-
raise ValueError(
|
982
|
-
"Index mismatch between validation features and target. Ensure data alignment."
|
983
|
-
)
|
984
|
-
|
985
|
-
y_true = y_true.loc[x_true_selected.index]
|
986
|
-
|
987
|
-
# Handle class imbalance using SMOTE
|
988
|
-
if smote:
|
989
|
-
if (
|
990
|
-
y_train.value_counts(normalize=True).max() < 0.8
|
991
|
-
): # Threshold to decide if data is imbalanced
|
992
|
-
smote = SMOTE(random_state=random_state)
|
993
|
-
x_train_resampled, y_train_resampled = smote.fit_resample(
|
994
|
-
x_train_selected, y_train
|
995
|
-
)
|
996
|
-
else:
|
997
|
-
# skip SMOTE
|
998
|
-
x_train_resampled, y_train_resampled = x_train_selected, y_train
|
999
|
-
else:
|
1000
|
-
x_train_resampled, y_train_resampled = x_train_selected, y_train
|
1001
|
-
|
1002
|
-
# Default models if not provided
|
1003
|
-
if models is None:
|
1004
|
-
models = {
|
1005
|
-
"Random Forest": RandomForestClassifier(
|
1006
|
-
class_weight=class_weight, random_state=random_state
|
1007
|
-
),
|
1008
|
-
"SVM": SVC(probability=True, class_weight=class_weight),
|
1009
|
-
"Logistic Regression": LogisticRegression(
|
1010
|
-
class_weight=class_weight, random_state=random_state
|
1011
|
-
),
|
1012
|
-
"Gradient Boosting": GradientBoostingClassifier(random_state=random_state),
|
1013
|
-
"AdaBoost": AdaBoostClassifier(
|
1014
|
-
random_state=random_state, algorithm="SAMME"
|
1015
|
-
),
|
1016
|
-
"Lasso": LogisticRegression(
|
1017
|
-
penalty="l1", solver="saga", random_state=random_state
|
1018
|
-
),
|
1019
|
-
"Ridge": LogisticRegression(
|
1020
|
-
penalty="l2", solver="saga", random_state=random_state
|
1021
|
-
),
|
1022
|
-
"Elastic Net": LogisticRegression(
|
1023
|
-
penalty="elasticnet",
|
1024
|
-
solver="saga",
|
1025
|
-
l1_ratio=0.5,
|
1026
|
-
random_state=random_state,
|
1027
|
-
),
|
1028
|
-
"XGBoost": xgb.XGBClassifier(eval_metric="logloss"),
|
1029
|
-
"Naive Bayes": GaussianNB(),
|
1030
|
-
"LDA": LinearDiscriminantAnalysis(),
|
1031
|
-
}
|
1032
|
-
|
1033
|
-
# Hyperparameter grids for tuning
|
1034
|
-
param_grids = {
|
1035
|
-
"Random Forest": {
|
1036
|
-
"n_estimators": [100, 200, 300, 400, 500],
|
1037
|
-
"max_depth": [None, 3, 5, 10, 20],
|
1038
|
-
"min_samples_split": [2, 5, 10],
|
1039
|
-
"min_samples_leaf": [1, 2, 4],
|
1040
|
-
"class_weight": [None, "balanced"],
|
1041
|
-
},
|
1042
|
-
"SVM": {
|
1043
|
-
"C": [0.01, 0.1, 1, 10, 100, 1000],
|
1044
|
-
"gamma": [0.001, 0.01, 0.1, "scale", "auto"],
|
1045
|
-
"kernel": ["linear", "rbf", "poly"],
|
1046
|
-
},
|
1047
|
-
"Logistic Regression": {
|
1048
|
-
"C": [0.01, 0.1, 1, 10, 100],
|
1049
|
-
"solver": ["liblinear", "saga", "newton-cg", "lbfgs"],
|
1050
|
-
"penalty": ["l1", "l2"],
|
1051
|
-
"max_iter": [100, 200, 300],
|
1052
|
-
},
|
1053
|
-
"Gradient Boosting": {
|
1054
|
-
"n_estimators": [100, 200, 300, 400, 500],
|
1055
|
-
"learning_rate": np.logspace(-3, 0, 4),
|
1056
|
-
"max_depth": [3, 5, 7, 9],
|
1057
|
-
"min_samples_split": [2, 5, 10],
|
1058
|
-
},
|
1059
|
-
"AdaBoost": {
|
1060
|
-
"n_estimators": [50, 100, 200, 300, 500],
|
1061
|
-
"learning_rate": np.logspace(-3, 0, 4),
|
1062
|
-
},
|
1063
|
-
"Lasso": {"C": np.logspace(-3, 1, 10), "max_iter": [100, 200, 300]},
|
1064
|
-
"Ridge": {"C": np.logspace(-3, 1, 10), "max_iter": [100, 200, 300]},
|
1065
|
-
"Elastic Net": {
|
1066
|
-
"C": np.logspace(-3, 1, 10),
|
1067
|
-
"l1_ratio": [0.1, 0.5, 0.9],
|
1068
|
-
"max_iter": [100, 200, 300],
|
1069
|
-
},
|
1070
|
-
"XGBoost": {
|
1071
|
-
"n_estimators": [100, 200],
|
1072
|
-
"max_depth": [3, 5, 7],
|
1073
|
-
"learning_rate": [0.01, 0.1, 0.2],
|
1074
|
-
"subsample": [0.8, 1.0],
|
1075
|
-
"colsample_bytree": [0.8, 1.0],
|
1076
|
-
},
|
1077
|
-
"Naive Bayes": {},
|
1078
|
-
"LDA": {"solver": ["svd", "lsqr", "eigen"]},
|
1079
|
-
}
|
1080
|
-
# Default metrics if not provided
|
1081
|
-
if metrics is None:
|
1082
|
-
metrics = [
|
1083
|
-
"accuracy",
|
1084
|
-
"precision",
|
1085
|
-
"recall",
|
1086
|
-
"f1",
|
1087
|
-
"roc_auc",
|
1088
|
-
"mcc",
|
1089
|
-
"specificity",
|
1090
|
-
"balanced_accuracy",
|
1091
|
-
"pr_auc",
|
1092
|
-
]
|
1093
|
-
|
1094
|
-
results = {}
|
1095
|
-
|
1096
|
-
# Validate each classifier with GridSearchCV
|
1097
|
-
for name, clf in tqdm(
|
1098
|
-
models.items(),
|
1099
|
-
desc="for metric in metrics",
|
1100
|
-
colour="green",
|
1101
|
-
bar_format="{l_bar}{bar} {n_fmt}/{total_fmt}",
|
1102
|
-
):
|
1103
|
-
print(f"\nValidating {name} on the validation dataset:")
|
1104
|
-
|
1105
|
-
# Check if `predict_proba` method exists; if not, use CalibratedClassifierCV
|
1106
|
-
# 没有predict_proba的分类器,使用 CalibratedClassifierCV 可以获得校准的概率估计。此外,为了使代码更灵活,我们可以在创建分类器
|
1107
|
-
# 时检查 predict_proba 方法是否存在,如果不存在且用户希望计算 roc_auc 或 pr_auc,则启用 CalibratedClassifierCV
|
1108
|
-
if not hasattr(clf, "predict_proba"):
|
1109
|
-
print(
|
1110
|
-
f"Using CalibratedClassifierCV for {name} due to lack of probability estimates."
|
1111
|
-
)
|
1112
|
-
calibrated_clf = CalibratedClassifierCV(clf, method="sigmoid", cv="prefit")
|
1113
|
-
else:
|
1114
|
-
calibrated_clf = clf
|
1115
|
-
# Stratified K-Fold for cross-validation
|
1116
|
-
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=random_state)
|
1117
|
-
|
1118
|
-
# Create GridSearchCV object
|
1119
|
-
gs = GridSearchCV(
|
1120
|
-
estimator=calibrated_clf,
|
1121
|
-
param_grid=param_grids[name],
|
1122
|
-
scoring="roc_auc", # Optimize for ROC AUC
|
1123
|
-
cv=skf, # Stratified K-Folds cross-validation
|
1124
|
-
n_jobs=n_jobs,
|
1125
|
-
verbose=1,
|
1126
|
-
)
|
1127
|
-
|
1128
|
-
# Fit the model using GridSearchCV
|
1129
|
-
gs.fit(x_train_resampled, y_train_resampled)
|
1130
|
-
# Best estimator from grid search
|
1131
|
-
best_clf = gs.best_estimator_
|
1132
|
-
# Make predictions on the validation set
|
1133
|
-
y_pred = best_clf.predict(x_true_selected)
|
1134
|
-
# Calculate probabilities for ROC AUC if possible
|
1135
|
-
if hasattr(best_clf, "predict_proba"):
|
1136
|
-
y_pred_proba = best_clf.predict_proba(x_true_selected)[:, 1]
|
1137
|
-
elif hasattr(best_clf, "decision_function"):
|
1138
|
-
# If predict_proba is not available, use decision_function (e.g., for SVM)
|
1139
|
-
y_pred_proba = best_clf.decision_function(x_true_selected)
|
1140
|
-
# Ensure y_pred_proba is within 0 and 1 bounds
|
1141
|
-
y_pred_proba = (y_pred_proba - y_pred_proba.min()) / (
|
1142
|
-
y_pred_proba.max() - y_pred_proba.min()
|
1143
|
-
)
|
1144
|
-
else:
|
1145
|
-
y_pred_proba = None # No probability output for certain models
|
1146
|
-
|
1147
|
-
# Calculate metrics
|
1148
|
-
validation_scores = {}
|
1149
|
-
for metric in metrics:
|
1150
|
-
if metric == "accuracy":
|
1151
|
-
validation_scores[metric] = accuracy_score(y_true, y_pred)
|
1152
|
-
elif metric == "precision":
|
1153
|
-
validation_scores[metric] = precision_score(
|
1154
|
-
y_true, y_pred, average="weighted"
|
1155
|
-
)
|
1156
|
-
elif metric == "recall":
|
1157
|
-
validation_scores[metric] = recall_score(
|
1158
|
-
y_true, y_pred, average="weighted"
|
1159
|
-
)
|
1160
|
-
elif metric == "f1":
|
1161
|
-
validation_scores[metric] = f1_score(y_true, y_pred, average="weighted")
|
1162
|
-
elif metric == "roc_auc" and y_pred_proba is not None:
|
1163
|
-
validation_scores[metric] = roc_auc_score(y_true, y_pred_proba)
|
1164
|
-
elif metric == "mcc":
|
1165
|
-
validation_scores[metric] = matthews_corrcoef(y_true, y_pred)
|
1166
|
-
elif metric == "specificity":
|
1167
|
-
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
|
1168
|
-
validation_scores[metric] = tn / (tn + fp) # Specificity calculation
|
1169
|
-
elif metric == "balanced_accuracy":
|
1170
|
-
validation_scores[metric] = balanced_accuracy_score(y_true, y_pred)
|
1171
|
-
elif metric == "pr_auc" and y_pred_proba is not None:
|
1172
|
-
precision, recall, _ = precision_recall_curve(y_true, y_pred_proba)
|
1173
|
-
validation_scores[metric] = average_precision_score(
|
1174
|
-
y_true, y_pred_proba
|
1175
|
-
)
|
1176
|
-
|
1177
|
-
# Calculate ROC curve
|
1178
|
-
# https://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html
|
1179
|
-
if y_pred_proba is not None:
|
1180
|
-
# fpr, tpr, roc_auc = dict(), dict(), dict()
|
1181
|
-
fpr, tpr, _ = roc_curve(y_true, y_pred_proba)
|
1182
|
-
lower_ci, upper_ci = cal_auc_ci(y_true, y_pred_proba, verbose=False)
|
1183
|
-
roc_auc = auc(fpr, tpr)
|
1184
|
-
roc_info = {
|
1185
|
-
"fpr": fpr.tolist(),
|
1186
|
-
"tpr": tpr.tolist(),
|
1187
|
-
"auc": roc_auc,
|
1188
|
-
"ci95": (lower_ci, upper_ci),
|
1189
|
-
}
|
1190
|
-
# precision-recall curve
|
1191
|
-
precision_, recall_, _ = precision_recall_curve(y_true, y_pred_proba)
|
1192
|
-
avg_precision_ = average_precision_score(y_true, y_pred_proba)
|
1193
|
-
pr_info = {
|
1194
|
-
"precision": precision_,
|
1195
|
-
"recall": recall_,
|
1196
|
-
"avg_precision": avg_precision_,
|
1197
|
-
}
|
1198
|
-
else:
|
1199
|
-
roc_info, pr_info = None, None
|
1200
|
-
results[name] = {
|
1201
|
-
"best_params": gs.best_params_,
|
1202
|
-
"scores": validation_scores,
|
1203
|
-
"roc_curve": roc_info,
|
1204
|
-
"pr_curve": pr_info,
|
1205
|
-
"confusion_matrix": confusion_matrix(y_true, y_pred),
|
1206
|
-
}
|
1207
|
-
|
1208
|
-
df_results = pd.DataFrame.from_dict(results, orient="index")
|
1209
|
-
|
1210
|
-
return df_results
|
1211
|
-
|
1212
|
-
|
1213
|
-
#! usage validate_features()
|
1214
|
-
# Validate models using the validation dataset (X_val, y_val)
|
1215
|
-
# validation_results = validate_features(X, y, X_val, y_val, common_features)
|
1216
|
-
|
1217
|
-
|
1218
|
-
# # If you want to access validation scores
|
1219
|
-
# print(validation_results)
|
1220
|
-
def plot_validate_features(res_val):
|
1221
|
-
"""
|
1222
|
-
plot the results of 'validate_features()'
|
1223
|
-
"""
|
1224
|
-
colors = plot.get_color(len(ips.flatten(res_val["pr_curve"].index)))
|
1225
|
-
if res_val.shape[0] > 5:
|
1226
|
-
alpha = 0
|
1227
|
-
figsize = [8, 10]
|
1228
|
-
subplot_layout = [1, 2]
|
1229
|
-
ncols = 2
|
1230
|
-
bbox_to_anchor = [1.5, 0.6]
|
1231
|
-
else:
|
1232
|
-
alpha = 0.03
|
1233
|
-
figsize = [10, 6]
|
1234
|
-
subplot_layout = [1, 1]
|
1235
|
-
ncols = 1
|
1236
|
-
bbox_to_anchor = [1, 1]
|
1237
|
-
nexttile = plot.subplot(figsize=figsize)
|
1238
|
-
ax = nexttile(subplot_layout[0], subplot_layout[1])
|
1239
|
-
for i, model_name in enumerate(ips.flatten(res_val["pr_curve"].index)):
|
1240
|
-
fpr = res_val["roc_curve"][model_name]["fpr"]
|
1241
|
-
tpr = res_val["roc_curve"][model_name]["tpr"]
|
1242
|
-
(lower_ci, upper_ci) = res_val["roc_curve"][model_name]["ci95"]
|
1243
|
-
mean_auc = res_val["roc_curve"][model_name]["auc"]
|
1244
|
-
plot_roc_curve(
|
1245
|
-
fpr,
|
1246
|
-
tpr,
|
1247
|
-
mean_auc,
|
1248
|
-
lower_ci,
|
1249
|
-
upper_ci,
|
1250
|
-
model_name=model_name,
|
1251
|
-
lw=1.5,
|
1252
|
-
color=colors[i],
|
1253
|
-
alpha=alpha,
|
1254
|
-
ax=ax,
|
1255
|
-
)
|
1256
|
-
plot.figsets(
|
1257
|
-
sp=2,
|
1258
|
-
legend=dict(
|
1259
|
-
loc="upper right",
|
1260
|
-
ncols=ncols,
|
1261
|
-
fontsize=8,
|
1262
|
-
bbox_to_anchor=[1.5, 0.6],
|
1263
|
-
markerscale=0.8,
|
1264
|
-
),
|
1265
|
-
)
|
1266
|
-
# plot.split_legend(ax,n=2, loc=["upper left", "lower left"],bbox=[[1,0.5],[1,0.5]],ncols=2,labelcolor="k",fontsize=8)
|
1267
|
-
|
1268
|
-
ax = nexttile(subplot_layout[0], subplot_layout[1])
|
1269
|
-
for i, model_name in enumerate(ips.flatten(res_val["pr_curve"].index)):
|
1270
|
-
plot_pr_curve(
|
1271
|
-
recall=res_val["pr_curve"][model_name]["recall"],
|
1272
|
-
precision=res_val["pr_curve"][model_name]["precision"],
|
1273
|
-
avg_precision=res_val["pr_curve"][model_name]["avg_precision"],
|
1274
|
-
model_name=model_name,
|
1275
|
-
color=colors[i],
|
1276
|
-
lw=1.5,
|
1277
|
-
alpha=alpha,
|
1278
|
-
ax=ax,
|
1279
|
-
)
|
1280
|
-
plot.figsets(
|
1281
|
-
sp=2,
|
1282
|
-
legend=dict(loc="upper right", ncols=1, fontsize=8, bbox_to_anchor=[1.5, 0.5]),
|
1283
|
-
)
|
1284
|
-
# plot.split_legend(ax,n=2, loc=["upper left", "lower left"],bbox=[[1,0.5],[1,0.5]],ncols=2,labelcolor="k",fontsize=8)
|
1285
|
-
|
1286
|
-
|
1287
|
-
def plot_validate_features_single(res_val, figsize=None):
|
1288
|
-
if figsize is None:
|
1289
|
-
nexttile = plot.subplot(len(ips.flatten(res_val["pr_curve"].index)), 3)
|
1290
|
-
else:
|
1291
|
-
nexttile = plot.subplot(
|
1292
|
-
len(ips.flatten(res_val["pr_curve"].index)), 3, figsize=figsize
|
1293
|
-
)
|
1294
|
-
for model_name in ips.flatten(res_val["pr_curve"].index):
|
1295
|
-
fpr = res_val["roc_curve"][model_name]["fpr"]
|
1296
|
-
tpr = res_val["roc_curve"][model_name]["tpr"]
|
1297
|
-
(lower_ci, upper_ci) = res_val["roc_curve"][model_name]["ci95"]
|
1298
|
-
mean_auc = res_val["roc_curve"][model_name]["auc"]
|
1299
|
-
|
1300
|
-
# Plotting
|
1301
|
-
plot_roc_curve(fpr, tpr, mean_auc, lower_ci, upper_ci,
|
1302
|
-
model_name=model_name, ax=nexttile())
|
1303
|
-
plot.figsets(title=model_name, sp=2)
|
1304
|
-
|
1305
|
-
plot_pr_binary(
|
1306
|
-
recall=res_val["pr_curve"][model_name]["recall"],
|
1307
|
-
precision=res_val["pr_curve"][model_name]["precision"],
|
1308
|
-
avg_precision=res_val["pr_curve"][model_name]["avg_precision"],
|
1309
|
-
model_name=model_name,
|
1310
|
-
ax=nexttile(),
|
1311
|
-
)
|
1312
|
-
plot.figsets(title=model_name, sp=2)
|
1313
|
-
|
1314
|
-
# plot cm
|
1315
|
-
plot_cm(res_val["confusion_matrix"][model_name], ax=nexttile(), normalize=False)
|
1316
|
-
plot.figsets(title=model_name, sp=2)
|
1317
|
-
|
1318
|
-
|
1319
|
-
def cal_auc_ci(
|
1320
|
-
y_true, y_pred, n_bootstraps=1000, ci=0.95, random_state=1, verbose=True
|
1321
|
-
):
|
1322
|
-
y_true = np.asarray(y_true)
|
1323
|
-
y_pred = np.asarray(y_pred)
|
1324
|
-
bootstrapped_scores = []
|
1325
|
-
if verbose:
|
1326
|
-
print("auroc score:", roc_auc_score(y_true, y_pred))
|
1327
|
-
rng = np.random.RandomState(random_state)
|
1328
|
-
for i in range(n_bootstraps):
|
1329
|
-
# bootstrap by sampling with replacement on the prediction indices
|
1330
|
-
indices = rng.randint(0, len(y_pred), len(y_pred))
|
1331
|
-
if len(np.unique(y_true[indices])) < 2:
|
1332
|
-
# We need at least one positive and one negative sample for ROC AUC
|
1333
|
-
# to be defined: reject the sample
|
1334
|
-
continue
|
1335
|
-
if isinstance(y_true, np.ndarray):
|
1336
|
-
score = roc_auc_score(y_true[indices], y_pred[indices])
|
1337
|
-
else:
|
1338
|
-
score = roc_auc_score(y_true.iloc[indices], y_pred.iloc[indices])
|
1339
|
-
bootstrapped_scores.append(score)
|
1340
|
-
# print("Bootstrap #{} ROC area: {:0.3f}".format(i + 1, score))
|
1341
|
-
sorted_scores = np.array(bootstrapped_scores)
|
1342
|
-
sorted_scores.sort()
|
1343
|
-
|
1344
|
-
# Computing the lower and upper bound of the 90% confidence interval
|
1345
|
-
# You can change the bounds percentiles to 0.025 and 0.975 to get
|
1346
|
-
# a 95% confidence interval instead.
|
1347
|
-
confidence_lower = sorted_scores[int((1 - ci) * len(sorted_scores))]
|
1348
|
-
confidence_upper = sorted_scores[int(ci * len(sorted_scores))]
|
1349
|
-
if verbose:
|
1350
|
-
print(
|
1351
|
-
"Confidence interval for the score: [{:0.3f} - {:0.3}]".format(
|
1352
|
-
confidence_lower, confidence_upper
|
1353
|
-
)
|
1354
|
-
)
|
1355
|
-
return confidence_lower, confidence_upper
|
1356
|
-
|
1357
|
-
|
1358
|
-
def plot_roc_curve(
|
1359
|
-
fpr=None,
|
1360
|
-
tpr=None,
|
1361
|
-
mean_auc=None,
|
1362
|
-
lower_ci=None,
|
1363
|
-
upper_ci=None,
|
1364
|
-
model_name=None,
|
1365
|
-
color="#FF8F00",
|
1366
|
-
lw=2,
|
1367
|
-
alpha=0.1,
|
1368
|
-
ci_display=True,
|
1369
|
-
title="ROC Curve",
|
1370
|
-
xlabel="1−Specificity",
|
1371
|
-
ylabel="Sensitivity",
|
1372
|
-
legend_loc="lower right",
|
1373
|
-
diagonal_color="0.5",
|
1374
|
-
figsize=(5, 5),
|
1375
|
-
ax=None,
|
1376
|
-
**kwargs,
|
1377
|
-
):
|
1378
|
-
if ax is None:
|
1379
|
-
fig, ax = plt.subplots(figsize=figsize)
|
1380
|
-
if mean_auc is not None:
|
1381
|
-
model_name = "ROC curve" if model_name is None else model_name
|
1382
|
-
if ci_display:
|
1383
|
-
label = f"{model_name} (AUC = {mean_auc:.3f})\n95% CI: {lower_ci:.3f} - {upper_ci:.3f}"
|
1384
|
-
else:
|
1385
|
-
label = f"{model_name} (AUC = {mean_auc:.3f})"
|
1386
|
-
else:
|
1387
|
-
label = None
|
1388
|
-
|
1389
|
-
# Plot ROC curve and the diagonal reference line
|
1390
|
-
ax.fill_between(fpr, tpr, alpha=alpha, color=color)
|
1391
|
-
ax.plot([0, 1], [0, 1], color=diagonal_color, clip_on=False, linestyle="--")
|
1392
|
-
ax.plot(fpr, tpr, color=color, lw=lw, label=label, clip_on=False, **kwargs)
|
1393
|
-
# Setting plot limits, labels, and title
|
1394
|
-
ax.set_xlim([-0.01, 1.0])
|
1395
|
-
ax.set_ylim([0.0, 1.0])
|
1396
|
-
ax.set_xlabel(xlabel)
|
1397
|
-
ax.set_ylabel(ylabel)
|
1398
|
-
ax.set_title(title)
|
1399
|
-
ax.legend(loc=legend_loc)
|
1400
|
-
return ax
|
1401
|
-
|
1402
|
-
|
1403
|
-
# * usage: ml2ls.plot_roc_curve(fpr, tpr, mean_auc, lower_ci, upper_ci)
|
1404
|
-
# for model_name in flatten(validation_results["roc_curve"].keys())[2:]:
|
1405
|
-
# fpr = validation_results["roc_curve"][model_name]["fpr"]
|
1406
|
-
# tpr = validation_results["roc_curve"][model_name]["tpr"]
|
1407
|
-
# (lower_ci, upper_ci) = validation_results["roc_curve"][model_name]["ci95"]
|
1408
|
-
# mean_auc = validation_results["roc_curve"][model_name]["auc"]
|
1409
|
-
|
1410
|
-
# # Plotting
|
1411
|
-
# ml2ls.plot_roc_curve(fpr, tpr, mean_auc, lower_ci, upper_ci)
|
1412
|
-
# figsets(title=model_name)
|
1413
|
-
|
1414
|
-
def plot_pr_curve(
|
1415
|
-
recall=None,
|
1416
|
-
precision=None,
|
1417
|
-
avg_precision=None,
|
1418
|
-
model_name=None,
|
1419
|
-
lw=2,
|
1420
|
-
figsize=[5, 5],
|
1421
|
-
title="Precision-Recall Curve",
|
1422
|
-
xlabel="Recall",
|
1423
|
-
ylabel="Precision",
|
1424
|
-
alpha=0.1,
|
1425
|
-
color="#FF8F00",
|
1426
|
-
legend_loc="lower left",
|
1427
|
-
ax=None,
|
1428
|
-
**kwargs,
|
1429
|
-
):
|
1430
|
-
if ax is None:
|
1431
|
-
fig, ax = plt.subplots(figsize=figsize)
|
1432
|
-
model_name = "PR curve" if model_name is None else model_name
|
1433
|
-
# Plot Precision-Recall curve
|
1434
|
-
ax.plot(
|
1435
|
-
recall,
|
1436
|
-
precision,
|
1437
|
-
lw=lw,
|
1438
|
-
color=color,
|
1439
|
-
label=(f"{model_name} (AP={avg_precision:.2f})"),
|
1440
|
-
clip_on=False,
|
1441
|
-
**kwargs,
|
1442
|
-
)
|
1443
|
-
# Fill area under the curve
|
1444
|
-
ax.fill_between(recall, precision, alpha=alpha, color=color)
|
1445
|
-
|
1446
|
-
# Customize axes
|
1447
|
-
ax.set_title(title)
|
1448
|
-
ax.set_xlabel(xlabel)
|
1449
|
-
ax.set_ylabel(ylabel)
|
1450
|
-
ax.set_xlim([-0.01, 1.0])
|
1451
|
-
ax.set_ylim([0.0, 1.0])
|
1452
|
-
ax.grid(False)
|
1453
|
-
ax.legend(loc=legend_loc)
|
1454
|
-
return ax
|
1455
|
-
|
1456
|
-
# * usage: ml2ls.plot_pr_curve()
|
1457
|
-
# for md_name in flatten(validation_results["pr_curve"].keys()):
|
1458
|
-
# ml2ls.plot_pr_curve(
|
1459
|
-
# recall=validation_results["pr_curve"][md_name]["recall"],
|
1460
|
-
# precision=validation_results["pr_curve"][md_name]["precision"],
|
1461
|
-
# avg_precision=validation_results["pr_curve"][md_name]["avg_precision"],
|
1462
|
-
# model_name=md_name,
|
1463
|
-
# lw=2,
|
1464
|
-
# alpha=0.1,
|
1465
|
-
# color="r",
|
1466
|
-
# )
|
1467
|
-
|
1468
|
-
def plot_pr_binary(
|
1469
|
-
recall=None,
|
1470
|
-
precision=None,
|
1471
|
-
avg_precision=None,
|
1472
|
-
model_name=None,
|
1473
|
-
lw=2,
|
1474
|
-
figsize=[5, 5],
|
1475
|
-
title="Precision-Recall Curve",
|
1476
|
-
xlabel="Recall",
|
1477
|
-
ylabel="Precision",
|
1478
|
-
alpha=0.1,
|
1479
|
-
color="#FF8F00",
|
1480
|
-
legend_loc="lower left",
|
1481
|
-
ax=None,
|
1482
|
-
show_avg_precision=False,
|
1483
|
-
**kwargs,
|
1484
|
-
):
|
1485
|
-
from scipy.interpolate import interp1d
|
1486
|
-
if ax is None:
|
1487
|
-
fig, ax = plt.subplots(figsize=figsize)
|
1488
|
-
model_name = "Binary PR Curve" if model_name is None else model_name
|
1489
|
-
|
1490
|
-
#* use sklearn bulitin function 'PrecisionRecallDisplay'?
|
1491
|
-
# from sklearn.metrics import PrecisionRecallDisplay
|
1492
|
-
# disp = PrecisionRecallDisplay(precision=precision,
|
1493
|
-
# recall=recall,
|
1494
|
-
# average_precision=avg_precision,**kwargs)
|
1495
|
-
# disp.plot(ax=ax, name=model_name, color=color)
|
1496
|
-
|
1497
|
-
# Plot Precision-Recall curve
|
1498
|
-
ax.plot(
|
1499
|
-
recall,
|
1500
|
-
precision,
|
1501
|
-
lw=lw,
|
1502
|
-
color=color,
|
1503
|
-
label=(f"{model_name} (AP={avg_precision:.2f})"),
|
1504
|
-
clip_on=False,
|
1505
|
-
**kwargs,
|
1506
|
-
)
|
1507
|
-
|
1508
|
-
# Fill area under the curve
|
1509
|
-
ax.fill_between(recall, precision, alpha=alpha, color=color)
|
1510
|
-
# Add F1 score iso-contours
|
1511
|
-
f_scores = np.linspace(0.2, 0.8, num=4)
|
1512
|
-
# for f_score in f_scores:
|
1513
|
-
# x = np.linspace(0.01, 1)
|
1514
|
-
# y = f_score * x / (2 * x - f_score)
|
1515
|
-
# plt.plot(x[y >= 0], y[y >= 0], color="gray", alpha=1)
|
1516
|
-
# plt.annotate(f"$f_1={f_score:0.1f}$", xy=(0.8, y[45] + 0.02))
|
1517
|
-
|
1518
|
-
pr_boundary = interp1d(recall, precision, kind="linear", fill_value="extrapolate")
|
1519
|
-
for f_score in f_scores:
|
1520
|
-
x_vals = np.linspace(0.01, 1, 10000)
|
1521
|
-
y_vals = f_score * x_vals / (2 * x_vals - f_score)
|
1522
|
-
y_vals_clipped = np.minimum(y_vals, pr_boundary(x_vals))
|
1523
|
-
y_vals_clipped = np.clip(y_vals_clipped, 1e-3, None) # Prevent going to zero
|
1524
|
-
valid = y_vals_clipped < pr_boundary(x_vals)
|
1525
|
-
valid_ = y_vals_clipped > 1e-3
|
1526
|
-
valid = valid&valid_
|
1527
|
-
x_vals = x_vals[valid]
|
1528
|
-
y_vals_clipped = y_vals_clipped[valid]
|
1529
|
-
if len(x_vals) > 0: # Ensure annotation is placed only if line segment exists
|
1530
|
-
ax.plot(x_vals, y_vals_clipped, color="gray", alpha=1)
|
1531
|
-
plt.annotate(f"$f_1={f_score:0.1f}$", xy=(0.8, y_vals_clipped[-int(len(y_vals_clipped)*0.35)] + 0.02))
|
1532
|
-
|
1533
|
-
|
1534
|
-
# # Plot the average precision line
|
1535
|
-
if show_avg_precision:
|
1536
|
-
plt.axhline(
|
1537
|
-
y=avg_precision,
|
1538
|
-
color="red",
|
1539
|
-
ls="--",
|
1540
|
-
lw=lw,
|
1541
|
-
label=f"Avg. precision={avg_precision:.2f}",
|
1542
|
-
)
|
1543
|
-
# Customize axes
|
1544
|
-
ax.set_title(title)
|
1545
|
-
ax.set_xlabel(xlabel)
|
1546
|
-
ax.set_ylabel(ylabel)
|
1547
|
-
ax.set_xlim([-0.01, 1.0])
|
1548
|
-
ax.set_ylim([0.0, 1.0])
|
1549
|
-
ax.grid(False)
|
1550
|
-
ax.legend(loc=legend_loc)
|
1551
|
-
return ax
|
1552
|
-
|
1553
|
-
def plot_cm(
|
1554
|
-
cm,
|
1555
|
-
labels_name=None,
|
1556
|
-
thresh=0.8,
|
1557
|
-
axis_labels=None,
|
1558
|
-
cmap="Reds",
|
1559
|
-
normalize=True,
|
1560
|
-
xlabel="Predicted Label",
|
1561
|
-
ylabel="Actual Label",
|
1562
|
-
fontsize=12,
|
1563
|
-
figsize=[5, 5],
|
1564
|
-
ax=None,
|
1565
|
-
):
|
1566
|
-
if ax is None:
|
1567
|
-
fig, ax = plt.subplots(figsize=figsize)
|
1568
|
-
|
1569
|
-
cm_normalized = np.round(
|
1570
|
-
cm.astype("float") / cm.sum(axis=1)[:, np.newaxis] * 100, 2
|
1571
|
-
)
|
1572
|
-
cm_value = cm_normalized if normalize else cm.astype("int")
|
1573
|
-
# Plot the heatmap
|
1574
|
-
cax = ax.imshow(cm_normalized, interpolation="nearest", cmap=cmap)
|
1575
|
-
plt.colorbar(cax, ax=ax, fraction=0.046, pad=0.04)
|
1576
|
-
cax.set_clim(0, 100)
|
1577
|
-
|
1578
|
-
# Define tick labels based on provided labels
|
1579
|
-
num_local = np.arange(len(labels_name)) if labels_name is not None else range(2)
|
1580
|
-
if axis_labels is None:
|
1581
|
-
axis_labels = labels_name if labels_name is not None else ["No", "Yes"]
|
1582
|
-
ax.set_xticks(num_local)
|
1583
|
-
ax.set_xticklabels(axis_labels)
|
1584
|
-
ax.set_yticks(num_local)
|
1585
|
-
ax.set_yticklabels(axis_labels)
|
1586
|
-
ax.set_ylabel(ylabel)
|
1587
|
-
ax.set_xlabel(xlabel)
|
1588
|
-
|
1589
|
-
# Add TN, FP, FN, TP annotations specifically for binary classification (2x2 matrix)
|
1590
|
-
if labels_name is None or len(labels_name) == 2:
|
1591
|
-
# True Negative (TN), False Positive (FP), False Negative (FN), and True Positive (TP)
|
1592
|
-
# Predicted
|
1593
|
-
# 0 | 1
|
1594
|
-
# ----------------
|
1595
|
-
# 0 | TN | FP
|
1596
|
-
# Actual ----------------
|
1597
|
-
# 1 | FN | TP
|
1598
|
-
tn_label = "TN"
|
1599
|
-
fp_label = "FP"
|
1600
|
-
fn_label = "FN"
|
1601
|
-
tp_label = "TP"
|
1602
|
-
|
1603
|
-
# Adjust positions slightly for TN, FP, FN, TP labels
|
1604
|
-
ax.text(
|
1605
|
-
0,
|
1606
|
-
0,
|
1607
|
-
(
|
1608
|
-
f"{tn_label}:{cm_normalized[0, 0]:.2f}%"
|
1609
|
-
if normalize
|
1610
|
-
else f"{tn_label}:{cm_value[0, 0]}"
|
1611
|
-
),
|
1612
|
-
ha="center",
|
1613
|
-
va="center",
|
1614
|
-
color="white" if cm_normalized[0, 0] > thresh * 100 else "black",
|
1615
|
-
fontsize=fontsize,
|
1616
|
-
)
|
1617
|
-
ax.text(
|
1618
|
-
1,
|
1619
|
-
0,
|
1620
|
-
(
|
1621
|
-
f"{fp_label}:{cm_normalized[0, 1]:.2f}%"
|
1622
|
-
if normalize
|
1623
|
-
else f"{fp_label}:{cm_value[0, 1]}"
|
1624
|
-
),
|
1625
|
-
ha="center",
|
1626
|
-
va="center",
|
1627
|
-
color="white" if cm_normalized[0, 1] > thresh * 100 else "black",
|
1628
|
-
fontsize=fontsize,
|
1629
|
-
)
|
1630
|
-
ax.text(
|
1631
|
-
0,
|
1632
|
-
1,
|
1633
|
-
(
|
1634
|
-
f"{fn_label}:{cm_normalized[1, 0]:.2f}%"
|
1635
|
-
if normalize
|
1636
|
-
else f"{fn_label}:{cm_value[1, 0]}"
|
1637
|
-
),
|
1638
|
-
ha="center",
|
1639
|
-
va="center",
|
1640
|
-
color="white" if cm_normalized[1, 0] > thresh * 100 else "black",
|
1641
|
-
fontsize=fontsize,
|
1642
|
-
)
|
1643
|
-
ax.text(
|
1644
|
-
1,
|
1645
|
-
1,
|
1646
|
-
(
|
1647
|
-
f"{tp_label}:{cm_normalized[1, 1]:.2f}%"
|
1648
|
-
if normalize
|
1649
|
-
else f"{tp_label}:{cm_value[1, 1]}"
|
1650
|
-
),
|
1651
|
-
ha="center",
|
1652
|
-
va="center",
|
1653
|
-
color="white" if cm_normalized[1, 1] > thresh * 100 else "black",
|
1654
|
-
fontsize=fontsize,
|
1655
|
-
)
|
1656
|
-
else:
|
1657
|
-
# Annotate cells with normalized percentage values
|
1658
|
-
for i in range(len(labels_name)):
|
1659
|
-
for j in range(len(labels_name)):
|
1660
|
-
val = cm_normalized[i, j]
|
1661
|
-
color = "white" if val > thresh * 100 else "black"
|
1662
|
-
ax.text(
|
1663
|
-
j,
|
1664
|
-
i,
|
1665
|
-
f"{val:.2f}%",
|
1666
|
-
ha="center",
|
1667
|
-
va="center",
|
1668
|
-
color=color,
|
1669
|
-
fontsize=fontsize,
|
1670
|
-
)
|
1671
|
-
|
1672
|
-
plot.figsets(ax=ax, boxloc="none")
|
1673
|
-
return ax
|
1674
|
-
|
1675
|
-
|
1676
|
-
def rank_models(
|
1677
|
-
cv_test_scores,
|
1678
|
-
rm_outlier=False,
|
1679
|
-
metric_weights=None,
|
1680
|
-
plot_=True,
|
1681
|
-
):
|
1682
|
-
"""
|
1683
|
-
Selects the best model based on a multi-metric scoring approach, with outlier handling, optional visualization,
|
1684
|
-
and additional performance metrics.
|
1685
|
-
|
1686
|
-
Parameters:
|
1687
|
-
- cv_test_scores (pd.DataFrame): DataFrame with cross-validation results across multiple metrics.
|
1688
|
-
Assumes columns are 'Classifier', 'accuracy', 'precision', 'recall', 'f1', 'roc_auc'.
|
1689
|
-
- metric_weights (dict): Dictionary specifying weights for each metric (e.g., {'accuracy': 0.2, 'precision': 0.3, ...}).
|
1690
|
-
If None, default weights are applied equally across available metrics.
|
1691
|
-
a. equal_weights(standard approch): 所有的metrics同等重要
|
1692
|
-
e.g., {"accuracy": 0.2, "precision": 0.2, "recall": 0.2, "f1": 0.2, "roc_auc": 0.2}
|
1693
|
-
b. accuracy_focosed: classification correctness (e.g., in balanced datasets), accuracy might be weighted more heavily.
|
1694
|
-
e.g., {"accuracy": 0.4, "precision": 0.2, "recall": 0.2, "f1": 0.1, "roc_auc": 0.1}
|
1695
|
-
c. Precision and Recall Emphasis: In cases where false positives and false negatives are particularly important (such as
|
1696
|
-
in medical applications or fraud detection), precision and recall may be weighted more heavily.
|
1697
|
-
e.g., {"accuracy": 0.2, "precision": 0.3, "recall": 0.3, "f1": 0.1, "roc_auc": 0.1}
|
1698
|
-
d. F1-Focused: When balance between precision and recall is crucial (e.g., in imbalanced datasets)
|
1699
|
-
e.g., {"accuracy": 0.2, "precision": 0.2, "recall": 0.2, "f1": 0.3, "roc_auc": 0.1}
|
1700
|
-
e. ROC-AUC Emphasis: In some cases, ROC AUC may be prioritized, particularly in classification tasks where class imbalance
|
1701
|
-
is present, as ROC AUC accounts for the model's performance across all classification thresholds.
|
1702
|
-
e.g., {"accuracy": 0.1, "precision": 0.2, "recall": 0.2, "f1": 0.3, "roc_auc": 0.3}
|
1703
|
-
|
1704
|
-
- normalize (bool): Whether to normalize scores of each metric to range [0, 1].
|
1705
|
-
- visualize (bool): If True, generates visualizations (e.g., bar plot, radar chart).
|
1706
|
-
- outlier_threshold (float): The threshold to detect outliers using the IQR method. Default is 1.5.
|
1707
|
-
- cv_folds (int): The number of cross-validation folds used.
|
1708
|
-
|
1709
|
-
Returns:
|
1710
|
-
- best_model (str): Name of the best model based on the combined metric scores.
|
1711
|
-
- scored_df (pd.DataFrame): DataFrame with an added 'combined_score' column used for model selection.
|
1712
|
-
- visualizations (dict): A dictionary containing visualizations if `visualize=True`.
|
1713
|
-
"""
|
1714
|
-
from sklearn.preprocessing import MinMaxScaler
|
1715
|
-
import seaborn as sns
|
1716
|
-
import matplotlib.pyplot as plt
|
1717
|
-
from py2ls import plot
|
1718
|
-
|
1719
|
-
# Check for missing metrics and set default weights if not provided
|
1720
|
-
available_metrics = cv_test_scores.columns[1:] # Exclude 'Classifier' column
|
1721
|
-
if metric_weights is None:
|
1722
|
-
metric_weights = {
|
1723
|
-
metric: 1 / len(available_metrics) for metric in available_metrics
|
1724
|
-
} # Equal weight if not specified
|
1725
|
-
elif metric_weights == "a":
|
1726
|
-
metric_weights = {
|
1727
|
-
"accuracy": 0.2,
|
1728
|
-
"precision": 0.2,
|
1729
|
-
"recall": 0.2,
|
1730
|
-
"f1": 0.2,
|
1731
|
-
"roc_auc": 0.2,
|
1732
|
-
}
|
1733
|
-
elif metric_weights == "b":
|
1734
|
-
metric_weights = {
|
1735
|
-
"accuracy": 0.4,
|
1736
|
-
"precision": 0.2,
|
1737
|
-
"recall": 0.2,
|
1738
|
-
"f1": 0.1,
|
1739
|
-
"roc_auc": 0.1,
|
1740
|
-
}
|
1741
|
-
elif metric_weights == "c":
|
1742
|
-
metric_weights = {
|
1743
|
-
"accuracy": 0.2,
|
1744
|
-
"precision": 0.3,
|
1745
|
-
"recall": 0.3,
|
1746
|
-
"f1": 0.1,
|
1747
|
-
"roc_auc": 0.1,
|
1748
|
-
}
|
1749
|
-
elif metric_weights == "d":
|
1750
|
-
metric_weights = {
|
1751
|
-
"accuracy": 0.2,
|
1752
|
-
"precision": 0.2,
|
1753
|
-
"recall": 0.2,
|
1754
|
-
"f1": 0.3,
|
1755
|
-
"roc_auc": 0.1,
|
1756
|
-
}
|
1757
|
-
elif metric_weights == "e":
|
1758
|
-
metric_weights = {
|
1759
|
-
"accuracy": 0.1,
|
1760
|
-
"precision": 0.2,
|
1761
|
-
"recall": 0.2,
|
1762
|
-
"f1": 0.3,
|
1763
|
-
"roc_auc": 0.3,
|
1764
|
-
}
|
1765
|
-
else:
|
1766
|
-
metric_weights = {
|
1767
|
-
metric: 1 / len(available_metrics) for metric in available_metrics
|
1768
|
-
}
|
1769
|
-
|
1770
|
-
# Normalize weights if they don’t sum to 1
|
1771
|
-
total_weight = sum(metric_weights.values())
|
1772
|
-
metric_weights = {
|
1773
|
-
metric: weight / total_weight for metric, weight in metric_weights.items()
|
1774
|
-
}
|
1775
|
-
if rm_outlier:
|
1776
|
-
cv_test_scores_ = ips.df_outlier(cv_test_scores)
|
1777
|
-
else:
|
1778
|
-
cv_test_scores_ = cv_test_scores
|
1779
|
-
|
1780
|
-
# Normalize the scores of metrics if normalize is True
|
1781
|
-
scaler = MinMaxScaler()
|
1782
|
-
normalized_scores = pd.DataFrame(
|
1783
|
-
scaler.fit_transform(cv_test_scores_[available_metrics]),
|
1784
|
-
columns=available_metrics,
|
1785
|
-
)
|
1786
|
-
cv_test_scores_ = pd.concat(
|
1787
|
-
[cv_test_scores_[["Classifier"]], normalized_scores], axis=1
|
1788
|
-
)
|
1789
|
-
|
1790
|
-
# Calculate weighted scores for each model
|
1791
|
-
cv_test_scores_["combined_score"] = sum(
|
1792
|
-
cv_test_scores_[metric] * weight for metric, weight in metric_weights.items()
|
1793
|
-
)
|
1794
|
-
top_models = cv_test_scores_.sort_values(by="combined_score", ascending=False)
|
1795
|
-
cv_test_scores = cv_test_scores.loc[top_models.index]
|
1796
|
-
top_models.reset_index(drop=True, inplace=True)
|
1797
|
-
cv_test_scores.reset_index(drop=True, inplace=True)
|
1798
|
-
|
1799
|
-
if plot_:
|
1800
|
-
|
1801
|
-
def generate_bar_plot(ax, cv_test_scores):
|
1802
|
-
ax = plot.plotxy(
|
1803
|
-
y="Classifier", x="combined_score", data=cv_test_scores, kind="bar"
|
1804
|
-
)
|
1805
|
-
plt.title("Classifier Performance")
|
1806
|
-
plt.tight_layout()
|
1807
|
-
return plt
|
1808
|
-
|
1809
|
-
nexttile = plot.subplot(2, 2, figsize=[10, 7])
|
1810
|
-
generate_bar_plot(nexttile(), top_models.dropna())
|
1811
|
-
plot.radar(
|
1812
|
-
ax=nexttile(projection="polar"),
|
1813
|
-
data=cv_test_scores.set_index("Classifier"),
|
1814
|
-
ylim=[0.5, 1],
|
1815
|
-
color=plot.get_color(10),
|
1816
|
-
alpha=0.05,
|
1817
|
-
circular=1,
|
1818
|
-
)
|
1819
|
-
return cv_test_scores
|
1820
|
-
|
1821
|
-
|
1822
|
-
# # Example Usage:
|
1823
|
-
# metric_weights = {
|
1824
|
-
# "accuracy": 0.2,
|
1825
|
-
# "precision": 0.3,
|
1826
|
-
# "recall": 0.2,
|
1827
|
-
# "f1": 0.2,
|
1828
|
-
# "roc_auc": 0.1,
|
1829
|
-
# }
|
1830
|
-
# cv_test_scores = res["cv_test_scores"].copy()
|
1831
|
-
# best_model = rank_models(
|
1832
|
-
# cv_test_scores, metric_weights=metric_weights, normalize=True, plot_=True
|
1833
|
-
# )
|
1834
|
-
|
1835
|
-
# figsave("classifier_performance.pdf")
|
1836
|
-
|
1837
|
-
|
1838
|
-
def predict(
|
1839
|
-
x_train: pd.DataFrame,
|
1840
|
-
y_train: pd.Series,
|
1841
|
-
x_true: pd.DataFrame = None,
|
1842
|
-
y_true: Optional[pd.Series] = None,
|
1843
|
-
common_features: set = None,
|
1844
|
-
purpose: str = "classification", # 'classification' or 'regression'
|
1845
|
-
cls: Optional[Dict[str, Any]] = None,
|
1846
|
-
metrics: Optional[List[str]] = None,
|
1847
|
-
random_state: int = 1,
|
1848
|
-
smote: bool = False,
|
1849
|
-
n_jobs: int = -1,
|
1850
|
-
plot_: bool = True,
|
1851
|
-
dir_save: str = "./",
|
1852
|
-
test_size: float = 0.2, # specific only when x_true is None
|
1853
|
-
cv_folds: int = 5, # more cv_folds 得更加稳定,auc可能更低
|
1854
|
-
cv_level: str = "l", # "s":'low',"m":'medium',"l":"high"
|
1855
|
-
class_weight: str = "balanced",
|
1856
|
-
verbose: bool = False,
|
1857
|
-
) -> pd.DataFrame:
|
1858
|
-
"""
|
1859
|
-
第一种情况是内部拆分,第二种是直接预测,第三种是外部验证。
|
1860
|
-
Usage:
|
1861
|
-
(1). predict(x_train, y_train,...) 对 x_train 进行拆分训练/测试集,并在测试集上进行验证.
|
1862
|
-
predict 函数会根据 test_size 参数,将 x_train 和 y_train 拆分出内部测试集。然后模型会在拆分出的训练集上进行训练,并在测试集上验证效果。
|
1863
|
-
(2). predict(x_train, y_train, x_true,...)使用 x_train 和 y_train 训练并对 x_true 进行预测
|
1864
|
-
由于传入了 x_true,函数会跳过 x_train 的拆分,直接使用全部的 x_train 和 y_train 进行训练。然后对 x_true 进行预测,但由于没有提供 y_true,
|
1865
|
-
因此无法与真实值进行对比。
|
1866
|
-
(3). predict(x_train, y_train, x_true, y_true,...)使用 x_train 和 y_train 训练,并验证 x_true 与真实标签 y_true.
|
1867
|
-
predict 函数会在 x_train 和 y_train 上进行训练,并将 x_true 作为测试集。由于提供了 y_true,函数可以将预测结果与 y_true 进行对比,从而
|
1868
|
-
计算验证指标,完成对 x_true 的真正验证。
|
1869
|
-
trains and validates a variety of machine learning models for both classification and regression tasks.
|
1870
|
-
It supports hyperparameter tuning with grid search and includes additional features like cross-validation,
|
1871
|
-
feature scaling, and handling of class imbalance through SMOTE.
|
1872
|
-
|
1873
|
-
Parameters:
|
1874
|
-
- x_train (pd.DataFrame):Training feature data, structured with each row as an observation and each column as a feature.
|
1875
|
-
- y_train (pd.Series):Target variable for the training dataset.
|
1876
|
-
- x_true (pd.DataFrame, optional):Test feature data. If not provided, the function splits x_train based on test_size.
|
1877
|
-
- y_true (pd.Series, optional):Test target values. If not provided, y_train is split into training and testing sets.
|
1878
|
-
- common_features (set, optional):Specifies a subset of features common across training and test data.
|
1879
|
-
- purpose (str, default = "classification"):Defines whether the task is "classification" or "regression". Determines which
|
1880
|
-
metrics and models are applied.
|
1881
|
-
- cls (dict, optional):Dictionary to specify custom classifiers/regressors. Defaults to a set of common models if not provided.
|
1882
|
-
- metrics (list, optional):List of evaluation metrics (like accuracy, F1 score) used for model evaluation.
|
1883
|
-
- random_state (int, default = 1):Random seed to ensure reproducibility.
|
1884
|
-
- smote (bool, default = False):Applies Synthetic Minority Oversampling Technique (SMOTE) to address class imbalance if enabled.
|
1885
|
-
- n_jobs (int, default = -1):Number of parallel jobs for computation. Set to -1 to use all available cores.
|
1886
|
-
- plot_ (bool, default = True):If True, generates plots of the model evaluation metrics.
|
1887
|
-
- test_size (float, default = 0.2):Test data proportion if x_true is not provided.
|
1888
|
-
- cv_folds (int, default = 5):Number of cross-validation folds.
|
1889
|
-
- cv_level (str, default = "l"):Sets the detail level of cross-validation. "s" for low, "m" for medium, and "l" for high.
|
1890
|
-
- class_weight (str, default = "balanced"):Balances class weights in classification tasks.
|
1891
|
-
- verbose (bool, default = False):If True, prints detailed output during model training.
|
1892
|
-
- dir_save (str, default = "./"):Directory path to save plot outputs and results.
|
1893
|
-
|
1894
|
-
Key Steps in the Function:
|
1895
|
-
Model Initialization: Depending on purpose, initializes either classification or regression models.
|
1896
|
-
Feature Selection: Ensures training and test sets have matching feature columns.
|
1897
|
-
SMOTE Application: Balances classes if smote is enabled and the task is classification.
|
1898
|
-
Cross-Validation and Hyperparameter Tuning: Utilizes GridSearchCV for model tuning based on cv_level.
|
1899
|
-
Evaluation and Plotting: Outputs evaluation metrics like AUC, confusion matrices, and optional plotting of performance metrics.
|
1900
|
-
"""
|
1901
|
-
from tqdm import tqdm
|
1902
|
-
from sklearn.ensemble import (
|
1903
|
-
RandomForestClassifier,
|
1904
|
-
RandomForestRegressor,
|
1905
|
-
ExtraTreesClassifier,
|
1906
|
-
ExtraTreesRegressor,
|
1907
|
-
BaggingClassifier,
|
1908
|
-
BaggingRegressor,
|
1909
|
-
AdaBoostClassifier,
|
1910
|
-
AdaBoostRegressor,
|
1911
|
-
)
|
1912
|
-
from sklearn.svm import SVC, SVR
|
1913
|
-
from sklearn.tree import DecisionTreeRegressor
|
1914
|
-
from sklearn.linear_model import (
|
1915
|
-
LogisticRegression,
|
1916
|
-
ElasticNet,
|
1917
|
-
ElasticNetCV,
|
1918
|
-
LinearRegression,
|
1919
|
-
Lasso,
|
1920
|
-
RidgeClassifierCV,
|
1921
|
-
Perceptron,
|
1922
|
-
SGDClassifier,
|
1923
|
-
)
|
1924
|
-
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
|
1925
|
-
from sklearn.naive_bayes import GaussianNB, BernoulliNB
|
1926
|
-
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
|
1927
|
-
import xgboost as xgb
|
1928
|
-
import lightgbm as lgb
|
1929
|
-
import catboost as cb
|
1930
|
-
from sklearn.neural_network import MLPClassifier, MLPRegressor
|
1931
|
-
from sklearn.model_selection import GridSearchCV, StratifiedKFold, KFold
|
1932
|
-
from sklearn.discriminant_analysis import (
|
1933
|
-
LinearDiscriminantAnalysis,
|
1934
|
-
QuadraticDiscriminantAnalysis,
|
1935
|
-
)
|
1936
|
-
from sklearn.preprocessing import PolynomialFeatures
|
1937
|
-
|
1938
|
-
# 拼写检查
|
1939
|
-
purpose = ips.strcmp(purpose, ["classification", "regression"])[0]
|
1940
|
-
print(f"{purpose} processing...")
|
1941
|
-
# Default models or regressors if not provided
|
1942
|
-
if purpose == "classification":
|
1943
|
-
model_ = {
|
1944
|
-
"Random Forest": RandomForestClassifier(
|
1945
|
-
random_state=random_state, class_weight=class_weight
|
1946
|
-
),
|
1947
|
-
# SVC (Support Vector Classification)
|
1948
|
-
"SVM": SVC(
|
1949
|
-
kernel="rbf",
|
1950
|
-
probability=True,
|
1951
|
-
class_weight=class_weight,
|
1952
|
-
random_state=random_state,
|
1953
|
-
),
|
1954
|
-
# fit the best model without enforcing sparsity, which means it does not directly perform feature selection.
|
1955
|
-
"Logistic Regression": LogisticRegression(
|
1956
|
-
class_weight=class_weight, random_state=random_state
|
1957
|
-
),
|
1958
|
-
# Logistic Regression with L1 Regularization (Lasso)
|
1959
|
-
"Lasso Logistic Regression": LogisticRegression(
|
1960
|
-
penalty="l1", solver="saga", random_state=random_state
|
1961
|
-
),
|
1962
|
-
"Gradient Boosting": GradientBoostingClassifier(random_state=random_state),
|
1963
|
-
"XGBoost": xgb.XGBClassifier(
|
1964
|
-
eval_metric="logloss",
|
1965
|
-
random_state=random_state,
|
1966
|
-
),
|
1967
|
-
"KNN": KNeighborsClassifier(n_neighbors=5),
|
1968
|
-
"Naive Bayes": GaussianNB(),
|
1969
|
-
"Linear Discriminant Analysis": LinearDiscriminantAnalysis(),
|
1970
|
-
"AdaBoost": AdaBoostClassifier(
|
1971
|
-
algorithm="SAMME", random_state=random_state
|
1972
|
-
),
|
1973
|
-
# "LightGBM": lgb.LGBMClassifier(random_state=random_state, class_weight=class_weight),
|
1974
|
-
"CatBoost": cb.CatBoostClassifier(verbose=0, random_state=random_state),
|
1975
|
-
"Extra Trees": ExtraTreesClassifier(
|
1976
|
-
random_state=random_state, class_weight=class_weight
|
1977
|
-
),
|
1978
|
-
"Bagging": BaggingClassifier(random_state=random_state),
|
1979
|
-
"Neural Network": MLPClassifier(max_iter=500, random_state=random_state),
|
1980
|
-
"DecisionTree": DecisionTreeClassifier(),
|
1981
|
-
"Quadratic Discriminant Analysis": QuadraticDiscriminantAnalysis(),
|
1982
|
-
"Ridge": RidgeClassifierCV(
|
1983
|
-
class_weight=class_weight, store_cv_results=True
|
1984
|
-
),
|
1985
|
-
"Perceptron": Perceptron(random_state=random_state),
|
1986
|
-
"Bernoulli Naive Bayes": BernoulliNB(),
|
1987
|
-
"SGDClassifier": SGDClassifier(random_state=random_state),
|
1988
|
-
}
|
1989
|
-
elif purpose == "regression":
|
1990
|
-
model_ = {
|
1991
|
-
"Random Forest": RandomForestRegressor(random_state=random_state),
|
1992
|
-
"SVM": SVR(), # SVR (Support Vector Regression)
|
1993
|
-
# "Lasso": Lasso(random_state=random_state), # 它和LassoCV相同(必须要提供alpha参数),
|
1994
|
-
"LassoCV": LassoCV(
|
1995
|
-
cv=cv_folds, random_state=random_state
|
1996
|
-
), # LassoCV自动找出最适alpha,优于Lasso
|
1997
|
-
"Gradient Boosting": GradientBoostingRegressor(random_state=random_state),
|
1998
|
-
"XGBoost": xgb.XGBRegressor(eval_metric="rmse", random_state=random_state),
|
1999
|
-
"Linear Regression": LinearRegression(),
|
2000
|
-
"Lasso": Lasso(random_state=random_state),
|
2001
|
-
"AdaBoost": AdaBoostRegressor(random_state=random_state),
|
2002
|
-
# "LightGBM": lgb.LGBMRegressor(random_state=random_state),
|
2003
|
-
"CatBoost": cb.CatBoostRegressor(verbose=0, random_state=random_state),
|
2004
|
-
"Extra Trees": ExtraTreesRegressor(random_state=random_state),
|
2005
|
-
"Bagging": BaggingRegressor(random_state=random_state),
|
2006
|
-
"Neural Network": MLPRegressor(max_iter=500, random_state=random_state),
|
2007
|
-
"ElasticNet": ElasticNet(random_state=random_state),
|
2008
|
-
"Ridge": Ridge(),
|
2009
|
-
"KNN": KNeighborsRegressor(),
|
2010
|
-
}
|
2011
|
-
# indicate cls:
|
2012
|
-
if ips.run_once_within(30): # 10 min
|
2013
|
-
print(f"supported models: {list(model_.keys())}")
|
2014
|
-
if cls is None:
|
2015
|
-
models = model_
|
2016
|
-
else:
|
2017
|
-
if not isinstance(cls, list):
|
2018
|
-
cls = [cls]
|
2019
|
-
models = {}
|
2020
|
-
for cls_ in cls:
|
2021
|
-
cls_ = ips.strcmp(cls_, list(model_.keys()))[0]
|
2022
|
-
models[cls_] = model_[cls_]
|
2023
|
-
if "LightGBM" in models:
|
2024
|
-
x_train = ips.df_special_characters_cleaner(x_train)
|
2025
|
-
x_true = (
|
2026
|
-
ips.df_special_characters_cleaner(x_true) if x_true is not None else None
|
2027
|
-
)
|
2028
|
-
|
2029
|
-
if isinstance(y_train, str) and y_train in x_train.columns:
|
2030
|
-
y_train_col_name = y_train
|
2031
|
-
y_train = x_train[y_train]
|
2032
|
-
# y_train = ips.df_encoder(pd.DataFrame(y_train), method="dummy")
|
2033
|
-
x_train = x_train.drop(y_train_col_name, axis=1)
|
2034
|
-
# else:
|
2035
|
-
# y_train = ips.df_encoder(pd.DataFrame(y_train), method="dummy").values.ravel()
|
2036
|
-
y_train=pd.DataFrame(y_train)
|
2037
|
-
y_train_=ips.df_encoder(y_train, method="dummy")
|
2038
|
-
is_binary = False if y_train_.shape[1] >1 else True
|
2039
|
-
print(is_binary)
|
2040
|
-
if is_binary:
|
2041
|
-
y_train = ips.df_encoder(pd.DataFrame(y_train), method="dummy").values.ravel()
|
2042
|
-
if x_true is None:
|
2043
|
-
x_train, x_true, y_train, y_true = train_test_split(
|
2044
|
-
x_train,
|
2045
|
-
y_train,
|
2046
|
-
test_size=test_size,
|
2047
|
-
random_state=random_state,
|
2048
|
-
stratify=y_train if purpose == "classification" else None,
|
2049
|
-
)
|
2050
|
-
if isinstance(y_train, str) and y_train in x_train.columns:
|
2051
|
-
y_train_col_name = y_train
|
2052
|
-
y_train = x_train[y_train]
|
2053
|
-
y_train = ips.df_encoder(pd.DataFrame(y_train), method="dummy")
|
2054
|
-
x_train = x_train.drop(y_train_col_name, axis=1)
|
2055
|
-
else:
|
2056
|
-
y_train = ips.df_encoder(
|
2057
|
-
pd.DataFrame(y_train), method="dummy"
|
2058
|
-
).values.ravel()
|
2059
|
-
|
2060
|
-
if y_true is not None:
|
2061
|
-
if isinstance(y_true, str) and y_true in x_true.columns:
|
2062
|
-
y_true_col_name = y_true
|
2063
|
-
y_true = x_true[y_true]
|
2064
|
-
# y_true = ips.df_encoder(pd.DataFrame(y_true), method="dummy")
|
2065
|
-
y_true = pd.DataFrame(y_true)
|
2066
|
-
x_true = x_true.drop(y_true_col_name, axis=1)
|
2067
|
-
# else:
|
2068
|
-
# y_true = ips.df_encoder(pd.DataFrame(y_true), method="dummy").values.ravel()
|
2069
|
-
|
2070
|
-
# to convert the 2D to 1D: 2D column-vector format (like [[1], [0], [1], ...]) instead of a 1D array ([1, 0, 1, ...]
|
2071
|
-
|
2072
|
-
# y_train=y_train.values.ravel() if y_train is not None else None
|
2073
|
-
# y_true=y_true.values.ravel() if y_true is not None else None
|
2074
|
-
y_train = (
|
2075
|
-
y_train.ravel() if isinstance(y_train, np.ndarray) else y_train.values.ravel()
|
2076
|
-
)
|
2077
|
-
print(len(y_train),len(y_true))
|
2078
|
-
y_true = y_true.ravel() if isinstance(y_true, np.ndarray) else y_true.values.ravel()
|
2079
|
-
print(len(y_train),len(y_true))
|
2080
|
-
# Ensure common features are selected
|
2081
|
-
if common_features is not None:
|
2082
|
-
x_train, x_true = x_train[common_features], x_true[common_features]
|
2083
|
-
else:
|
2084
|
-
share_col_names = ips.shared(x_train.columns, x_true.columns, verbose=verbose)
|
2085
|
-
x_train, x_true = x_train[share_col_names], x_true[share_col_names]
|
2086
|
-
|
2087
|
-
x_train, x_true = ips.df_scaler(x_train), ips.df_scaler(x_true)
|
2088
|
-
x_train, x_true = ips.df_encoder(x_train, method="dummy"), ips.df_encoder(x_true, method="dummy")
|
2089
|
-
# Handle class imbalance using SMOTE (only for classification)
|
2090
|
-
if (
|
2091
|
-
smote
|
2092
|
-
and purpose == "classification"
|
2093
|
-
and y_train.value_counts(normalize=True).max() < 0.8
|
2094
|
-
):
|
2095
|
-
from imblearn.over_sampling import SMOTE
|
2096
|
-
|
2097
|
-
smote_sampler = SMOTE(random_state=random_state)
|
2098
|
-
x_train, y_train = smote_sampler.fit_resample(x_train, y_train)
|
2099
|
-
|
2100
|
-
# Hyperparameter grids for tuning
|
2101
|
-
if cv_level in ["low", "simple", "s", "l"]:
|
2102
|
-
param_grids = {
|
2103
|
-
"Random Forest": (
|
2104
|
-
{
|
2105
|
-
"n_estimators": [100], # One basic option
|
2106
|
-
"max_depth": [None, 10],
|
2107
|
-
"min_samples_split": [2],
|
2108
|
-
"min_samples_leaf": [1],
|
2109
|
-
"class_weight": [None],
|
2110
|
-
}
|
2111
|
-
if purpose == "classification"
|
2112
|
-
else {
|
2113
|
-
"n_estimators": [100], # One basic option
|
2114
|
-
"max_depth": [None, 10],
|
2115
|
-
"min_samples_split": [2],
|
2116
|
-
"min_samples_leaf": [1],
|
2117
|
-
"max_features": [None],
|
2118
|
-
"bootstrap": [True], # Only one option for simplicity
|
2119
|
-
}
|
2120
|
-
),
|
2121
|
-
"SVM": {
|
2122
|
-
"C": [1],
|
2123
|
-
"gamma": ["scale"],
|
2124
|
-
"kernel": ["rbf"],
|
2125
|
-
},
|
2126
|
-
"Lasso": {
|
2127
|
-
"alpha": [0.1],
|
2128
|
-
},
|
2129
|
-
"LassoCV": {
|
2130
|
-
"alphas": [[0.1]],
|
2131
|
-
},
|
2132
|
-
"Logistic Regression": {
|
2133
|
-
"C": [1],
|
2134
|
-
"solver": ["lbfgs"],
|
2135
|
-
"penalty": ["l2"],
|
2136
|
-
"max_iter": [500],
|
2137
|
-
},
|
2138
|
-
"Gradient Boosting": {
|
2139
|
-
"n_estimators": [100],
|
2140
|
-
"learning_rate": [0.1],
|
2141
|
-
"max_depth": [3],
|
2142
|
-
"min_samples_split": [2],
|
2143
|
-
"subsample": [0.8],
|
2144
|
-
},
|
2145
|
-
"XGBoost": {
|
2146
|
-
"n_estimators": [100],
|
2147
|
-
"max_depth": [3],
|
2148
|
-
"learning_rate": [0.1],
|
2149
|
-
"subsample": [0.8],
|
2150
|
-
"colsample_bytree": [0.8],
|
2151
|
-
},
|
2152
|
-
"KNN": (
|
2153
|
-
{
|
2154
|
-
"n_neighbors": [3],
|
2155
|
-
"weights": ["uniform"],
|
2156
|
-
"algorithm": ["auto"],
|
2157
|
-
"p": [2],
|
2158
|
-
}
|
2159
|
-
if purpose == "classification"
|
2160
|
-
else {
|
2161
|
-
"n_neighbors": [3],
|
2162
|
-
"weights": ["uniform"],
|
2163
|
-
"metric": ["euclidean"],
|
2164
|
-
"leaf_size": [30],
|
2165
|
-
"p": [2],
|
2166
|
-
}
|
2167
|
-
),
|
2168
|
-
"Naive Bayes": {
|
2169
|
-
"var_smoothing": [1e-9],
|
2170
|
-
},
|
2171
|
-
"SVR": {
|
2172
|
-
"C": [1],
|
2173
|
-
"gamma": ["scale"],
|
2174
|
-
"kernel": ["rbf"],
|
2175
|
-
},
|
2176
|
-
"Linear Regression": {
|
2177
|
-
"fit_intercept": [True],
|
2178
|
-
},
|
2179
|
-
"Extra Trees": {
|
2180
|
-
"n_estimators": [100],
|
2181
|
-
"max_depth": [None, 10],
|
2182
|
-
"min_samples_split": [2],
|
2183
|
-
"min_samples_leaf": [1],
|
2184
|
-
},
|
2185
|
-
"CatBoost": {
|
2186
|
-
"iterations": [100],
|
2187
|
-
"learning_rate": [0.1],
|
2188
|
-
"depth": [3],
|
2189
|
-
"l2_leaf_reg": [1],
|
2190
|
-
},
|
2191
|
-
"LightGBM": {
|
2192
|
-
"n_estimators": [100],
|
2193
|
-
"num_leaves": [31],
|
2194
|
-
"max_depth": [10],
|
2195
|
-
"min_data_in_leaf": [20],
|
2196
|
-
"min_gain_to_split": [0.01],
|
2197
|
-
"scale_pos_weight": [10],
|
2198
|
-
},
|
2199
|
-
"Bagging": {
|
2200
|
-
"n_estimators": [50],
|
2201
|
-
"max_samples": [0.7],
|
2202
|
-
"max_features": [0.7],
|
2203
|
-
},
|
2204
|
-
"Neural Network": {
|
2205
|
-
"hidden_layer_sizes": [(50,)],
|
2206
|
-
"activation": ["relu"],
|
2207
|
-
"solver": ["adam"],
|
2208
|
-
"alpha": [0.0001],
|
2209
|
-
},
|
2210
|
-
"Decision Tree": {
|
2211
|
-
"max_depth": [None, 10],
|
2212
|
-
"min_samples_split": [2],
|
2213
|
-
"min_samples_leaf": [1],
|
2214
|
-
"criterion": ["gini"],
|
2215
|
-
},
|
2216
|
-
"AdaBoost": {
|
2217
|
-
"n_estimators": [50],
|
2218
|
-
"learning_rate": [0.5],
|
2219
|
-
},
|
2220
|
-
"Linear Discriminant Analysis": {
|
2221
|
-
"solver": ["svd"],
|
2222
|
-
"shrinkage": [None],
|
2223
|
-
},
|
2224
|
-
"Quadratic Discriminant Analysis": {
|
2225
|
-
"reg_param": [0.0],
|
2226
|
-
"priors": [None],
|
2227
|
-
"tol": [1e-4],
|
2228
|
-
},
|
2229
|
-
"Ridge": (
|
2230
|
-
{"class_weight": [None, "balanced"]}
|
2231
|
-
if purpose == "classification"
|
2232
|
-
else {
|
2233
|
-
"alpha": [0.1, 1, 10],
|
2234
|
-
}
|
2235
|
-
),
|
2236
|
-
"Perceptron": {
|
2237
|
-
"alpha": [1e-3],
|
2238
|
-
"penalty": ["l2"],
|
2239
|
-
"max_iter": [1000],
|
2240
|
-
"eta0": [1.0],
|
2241
|
-
},
|
2242
|
-
"Bernoulli Naive Bayes": {
|
2243
|
-
"alpha": [0.1, 1, 10],
|
2244
|
-
"binarize": [0.0],
|
2245
|
-
"fit_prior": [True],
|
2246
|
-
},
|
2247
|
-
"SGDClassifier": {
|
2248
|
-
"eta0": [0.01],
|
2249
|
-
"loss": ["hinge"],
|
2250
|
-
"penalty": ["l2"],
|
2251
|
-
"alpha": [1e-3],
|
2252
|
-
"max_iter": [1000],
|
2253
|
-
"tol": [1e-3],
|
2254
|
-
"random_state": [random_state],
|
2255
|
-
"learning_rate": ["constant"],
|
2256
|
-
},
|
2257
|
-
}
|
2258
|
-
elif cv_level in ["high", "advanced", "h"]:
|
2259
|
-
param_grids = {
|
2260
|
-
"Random Forest": (
|
2261
|
-
{
|
2262
|
-
"n_estimators": [100, 200, 500, 700, 1000],
|
2263
|
-
"max_depth": [None, 3, 5, 10, 15, 20, 30],
|
2264
|
-
"min_samples_split": [2, 5, 10, 20],
|
2265
|
-
"min_samples_leaf": [1, 2, 4],
|
2266
|
-
"class_weight": (
|
2267
|
-
[None, "balanced"] if purpose == "classification" else {}
|
2268
|
-
),
|
2269
|
-
}
|
2270
|
-
if purpose == "classification"
|
2271
|
-
else {
|
2272
|
-
"n_estimators": [100, 200, 500, 700, 1000],
|
2273
|
-
"max_depth": [None, 3, 5, 10, 15, 20, 30],
|
2274
|
-
"min_samples_split": [2, 5, 10, 20],
|
2275
|
-
"min_samples_leaf": [1, 2, 4],
|
2276
|
-
"max_features": [
|
2277
|
-
"auto",
|
2278
|
-
"sqrt",
|
2279
|
-
"log2",
|
2280
|
-
], # Number of features to consider when looking for the best split
|
2281
|
-
"bootstrap": [
|
2282
|
-
True,
|
2283
|
-
False,
|
2284
|
-
], # Whether bootstrap samples are used when building trees
|
2285
|
-
}
|
2286
|
-
),
|
2287
|
-
"SVM": {
|
2288
|
-
"C": [0.001, 0.01, 0.1, 1, 10, 100, 1000],
|
2289
|
-
"gamma": ["scale", "auto", 0.001, 0.01, 0.1],
|
2290
|
-
"kernel": ["linear", "rbf", "poly"],
|
2291
|
-
},
|
2292
|
-
"Logistic Regression": {
|
2293
|
-
"C": [0.001, 0.01, 0.1, 1, 10, 100, 1000],
|
2294
|
-
"solver": ["liblinear", "saga", "newton-cg", "lbfgs"],
|
2295
|
-
"penalty": ["l1", "l2", "elasticnet"],
|
2296
|
-
"max_iter": [100, 200, 300, 500],
|
2297
|
-
},
|
2298
|
-
"Lasso": {
|
2299
|
-
"alpha": [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0],
|
2300
|
-
"max_iter": [500, 1000, 2000, 5000],
|
2301
|
-
"tol": [1e-4, 1e-5, 1e-6],
|
2302
|
-
"selection": ["cyclic", "random"],
|
2303
|
-
},
|
2304
|
-
"LassoCV": {
|
2305
|
-
"alphas": [[0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0]],
|
2306
|
-
"max_iter": [500, 1000, 2000, 5000],
|
2307
|
-
"cv": [3, 5, 10],
|
2308
|
-
"tol": [1e-4, 1e-5, 1e-6],
|
2309
|
-
},
|
2310
|
-
"Gradient Boosting": {
|
2311
|
-
"n_estimators": [100, 200, 300, 400, 500, 700, 1000],
|
2312
|
-
"learning_rate": [0.001, 0.01, 0.1, 0.2, 0.3, 0.5],
|
2313
|
-
"max_depth": [3, 5, 7, 9, 15],
|
2314
|
-
"min_samples_split": [2, 5, 10, 20],
|
2315
|
-
"subsample": [0.8, 1.0],
|
2316
|
-
},
|
2317
|
-
"XGBoost": {
|
2318
|
-
"n_estimators": [100, 200, 500, 700],
|
2319
|
-
"max_depth": [3, 5, 7, 10],
|
2320
|
-
"learning_rate": [0.01, 0.1, 0.2, 0.3],
|
2321
|
-
"subsample": [0.8, 1.0],
|
2322
|
-
"colsample_bytree": [0.8, 0.9, 1.0],
|
2323
|
-
},
|
2324
|
-
"KNN": (
|
2325
|
-
{
|
2326
|
-
"n_neighbors": [1, 3, 5, 10, 15, 20],
|
2327
|
-
"weights": ["uniform", "distance"],
|
2328
|
-
"algorithm": ["auto", "ball_tree", "kd_tree", "brute"],
|
2329
|
-
"p": [1, 2], # 1 for Manhattan, 2 for Euclidean distance
|
2330
|
-
}
|
2331
|
-
if purpose == "classification"
|
2332
|
-
else {
|
2333
|
-
"n_neighbors": [3, 5, 7, 9, 11], # Number of neighbors
|
2334
|
-
"weights": [
|
2335
|
-
"uniform",
|
2336
|
-
"distance",
|
2337
|
-
], # Weight function used in prediction
|
2338
|
-
"metric": [
|
2339
|
-
"euclidean",
|
2340
|
-
"manhattan",
|
2341
|
-
"minkowski",
|
2342
|
-
], # Distance metric
|
2343
|
-
"leaf_size": [
|
2344
|
-
20,
|
2345
|
-
30,
|
2346
|
-
40,
|
2347
|
-
50,
|
2348
|
-
], # Leaf size for KDTree or BallTree algorithms
|
2349
|
-
"p": [
|
2350
|
-
1,
|
2351
|
-
2,
|
2352
|
-
], # Power parameter for the Minkowski metric (1 = Manhattan, 2 = Euclidean)
|
2353
|
-
}
|
2354
|
-
),
|
2355
|
-
"Naive Bayes": {
|
2356
|
-
"var_smoothing": [1e-10, 1e-9, 1e-8, 1e-7],
|
2357
|
-
},
|
2358
|
-
"AdaBoost": {
|
2359
|
-
"n_estimators": [50, 100, 200, 300, 500],
|
2360
|
-
"learning_rate": [0.001, 0.01, 0.1, 0.5, 1.0],
|
2361
|
-
},
|
2362
|
-
"SVR": {
|
2363
|
-
"C": [0.01, 0.1, 1, 10, 100, 1000],
|
2364
|
-
"gamma": [0.001, 0.01, 0.1, "scale", "auto"],
|
2365
|
-
"kernel": ["linear", "rbf", "poly"],
|
2366
|
-
},
|
2367
|
-
"Linear Regression": {
|
2368
|
-
"fit_intercept": [True, False],
|
2369
|
-
},
|
2370
|
-
"Lasso": {
|
2371
|
-
"alpha": [0.001, 0.01, 0.1, 1.0, 10.0, 100.0],
|
2372
|
-
"max_iter": [1000, 2000], # Higher iteration limit for fine-tuning
|
2373
|
-
},
|
2374
|
-
"Extra Trees": {
|
2375
|
-
"n_estimators": [100, 200, 500, 700, 1000],
|
2376
|
-
"max_depth": [None, 5, 10, 15, 20, 30],
|
2377
|
-
"min_samples_split": [2, 5, 10, 20],
|
2378
|
-
"min_samples_leaf": [1, 2, 4],
|
2379
|
-
},
|
2380
|
-
"CatBoost": {
|
2381
|
-
"iterations": [100, 200, 500],
|
2382
|
-
"learning_rate": [0.001, 0.01, 0.1, 0.2],
|
2383
|
-
"depth": [3, 5, 7, 10],
|
2384
|
-
"l2_leaf_reg": [1, 3, 5, 7, 10],
|
2385
|
-
"border_count": [32, 64, 128],
|
2386
|
-
},
|
2387
|
-
"LightGBM": {
|
2388
|
-
"n_estimators": [100, 200, 500, 700, 1000],
|
2389
|
-
"learning_rate": [0.001, 0.01, 0.1, 0.2],
|
2390
|
-
"num_leaves": [31, 50, 100, 200],
|
2391
|
-
"max_depth": [-1, 5, 10, 20, 30],
|
2392
|
-
"min_child_samples": [5, 10, 20],
|
2393
|
-
"subsample": [0.8, 1.0],
|
2394
|
-
"colsample_bytree": [0.8, 0.9, 1.0],
|
2395
|
-
},
|
2396
|
-
"Neural Network": {
|
2397
|
-
"hidden_layer_sizes": [(50,), (100,), (100, 50), (200, 100)],
|
2398
|
-
"activation": ["relu", "tanh", "logistic"],
|
2399
|
-
"solver": ["adam", "sgd", "lbfgs"],
|
2400
|
-
"alpha": [0.0001, 0.001, 0.01],
|
2401
|
-
"learning_rate": ["constant", "adaptive"],
|
2402
|
-
},
|
2403
|
-
"Decision Tree": {
|
2404
|
-
"max_depth": [None, 5, 10, 20, 30],
|
2405
|
-
"min_samples_split": [2, 5, 10, 20],
|
2406
|
-
"min_samples_leaf": [1, 2, 5, 10],
|
2407
|
-
"criterion": ["gini", "entropy"],
|
2408
|
-
"splitter": ["best", "random"],
|
2409
|
-
},
|
2410
|
-
"Linear Discriminant Analysis": {
|
2411
|
-
"solver": ["svd", "lsqr", "eigen"],
|
2412
|
-
"shrinkage": [
|
2413
|
-
None,
|
2414
|
-
"auto",
|
2415
|
-
0.1,
|
2416
|
-
0.5,
|
2417
|
-
1.0,
|
2418
|
-
], # shrinkage levels for 'lsqr' and 'eigen'
|
2419
|
-
},
|
2420
|
-
"Ridge": (
|
2421
|
-
{"class_weight": [None, "balanced"]}
|
2422
|
-
if purpose == "classification"
|
2423
|
-
else {
|
2424
|
-
"alpha": [0.1, 1, 10, 100, 1000],
|
2425
|
-
"solver": ["auto", "svd", "cholesky", "lsqr", "lbfgs"],
|
2426
|
-
"fit_intercept": [
|
2427
|
-
True,
|
2428
|
-
False,
|
2429
|
-
], # Whether to calculate the intercept
|
2430
|
-
"normalize": [
|
2431
|
-
True,
|
2432
|
-
False,
|
2433
|
-
], # If True, the regressors X will be normalized
|
2434
|
-
}
|
2435
|
-
),
|
2436
|
-
}
|
2437
|
-
else: # median level
|
2438
|
-
param_grids = {
|
2439
|
-
"Random Forest": (
|
2440
|
-
{
|
2441
|
-
"n_estimators": [100, 200, 500],
|
2442
|
-
"max_depth": [None, 10, 20, 30],
|
2443
|
-
"min_samples_split": [2, 5, 10],
|
2444
|
-
"min_samples_leaf": [1, 2, 4],
|
2445
|
-
"class_weight": [None, "balanced"],
|
2446
|
-
}
|
2447
|
-
if purpose == "classification"
|
2448
|
-
else {
|
2449
|
-
"n_estimators": [100, 200, 500],
|
2450
|
-
"max_depth": [None, 10, 20, 30],
|
2451
|
-
"min_samples_split": [2, 5, 10],
|
2452
|
-
"min_samples_leaf": [1, 2, 4],
|
2453
|
-
"max_features": [
|
2454
|
-
"auto",
|
2455
|
-
"sqrt",
|
2456
|
-
"log2",
|
2457
|
-
], # Number of features to consider when looking for the best split
|
2458
|
-
"bootstrap": [
|
2459
|
-
True,
|
2460
|
-
False,
|
2461
|
-
], # Whether bootstrap samples are used when building trees
|
2462
|
-
}
|
2463
|
-
),
|
2464
|
-
"SVM": {
|
2465
|
-
"C": [0.1, 1, 10, 100], # Regularization strength
|
2466
|
-
"gamma": ["scale", "auto"], # Common gamma values
|
2467
|
-
"kernel": ["rbf", "linear", "poly"],
|
2468
|
-
},
|
2469
|
-
"Logistic Regression": {
|
2470
|
-
"C": [0.1, 1, 10, 100], # Regularization strength
|
2471
|
-
"solver": ["lbfgs", "liblinear", "saga"], # Common solvers
|
2472
|
-
"penalty": ["l2"], # L2 penalty is most common
|
2473
|
-
"max_iter": [
|
2474
|
-
500,
|
2475
|
-
1000,
|
2476
|
-
2000,
|
2477
|
-
], # Increased max_iter for better convergence
|
2478
|
-
},
|
2479
|
-
"Lasso": {
|
2480
|
-
"alpha": [0.001, 0.01, 0.1, 1.0, 10.0, 100.0],
|
2481
|
-
"max_iter": [500, 1000, 2000],
|
2482
|
-
},
|
2483
|
-
"LassoCV": {
|
2484
|
-
"alphas": [[0.001, 0.01, 0.1, 1.0, 10.0, 100.0]],
|
2485
|
-
"max_iter": [500, 1000, 2000],
|
2486
|
-
},
|
2487
|
-
"Gradient Boosting": {
|
2488
|
-
"n_estimators": [100, 200, 500],
|
2489
|
-
"learning_rate": [0.01, 0.1, 0.2],
|
2490
|
-
"max_depth": [3, 5, 7],
|
2491
|
-
"min_samples_split": [2, 5, 10],
|
2492
|
-
"subsample": [0.8, 1.0],
|
2493
|
-
},
|
2494
|
-
"XGBoost": {
|
2495
|
-
"n_estimators": [100, 200, 500],
|
2496
|
-
"max_depth": [3, 5, 7],
|
2497
|
-
"learning_rate": [0.01, 0.1, 0.2],
|
2498
|
-
"subsample": [0.8, 1.0],
|
2499
|
-
"colsample_bytree": [0.8, 1.0],
|
2500
|
-
},
|
2501
|
-
"KNN": (
|
2502
|
-
{
|
2503
|
-
"n_neighbors": [3, 5, 7, 10],
|
2504
|
-
"weights": ["uniform", "distance"],
|
2505
|
-
"algorithm": ["auto", "ball_tree", "kd_tree", "brute"],
|
2506
|
-
"p": [1, 2],
|
2507
|
-
}
|
2508
|
-
if purpose == "classification"
|
2509
|
-
else {
|
2510
|
-
"n_neighbors": [3, 5, 7, 9, 11], # Number of neighbors
|
2511
|
-
"weights": [
|
2512
|
-
"uniform",
|
2513
|
-
"distance",
|
2514
|
-
], # Weight function used in prediction
|
2515
|
-
"metric": [
|
2516
|
-
"euclidean",
|
2517
|
-
"manhattan",
|
2518
|
-
"minkowski",
|
2519
|
-
], # Distance metric
|
2520
|
-
"leaf_size": [
|
2521
|
-
20,
|
2522
|
-
30,
|
2523
|
-
40,
|
2524
|
-
50,
|
2525
|
-
], # Leaf size for KDTree or BallTree algorithms
|
2526
|
-
"p": [
|
2527
|
-
1,
|
2528
|
-
2,
|
2529
|
-
], # Power parameter for the Minkowski metric (1 = Manhattan, 2 = Euclidean)
|
2530
|
-
}
|
2531
|
-
),
|
2532
|
-
"Naive Bayes": {
|
2533
|
-
"var_smoothing": [1e-9, 1e-8, 1e-7],
|
2534
|
-
},
|
2535
|
-
"SVR": {
|
2536
|
-
"C": [0.1, 1, 10, 100],
|
2537
|
-
"gamma": ["scale", "auto"],
|
2538
|
-
"kernel": ["rbf", "linear"],
|
2539
|
-
},
|
2540
|
-
"Linear Regression": {
|
2541
|
-
"fit_intercept": [True, False],
|
2542
|
-
},
|
2543
|
-
"Lasso": {
|
2544
|
-
"alpha": [0.1, 1.0, 10.0],
|
2545
|
-
"max_iter": [1000, 2000], # Sufficient iterations for convergence
|
2546
|
-
},
|
2547
|
-
"Extra Trees": {
|
2548
|
-
"n_estimators": [100, 200, 500],
|
2549
|
-
"max_depth": [None, 10, 20, 30],
|
2550
|
-
"min_samples_split": [2, 5, 10],
|
2551
|
-
"min_samples_leaf": [1, 2, 4],
|
2552
|
-
},
|
2553
|
-
"CatBoost": {
|
2554
|
-
"iterations": [100, 200],
|
2555
|
-
"learning_rate": [0.01, 0.1],
|
2556
|
-
"depth": [3, 6, 10],
|
2557
|
-
"l2_leaf_reg": [1, 3, 5, 7],
|
2558
|
-
},
|
2559
|
-
"LightGBM": {
|
2560
|
-
"n_estimators": [100, 200, 500],
|
2561
|
-
"learning_rate": [0.01, 0.1],
|
2562
|
-
"num_leaves": [31, 50, 100],
|
2563
|
-
"max_depth": [-1, 10, 20],
|
2564
|
-
"min_data_in_leaf": [20], # Minimum samples in each leaf
|
2565
|
-
"min_gain_to_split": [0.01], # Minimum gain to allow a split
|
2566
|
-
"scale_pos_weight": [10], # Address class imbalance
|
2567
|
-
},
|
2568
|
-
"Bagging": {
|
2569
|
-
"n_estimators": [10, 50, 100],
|
2570
|
-
"max_samples": [0.5, 0.7, 1.0],
|
2571
|
-
"max_features": [0.5, 0.7, 1.0],
|
2572
|
-
},
|
2573
|
-
"Neural Network": {
|
2574
|
-
"hidden_layer_sizes": [(50,), (100,), (100, 50)],
|
2575
|
-
"activation": ["relu", "tanh"],
|
2576
|
-
"solver": ["adam", "sgd"],
|
2577
|
-
"alpha": [0.0001, 0.001],
|
2578
|
-
},
|
2579
|
-
"Decision Tree": {
|
2580
|
-
"max_depth": [None, 10, 20],
|
2581
|
-
"min_samples_split": [2, 10],
|
2582
|
-
"min_samples_leaf": [1, 4],
|
2583
|
-
"criterion": ["gini", "entropy"],
|
2584
|
-
},
|
2585
|
-
"AdaBoost": {
|
2586
|
-
"n_estimators": [50, 100],
|
2587
|
-
"learning_rate": [0.5, 1.0],
|
2588
|
-
},
|
2589
|
-
"Linear Discriminant Analysis": {
|
2590
|
-
"solver": ["svd", "lsqr", "eigen"],
|
2591
|
-
"shrinkage": [None, "auto"],
|
2592
|
-
},
|
2593
|
-
"Quadratic Discriminant Analysis": {
|
2594
|
-
"reg_param": [0.0, 0.1, 0.5, 1.0], # Regularization parameter
|
2595
|
-
"priors": [None, [0.5, 0.5], [0.3, 0.7]], # Class priors
|
2596
|
-
"tol": [
|
2597
|
-
1e-4,
|
2598
|
-
1e-3,
|
2599
|
-
1e-2,
|
2600
|
-
], # Tolerance value for the convergence of the algorithm
|
2601
|
-
},
|
2602
|
-
"Perceptron": {
|
2603
|
-
"alpha": [1e-4, 1e-3, 1e-2], # Regularization parameter
|
2604
|
-
"penalty": ["l2", "l1", "elasticnet"], # Regularization penalty
|
2605
|
-
"max_iter": [1000, 2000], # Maximum number of iterations
|
2606
|
-
"eta0": [1.0, 0.1], # Learning rate for gradient descent
|
2607
|
-
"tol": [1e-3, 1e-4, 1e-5], # Tolerance for stopping criteria
|
2608
|
-
"random_state": [random_state], # Random state for reproducibility
|
2609
|
-
},
|
2610
|
-
"Bernoulli Naive Bayes": {
|
2611
|
-
"alpha": [0.1, 1.0, 10.0], # Additive (Laplace) smoothing parameter
|
2612
|
-
"binarize": [
|
2613
|
-
0.0,
|
2614
|
-
0.5,
|
2615
|
-
1.0,
|
2616
|
-
], # Threshold for binarizing the input features
|
2617
|
-
"fit_prior": [
|
2618
|
-
True,
|
2619
|
-
False,
|
2620
|
-
], # Whether to learn class prior probabilities
|
2621
|
-
},
|
2622
|
-
"SGDClassifier": {
|
2623
|
-
"eta0": [0.01, 0.1, 1.0],
|
2624
|
-
"loss": [
|
2625
|
-
"hinge",
|
2626
|
-
"log",
|
2627
|
-
"modified_huber",
|
2628
|
-
"squared_hinge",
|
2629
|
-
"perceptron",
|
2630
|
-
], # Loss function
|
2631
|
-
"penalty": ["l2", "l1", "elasticnet"], # Regularization penalty
|
2632
|
-
"alpha": [1e-4, 1e-3, 1e-2], # Regularization strength
|
2633
|
-
"l1_ratio": [0.15, 0.5, 0.85], # L1 ratio for elasticnet penalty
|
2634
|
-
"max_iter": [1000, 2000], # Maximum number of iterations
|
2635
|
-
"tol": [1e-3, 1e-4], # Tolerance for stopping criteria
|
2636
|
-
"random_state": [random_state], # Random state for reproducibility
|
2637
|
-
"learning_rate": [
|
2638
|
-
"constant",
|
2639
|
-
"optimal",
|
2640
|
-
"invscaling",
|
2641
|
-
"adaptive",
|
2642
|
-
], # Learning rate schedule
|
2643
|
-
},
|
2644
|
-
"Ridge": (
|
2645
|
-
{"class_weight": [None, "balanced"]}
|
2646
|
-
if purpose == "classification"
|
2647
|
-
else {
|
2648
|
-
"alpha": [0.1, 1, 10, 100],
|
2649
|
-
"solver": [
|
2650
|
-
"auto",
|
2651
|
-
"svd",
|
2652
|
-
"cholesky",
|
2653
|
-
"lsqr",
|
2654
|
-
], # Solver for optimization
|
2655
|
-
}
|
2656
|
-
),
|
2657
|
-
}
|
2658
|
-
|
2659
|
-
results = {}
|
2660
|
-
# Use StratifiedKFold for classification and KFold for regression
|
2661
|
-
cv = (
|
2662
|
-
StratifiedKFold(n_splits=cv_folds, shuffle=True, random_state=random_state)
|
2663
|
-
if purpose == "classification"
|
2664
|
-
else KFold(n_splits=cv_folds, shuffle=True, random_state=random_state)
|
2665
|
-
)
|
2666
|
-
|
2667
|
-
# Train and validate each model
|
2668
|
-
for name, clf in tqdm(
|
2669
|
-
models.items(),
|
2670
|
-
desc="models",
|
2671
|
-
colour="green",
|
2672
|
-
bar_format="{l_bar}{bar} {n_fmt}/{total_fmt}",
|
2673
|
-
):
|
2674
|
-
if verbose:
|
2675
|
-
print(f"\nTraining and validating {name}:")
|
2676
|
-
|
2677
|
-
# Grid search with KFold or StratifiedKFold
|
2678
|
-
gs = GridSearchCV(
|
2679
|
-
clf,
|
2680
|
-
param_grid=param_grids.get(name, {}),
|
2681
|
-
scoring=(
|
2682
|
-
"roc_auc" if purpose == "classification" else "neg_mean_squared_error"
|
2683
|
-
),
|
2684
|
-
cv=cv,
|
2685
|
-
n_jobs=n_jobs,
|
2686
|
-
verbose=verbose,
|
2687
|
-
)
|
2688
|
-
gs.fit(x_train, y_train)
|
2689
|
-
best_clf = gs.best_estimator_
|
2690
|
-
# make sure x_train and x_test has the same name
|
2691
|
-
x_true = x_true.reindex(columns=x_train.columns, fill_value=0)
|
2692
|
-
y_pred = best_clf.predict(x_true)
|
2693
|
-
|
2694
|
-
# y_pred_proba
|
2695
|
-
if hasattr(best_clf, "predict_proba"):
|
2696
|
-
y_pred_proba = best_clf.predict_proba(x_true)[:, 1]
|
2697
|
-
elif hasattr(best_clf, "decision_function"):
|
2698
|
-
# If predict_proba is not available, use decision_function (e.g., for SVM)
|
2699
|
-
y_pred_proba = best_clf.decision_function(x_true)
|
2700
|
-
# Ensure y_pred_proba is within 0 and 1 bounds
|
2701
|
-
y_pred_proba = (y_pred_proba - y_pred_proba.min()) / (
|
2702
|
-
y_pred_proba.max() - y_pred_proba.min()
|
2703
|
-
)
|
2704
|
-
else:
|
2705
|
-
y_pred_proba = None # No probability output for certain models
|
2706
|
-
|
2707
|
-
validation_scores = {}
|
2708
|
-
if y_true is not None:
|
2709
|
-
validation_scores = cal_metrics(
|
2710
|
-
y_true,
|
2711
|
-
y_pred,
|
2712
|
-
y_pred_proba=y_pred_proba,
|
2713
|
-
is_binary=is_binary,
|
2714
|
-
purpose=purpose,
|
2715
|
-
average="weighted",
|
2716
|
-
)
|
2717
|
-
|
2718
|
-
# Calculate ROC curve
|
2719
|
-
# https://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html
|
2720
|
-
if y_pred_proba is not None:
|
2721
|
-
# fpr, tpr, roc_auc = dict(), dict(), dict()
|
2722
|
-
fpr, tpr, _ = roc_curve(y_true, y_pred_proba)
|
2723
|
-
lower_ci, upper_ci = cal_auc_ci(y_true, y_pred_proba, verbose=False)
|
2724
|
-
roc_auc = auc(fpr, tpr)
|
2725
|
-
roc_info = {
|
2726
|
-
"fpr": fpr.tolist(),
|
2727
|
-
"tpr": tpr.tolist(),
|
2728
|
-
"auc": roc_auc,
|
2729
|
-
"ci95": (lower_ci, upper_ci),
|
2730
|
-
}
|
2731
|
-
# precision-recall curve
|
2732
|
-
precision_, recall_, _ = precision_recall_curve(y_true, y_pred_proba)
|
2733
|
-
avg_precision_ = average_precision_score(y_true, y_pred_proba)
|
2734
|
-
pr_info = {
|
2735
|
-
"precision": precision_,
|
2736
|
-
"recall": recall_,
|
2737
|
-
"avg_precision": avg_precision_,
|
2738
|
-
}
|
2739
|
-
else:
|
2740
|
-
roc_info, pr_info = None, None
|
2741
|
-
if purpose == "classification":
|
2742
|
-
results[name] = {
|
2743
|
-
"best_clf": gs.best_estimator_,
|
2744
|
-
"best_params": gs.best_params_,
|
2745
|
-
"auc_indiv": [
|
2746
|
-
gs.cv_results_[f"split{i}_test_score"][gs.best_index_]
|
2747
|
-
for i in range(cv_folds)
|
2748
|
-
],
|
2749
|
-
"scores": validation_scores,
|
2750
|
-
"roc_curve": roc_info,
|
2751
|
-
"pr_curve": pr_info,
|
2752
|
-
"confusion_matrix": confusion_matrix(y_true, y_pred),
|
2753
|
-
"predictions": y_pred.tolist(),
|
2754
|
-
"predictions_proba": (
|
2755
|
-
y_pred_proba.tolist() if y_pred_proba is not None else None
|
2756
|
-
),
|
2757
|
-
}
|
2758
|
-
else: # "regression"
|
2759
|
-
results[name] = {
|
2760
|
-
"best_clf": gs.best_estimator_,
|
2761
|
-
"best_params": gs.best_params_,
|
2762
|
-
"scores": validation_scores, # e.g., neg_MSE, R², etc.
|
2763
|
-
"predictions": y_pred.tolist(),
|
2764
|
-
"predictions_proba": (
|
2765
|
-
y_pred_proba.tolist() if y_pred_proba is not None else None
|
2766
|
-
),
|
2767
|
-
}
|
2768
|
-
|
2769
|
-
else:
|
2770
|
-
results[name] = {
|
2771
|
-
"best_clf": gs.best_estimator_,
|
2772
|
-
"best_params": gs.best_params_,
|
2773
|
-
"scores": validation_scores,
|
2774
|
-
"predictions": y_pred.tolist(),
|
2775
|
-
"predictions_proba": (
|
2776
|
-
y_pred_proba.tolist() if y_pred_proba is not None else None
|
2777
|
-
),
|
2778
|
-
}
|
2779
|
-
|
2780
|
-
# Convert results to DataFrame
|
2781
|
-
df_results = pd.DataFrame.from_dict(results, orient="index")
|
2782
|
-
|
2783
|
-
# sort
|
2784
|
-
if y_true is not None and purpose == "classification":
|
2785
|
-
df_scores = pd.DataFrame(
|
2786
|
-
df_results["scores"].tolist(), index=df_results["scores"].index
|
2787
|
-
).sort_values(by="roc_auc", ascending=False)
|
2788
|
-
df_results = df_results.loc[df_scores.index]
|
2789
|
-
|
2790
|
-
if plot_:
|
2791
|
-
from datetime import datetime
|
2792
|
-
|
2793
|
-
now_ = datetime.now().strftime("%y%m%d_%H%M%S")
|
2794
|
-
nexttile = plot.subplot(figsize=[12, 10])
|
2795
|
-
plot.heatmap(df_scores, kind="direct", ax=nexttile())
|
2796
|
-
plot.figsets(xangle=30)
|
2797
|
-
if dir_save:
|
2798
|
-
ips.figsave(dir_save + f"scores_sorted_heatmap{now_}.pdf")
|
2799
|
-
if df_scores.shape[0] > 1: # draw cluster
|
2800
|
-
plot.heatmap(df_scores, kind="direct", cluster=True)
|
2801
|
-
plot.figsets(xangle=30)
|
2802
|
-
if dir_save:
|
2803
|
-
ips.figsave(dir_save + f"scores_clus{now_}.pdf")
|
2804
|
-
if all([plot_, y_true is not None, purpose == "classification"]):
|
2805
|
-
try:
|
2806
|
-
if len(models) > 3:
|
2807
|
-
plot_validate_features(df_results)
|
2808
|
-
else:
|
2809
|
-
plot_validate_features_single(df_results, figsize=(12, 4 * len(models)))
|
2810
|
-
if dir_save:
|
2811
|
-
ips.figsave(dir_save + f"validate_features{now_}.pdf")
|
2812
|
-
except Exception as e:
|
2813
|
-
print(f"Error: 在画图的过程中出现了问题:{e}")
|
2814
|
-
return df_results
|
2815
|
-
|
2816
|
-
|
2817
|
-
def cal_metrics(
|
2818
|
-
y_true, y_pred, y_pred_proba=None, is_binary=True,purpose="regression", average="weighted"
|
2819
|
-
):
|
2820
|
-
"""
|
2821
|
-
Calculate regression or classification metrics based on the purpose.
|
2822
|
-
|
2823
|
-
Parameters:
|
2824
|
-
- y_true: Array of true values.
|
2825
|
-
- y_pred: Array of predicted labels for classification or predicted values for regression.
|
2826
|
-
- y_pred_proba: Array of predicted probabilities for classification (optional).
|
2827
|
-
- purpose: str, "regression" or "classification".
|
2828
|
-
- average: str, averaging method for multi-class classification ("binary", "micro", "macro", "weighted", etc.).
|
2829
|
-
|
2830
|
-
Returns:
|
2831
|
-
- validation_scores: dict of computed metrics.
|
2832
|
-
"""
|
2833
|
-
from sklearn.metrics import (
|
2834
|
-
mean_squared_error,
|
2835
|
-
mean_absolute_error,
|
2836
|
-
mean_absolute_percentage_error,
|
2837
|
-
explained_variance_score,
|
2838
|
-
r2_score,
|
2839
|
-
mean_squared_log_error,
|
2840
|
-
accuracy_score,
|
2841
|
-
precision_score,
|
2842
|
-
recall_score,
|
2843
|
-
f1_score,
|
2844
|
-
roc_auc_score,
|
2845
|
-
matthews_corrcoef,
|
2846
|
-
confusion_matrix,
|
2847
|
-
balanced_accuracy_score,
|
2848
|
-
average_precision_score,
|
2849
|
-
precision_recall_curve,
|
2850
|
-
)
|
2851
|
-
|
2852
|
-
validation_scores = {}
|
2853
|
-
|
2854
|
-
if purpose == "regression":
|
2855
|
-
y_true = np.asarray(y_true)
|
2856
|
-
y_true = y_true.ravel()
|
2857
|
-
y_pred = np.asarray(y_pred)
|
2858
|
-
y_pred = y_pred.ravel()
|
2859
|
-
# Regression metrics
|
2860
|
-
validation_scores = {
|
2861
|
-
"mse": mean_squared_error(y_true, y_pred),
|
2862
|
-
"rmse": np.sqrt(mean_squared_error(y_true, y_pred)),
|
2863
|
-
"mae": mean_absolute_error(y_true, y_pred),
|
2864
|
-
"r2": r2_score(y_true, y_pred),
|
2865
|
-
"mape": mean_absolute_percentage_error(y_true, y_pred),
|
2866
|
-
"explained_variance": explained_variance_score(y_true, y_pred),
|
2867
|
-
"mbd": np.mean(y_pred - y_true), # Mean Bias Deviation
|
2868
|
-
}
|
2869
|
-
# Check if MSLE can be calculated
|
2870
|
-
if np.all(y_true >= 0) and np.all(y_pred >= 0): # Ensure no negative values
|
2871
|
-
validation_scores["msle"] = mean_squared_log_error(y_true, y_pred)
|
2872
|
-
else:
|
2873
|
-
validation_scores["msle"] = "Cannot be calculated due to negative values"
|
2874
|
-
|
2875
|
-
elif purpose == "classification":
|
2876
|
-
# Classification metrics
|
2877
|
-
validation_scores = {
|
2878
|
-
"accuracy": accuracy_score(y_true, y_pred),
|
2879
|
-
"precision": precision_score(y_true, y_pred, average=average),
|
2880
|
-
"recall": recall_score(y_true, y_pred, average=average),
|
2881
|
-
"f1": f1_score(y_true, y_pred, average=average),
|
2882
|
-
"mcc": matthews_corrcoef(y_true, y_pred),
|
2883
|
-
"specificity": None,
|
2884
|
-
"balanced_accuracy": balanced_accuracy_score(y_true, y_pred),
|
2885
|
-
}
|
2886
|
-
|
2887
|
-
# Confusion matrix to calculate specificity
|
2888
|
-
if is_binary:
|
2889
|
-
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
|
2890
|
-
else:
|
2891
|
-
cm=onfusion_matrix(y_true, y_pred)
|
2892
|
-
validation_scores["specificity"] = (
|
2893
|
-
tn / (tn + fp) if (tn + fp) > 0 else 0
|
2894
|
-
) # Specificity calculation
|
2895
|
-
|
2896
|
-
if y_pred_proba is not None:
|
2897
|
-
# Calculate ROC-AUC
|
2898
|
-
validation_scores["roc_auc"] = roc_auc_score(y_true, y_pred_proba)
|
2899
|
-
# PR-AUC (Precision-Recall AUC) calculation
|
2900
|
-
validation_scores["pr_auc"] = average_precision_score(y_true, y_pred_proba)
|
2901
|
-
else:
|
2902
|
-
raise ValueError(
|
2903
|
-
"Invalid purpose specified. Choose 'regression' or 'classification'."
|
2904
|
-
)
|
2905
|
-
|
2906
|
-
return validation_scores
|