classifier-toolkit 0.1.0__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.
- classifier_toolkit/eda/__init__.py +19 -0
- classifier_toolkit/eda/bivariate_analysis.py +413 -0
- classifier_toolkit/eda/eda_toolkit.py +549 -0
- classifier_toolkit/eda/feature_engineering.py +1310 -0
- classifier_toolkit/eda/first_glance.py +253 -0
- classifier_toolkit/eda/univariate_analysis.py +778 -0
- classifier_toolkit/eda/visualizations.py +1248 -0
- classifier_toolkit/eda/warnings/__init__.py +4 -0
- classifier_toolkit/eda/warnings/automated_warnings.py +20 -0
- classifier_toolkit/eda/warnings/default_warnings.py +286 -0
- classifier_toolkit/feature_selection/__init__.py +33 -0
- classifier_toolkit/feature_selection/base.py +182 -0
- classifier_toolkit/feature_selection/embedded_methods/__init__.py +7 -0
- classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +178 -0
- classifier_toolkit/feature_selection/meta_selector.py +329 -0
- classifier_toolkit/feature_selection/utils/__init__.py +17 -0
- classifier_toolkit/feature_selection/utils/plottings.py +65 -0
- classifier_toolkit/feature_selection/utils/scoring.py +86 -0
- classifier_toolkit/feature_selection/wrapper_methods/__init__.py +11 -0
- classifier_toolkit/feature_selection/wrapper_methods/rfe.py +345 -0
- classifier_toolkit/feature_selection/wrapper_methods/sequential_selection.py +566 -0
- classifier_toolkit/model_fitting/__init__.py +0 -0
- classifier_toolkit/model_fitting/model_search.py +3 -0
- classifier_toolkit-0.1.0.dist-info/METADATA +99 -0
- classifier_toolkit-0.1.0.dist-info/RECORD +26 -0
- classifier_toolkit-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
from typing import List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from catboost import (
|
|
5
|
+
CatBoostClassifier,
|
|
6
|
+
EFeaturesSelectionAlgorithm,
|
|
7
|
+
EShapCalcType,
|
|
8
|
+
Pool,
|
|
9
|
+
)
|
|
10
|
+
from lightgbm import LGBMClassifier
|
|
11
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
12
|
+
from sklearn.feature_selection import RFECV
|
|
13
|
+
from xgboost import XGBClassifier
|
|
14
|
+
|
|
15
|
+
from classifier_toolkit.feature_selection.base import (
|
|
16
|
+
BaseFeatureSelector,
|
|
17
|
+
FeatureSelectionError,
|
|
18
|
+
)
|
|
19
|
+
from classifier_toolkit.feature_selection.utils.plottings import plot_rfecv_results
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EnsembleFeatureSelector(BaseFeatureSelector):
|
|
23
|
+
"""
|
|
24
|
+
A feature selector that uses ensemble methods for feature selection.
|
|
25
|
+
|
|
26
|
+
This class implements feature selection using various ensemble methods such as
|
|
27
|
+
LightGBM, XGBoost, Random Forest, and CatBoost. It supports both Recursive Feature
|
|
28
|
+
Elimination (RFE) and CatBoost's built-in feature selection.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
estimator : {"xgboost", "random_forest", "lightgbm", "catboost"}, default="lightgbm"
|
|
33
|
+
The estimator to use for feature selection.
|
|
34
|
+
n_features_to_select : int, optional
|
|
35
|
+
The number of features to select. If None, half of the features are selected.
|
|
36
|
+
scoring : {"accuracy", "f1", "precision", "recall", "roc_auc"}, default="roc_auc"
|
|
37
|
+
The scoring metric to use for feature selection.
|
|
38
|
+
cv : int, default=5
|
|
39
|
+
The number of cross-validation folds.
|
|
40
|
+
verbose : int, default=0
|
|
41
|
+
Controls the verbosity of the feature selection process.
|
|
42
|
+
method : str, default="rfe"
|
|
43
|
+
The feature selection method to use. Either "rfe" or "catboost".
|
|
44
|
+
step : int, default=1
|
|
45
|
+
The number of features to remove at each iteration for RFE.
|
|
46
|
+
n_jobs : int, default=-1
|
|
47
|
+
The number of jobs to run in parallel. -1 means using all processors.
|
|
48
|
+
catboost_params : dict, optional
|
|
49
|
+
Additional parameters for CatBoost.
|
|
50
|
+
min_features_to_select : int, default=1
|
|
51
|
+
The minimum number of features to select.
|
|
52
|
+
|
|
53
|
+
Attributes
|
|
54
|
+
----------
|
|
55
|
+
estimator_name : str
|
|
56
|
+
The name of the estimator used.
|
|
57
|
+
estimator : object
|
|
58
|
+
The estimator object.
|
|
59
|
+
selected_features_ : list
|
|
60
|
+
The list of selected feature names after fitting.
|
|
61
|
+
feature_importances_ : pd.Series
|
|
62
|
+
The importance scores for each feature after fitting.
|
|
63
|
+
rfecv : RFECV
|
|
64
|
+
The RFECV object if RFE method is used.
|
|
65
|
+
|
|
66
|
+
Methods
|
|
67
|
+
-------
|
|
68
|
+
fit(X, y)
|
|
69
|
+
Fit the feature selector to the data.
|
|
70
|
+
transform(X)
|
|
71
|
+
Transform the data to include only selected features.
|
|
72
|
+
get_feature_importances()
|
|
73
|
+
Get the importance scores for each feature.
|
|
74
|
+
print_feature_importances()
|
|
75
|
+
Print the feature importances in descending order.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
estimator: Literal[
|
|
81
|
+
"xgboost", "random_forest", "lightgbm", "catboost"
|
|
82
|
+
] = "lightgbm",
|
|
83
|
+
n_features_to_select: int = 1,
|
|
84
|
+
scoring: Literal[
|
|
85
|
+
"accuracy", "f1", "precision", "recall", "roc_auc"
|
|
86
|
+
] = "roc_auc",
|
|
87
|
+
cv: int = 5,
|
|
88
|
+
verbose: int = 0,
|
|
89
|
+
method: str = "rfe",
|
|
90
|
+
step: int = 1,
|
|
91
|
+
n_jobs: int = -1,
|
|
92
|
+
catboost_params: Optional[dict] = None,
|
|
93
|
+
min_features_to_select: int = 1,
|
|
94
|
+
**kwargs,
|
|
95
|
+
) -> None:
|
|
96
|
+
self.catboost_params = catboost_params or {}
|
|
97
|
+
self.estimator_name = estimator
|
|
98
|
+
self.estimator = self._get_estimator(estimator) # type: ignore
|
|
99
|
+
super().__init__(
|
|
100
|
+
estimator=self.estimator,
|
|
101
|
+
n_features_to_select=n_features_to_select,
|
|
102
|
+
scoring=scoring,
|
|
103
|
+
cv=cv,
|
|
104
|
+
verbose=verbose,
|
|
105
|
+
**kwargs,
|
|
106
|
+
)
|
|
107
|
+
self.method = method
|
|
108
|
+
self.step = step
|
|
109
|
+
self.n_jobs = n_jobs
|
|
110
|
+
self.min_features_to_select = min_features_to_select
|
|
111
|
+
|
|
112
|
+
def _get_estimator(self, estimator_name):
|
|
113
|
+
"""
|
|
114
|
+
Get the estimator based on the provided name.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
estimator_name : str
|
|
119
|
+
The name of the estimator to use.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
object
|
|
124
|
+
The initialized estimator.
|
|
125
|
+
|
|
126
|
+
Raises
|
|
127
|
+
------
|
|
128
|
+
ValueError
|
|
129
|
+
If the estimator name is not recognized.
|
|
130
|
+
"""
|
|
131
|
+
if estimator_name == "lightgbm":
|
|
132
|
+
return LGBMClassifier()
|
|
133
|
+
elif estimator_name == "xgboost":
|
|
134
|
+
return XGBClassifier()
|
|
135
|
+
elif estimator_name == "random_forest":
|
|
136
|
+
return RandomForestClassifier()
|
|
137
|
+
elif estimator_name == "catboost":
|
|
138
|
+
return CatBoostClassifier(**self.catboost_params)
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError(f"Unknown estimator: {estimator_name}")
|
|
141
|
+
|
|
142
|
+
def fit(self, X: pd.DataFrame, y: pd.Series) -> "EnsembleFeatureSelector":
|
|
143
|
+
"""
|
|
144
|
+
Fit the feature selector to the data.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
X : pd.DataFrame
|
|
149
|
+
The input features.
|
|
150
|
+
y : pd.Series
|
|
151
|
+
The target variable.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
EnsembleFeatureSelector
|
|
156
|
+
The fitted feature selector.
|
|
157
|
+
"""
|
|
158
|
+
if self.estimator_name == "catboost":
|
|
159
|
+
self._fit_catboost(X, y)
|
|
160
|
+
elif self.method == "rfe":
|
|
161
|
+
self._fit_rfe(X, y)
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
"Invalid method. Choose 'rfe' or use 'catboost' estimator."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def _fit_rfe(self, X: pd.DataFrame, y: pd.Series) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Fit the RFE method to the data.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
X : pd.DataFrame
|
|
176
|
+
The input features.
|
|
177
|
+
y : pd.Series
|
|
178
|
+
The target variable.
|
|
179
|
+
|
|
180
|
+
Raises
|
|
181
|
+
------
|
|
182
|
+
ValueError
|
|
183
|
+
If the estimator is not provided.
|
|
184
|
+
"""
|
|
185
|
+
if self.estimator is None:
|
|
186
|
+
raise ValueError("Estimator must be provided for RFE method")
|
|
187
|
+
self.rfecv = RFECV(
|
|
188
|
+
estimator=self.estimator,
|
|
189
|
+
step=self.step,
|
|
190
|
+
cv=self.cv,
|
|
191
|
+
scoring=self.scoring,
|
|
192
|
+
n_jobs=self.n_jobs,
|
|
193
|
+
verbose=self.verbose,
|
|
194
|
+
min_features_to_select=self.min_features_to_select,
|
|
195
|
+
)
|
|
196
|
+
self.rfecv.fit(X, y)
|
|
197
|
+
self.selected_features_ = X.columns[self.rfecv.support_].tolist()
|
|
198
|
+
self.feature_importances_ = pd.Series(self.rfecv.ranking_, index=X.columns)
|
|
199
|
+
|
|
200
|
+
def _fit_catboost(self, X: pd.DataFrame, y: pd.Series) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Fit the CatBoost method to the data.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
X : pd.DataFrame
|
|
207
|
+
The input features.
|
|
208
|
+
y : pd.Series
|
|
209
|
+
The target variable.
|
|
210
|
+
"""
|
|
211
|
+
model = CatBoostClassifier(**self.catboost_params)
|
|
212
|
+
|
|
213
|
+
# Create CatBoost Pool objects
|
|
214
|
+
train_pool = Pool(X, y)
|
|
215
|
+
|
|
216
|
+
# Calculate the number of features to select
|
|
217
|
+
nb_features = X.shape[1]
|
|
218
|
+
features_select = (
|
|
219
|
+
nb_features - 1
|
|
220
|
+
if self.n_features_to_select is None
|
|
221
|
+
else self.n_features_to_select
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Run feature selection
|
|
225
|
+
summary = model.select_features(
|
|
226
|
+
train_pool,
|
|
227
|
+
eval_set=None, # We're not using a separate evaluation set here
|
|
228
|
+
features_for_select=f"0-{nb_features - 1}",
|
|
229
|
+
num_features_to_select=features_select,
|
|
230
|
+
algorithm=EFeaturesSelectionAlgorithm.RecursiveByShapValues,
|
|
231
|
+
shap_calc_type=EShapCalcType.Regular,
|
|
232
|
+
train_final_model=False,
|
|
233
|
+
logging_level="Silent",
|
|
234
|
+
plot=True,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Extract selected features and their importances
|
|
238
|
+
self.selected_features_ = [X.columns[i] for i in summary["selected_features"]]
|
|
239
|
+
|
|
240
|
+
print(summary)
|
|
241
|
+
|
|
242
|
+
def get_feature_importances(self) -> pd.Series:
|
|
243
|
+
"""
|
|
244
|
+
Get the feature importances (scores) from the fitted model.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
pd.Series
|
|
249
|
+
The feature importances.
|
|
250
|
+
|
|
251
|
+
Raises
|
|
252
|
+
------
|
|
253
|
+
FeatureSelectionError
|
|
254
|
+
If the selector has not been fitted yet.
|
|
255
|
+
"""
|
|
256
|
+
if self.feature_importances_ is None:
|
|
257
|
+
raise FeatureSelectionError(
|
|
258
|
+
"Selector has not been fitted yet. Call 'fit' first."
|
|
259
|
+
)
|
|
260
|
+
return self.feature_importances_
|
|
261
|
+
|
|
262
|
+
def _get_score(self, X: pd.DataFrame, y: pd.Series, features: List[int]) -> float:
|
|
263
|
+
"""
|
|
264
|
+
Calculate the cross-validation score for a subset of features.
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
X : pd.DataFrame
|
|
269
|
+
The input features.
|
|
270
|
+
y : pd.Series
|
|
271
|
+
The target variable.
|
|
272
|
+
features : List[int]
|
|
273
|
+
The indices of features to use.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
float
|
|
278
|
+
The mean cross-validation score.
|
|
279
|
+
"""
|
|
280
|
+
X_subset = X.iloc[:, features]
|
|
281
|
+
self.estimator.fit(X_subset, y) # type: ignore
|
|
282
|
+
return self.scorer(self.estimator, X_subset, y)
|
|
283
|
+
|
|
284
|
+
def transform(self, X: pd.DataFrame) -> pd.DataFrame:
|
|
285
|
+
"""
|
|
286
|
+
Transform the input data by selecting the chosen features.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
X : pd.DataFrame
|
|
291
|
+
The input features.
|
|
292
|
+
|
|
293
|
+
Returns
|
|
294
|
+
-------
|
|
295
|
+
pd.DataFrame
|
|
296
|
+
The transformed data containing only the selected features.
|
|
297
|
+
|
|
298
|
+
Raises
|
|
299
|
+
------
|
|
300
|
+
FeatureSelectionError
|
|
301
|
+
If the selector has not been fitted yet.
|
|
302
|
+
"""
|
|
303
|
+
if self.selected_features_ is None:
|
|
304
|
+
raise FeatureSelectionError(
|
|
305
|
+
"Selector has not been fitted yet. Call 'fit' first."
|
|
306
|
+
)
|
|
307
|
+
return X[self.selected_features_]
|
|
308
|
+
|
|
309
|
+
def plot_results(self) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Plot the results of the RFECV feature selection process.
|
|
312
|
+
|
|
313
|
+
Raises
|
|
314
|
+
------
|
|
315
|
+
ValueError
|
|
316
|
+
If the RFECV results are not available.
|
|
317
|
+
"""
|
|
318
|
+
try:
|
|
319
|
+
plot_rfecv_results(self)
|
|
320
|
+
except ValueError as e:
|
|
321
|
+
print(f"Error plotting RFECV results: {e}")
|
|
322
|
+
|
|
323
|
+
def print_feature_importances(self):
|
|
324
|
+
"""
|
|
325
|
+
Print the feature importances (scores) in descending order.
|
|
326
|
+
|
|
327
|
+
Raises
|
|
328
|
+
------
|
|
329
|
+
FeatureSelectionError
|
|
330
|
+
If feature importances are not available (i.e., fit hasn't been called).
|
|
331
|
+
"""
|
|
332
|
+
if self.feature_importances_ is None:
|
|
333
|
+
raise FeatureSelectionError(
|
|
334
|
+
"Feature importances are not available. Call 'fit' first."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
sorted_importances = sorted(
|
|
338
|
+
zip(self.feature_importances_.index, self.feature_importances_),
|
|
339
|
+
key=lambda x: x[1],
|
|
340
|
+
reverse=True,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
print("Feature importances (in descending order):")
|
|
344
|
+
for feature, importance in sorted_importances:
|
|
345
|
+
print(f"('{feature}', {importance:.6f})")
|