classifier-toolkit 0.2.0__tar.gz → 0.2.2__tar.gz
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-0.2.0 → classifier_toolkit-0.2.2}/LICENSE +1 -1
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/PKG-INFO +11 -3
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/README.md +6 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/visualizations.py +7 -3
- classifier_toolkit-0.2.2/classifier_toolkit/feature_selection/__init__.py +59 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/base.py +13 -6
- classifier_toolkit-0.2.2/classifier_toolkit/feature_selection/feature_stability.py +246 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/meta_selector.py +24 -1
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/utils/data_handling.py +59 -16
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/wrapper_methods/__init__.py +10 -2
- classifier_toolkit-0.2.2/classifier_toolkit/feature_selection/wrapper_methods/bayesian_search.py +265 -0
- classifier_toolkit-0.2.2/classifier_toolkit/feature_selection/wrapper_methods/boruta.py +900 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/wrapper_methods/rfe.py +40 -12
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/__init__.py +3 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/hyper_parameter_tuning/__init__.py +0 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/hyper_parameter_tuning/tuner.py +641 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/models/__init__.py +1 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/models/base.py +113 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/models/ensemble_methods.py +211 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/utils/__init__.py +1 -0
- classifier_toolkit-0.2.2/classifier_toolkit/model_training/utils/params.py +151 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/pyproject.toml +8 -5
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/__init__.py +0 -38
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/__init__.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/bivariate_analysis.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/eda_toolkit.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/feature_engineering.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/first_glance.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/univariate_analysis.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/warnings/__init__.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/warnings/automated_warnings.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/warnings/default_warnings.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/embedded_methods/__init__.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/utils/__init__.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/utils/plottings.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/utils/scoring.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/wrapper_methods/rfe_catboost.py +0 -0
- {classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/wrapper_methods/sequential_selection.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: classifier-toolkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary:
|
|
5
|
-
Author:
|
|
6
|
-
Author-email:
|
|
5
|
+
Author: senih.yilmaz
|
|
6
|
+
Author-email: senih.yilmaz@qonto.com
|
|
7
7
|
Requires-Python: >=3.9,<3.13
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -19,14 +19,22 @@ Requires-Dist: numpy (<2.0)
|
|
|
19
19
|
Requires-Dist: optuna (>=3.5.0,<4.0.0)
|
|
20
20
|
Requires-Dist: pandas (>=2.2.0,<3.0.0)
|
|
21
21
|
Requires-Dist: polars (>=1.2.1,<2.0.0)
|
|
22
|
+
Requires-Dist: pyarrow (>=18.0.0,<19.0.0)
|
|
22
23
|
Requires-Dist: scikit-learn (>=1.4.0,<2.0.0)
|
|
23
24
|
Requires-Dist: scipy (<1.14)
|
|
24
25
|
Requires-Dist: shap (>=0.44.1,<0.45.0)
|
|
25
26
|
Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
|
|
26
27
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
28
|
+
Requires-Dist: tqdm (>=4.66.0,<5.0.0)
|
|
27
29
|
Requires-Dist: xgboost (>=2.1.1,<3.0.0)
|
|
28
30
|
Description-Content-Type: text/markdown
|
|
29
31
|
|
|
32
|
+
[PyPI: classifier-toolkit](https://pypi.org/project/classifier-toolkit/)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install classifier-toolkit
|
|
36
|
+
```
|
|
37
|
+
|
|
30
38
|
# Classifier Toolkit
|
|
31
39
|
|
|
32
40
|
[](https://python-poetry.org/)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
[PyPI: classifier-toolkit](https://pypi.org/project/classifier-toolkit/)
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
pip install classifier-toolkit
|
|
5
|
+
```
|
|
6
|
+
|
|
1
7
|
# Classifier Toolkit
|
|
2
8
|
|
|
3
9
|
[](https://python-poetry.org/)
|
{classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/eda/visualizations.py
RENAMED
|
@@ -1260,11 +1260,15 @@ class Visualizations:
|
|
|
1260
1260
|
|
|
1261
1261
|
def plot_boxplots(self):
|
|
1262
1262
|
"""
|
|
1263
|
-
Plot boxplots for numerical columns by target variable.
|
|
1263
|
+
Plot boxplots for numerical columns by target variable with one random column initially visible.
|
|
1264
1264
|
"""
|
|
1265
|
-
|
|
1266
1265
|
fig = make_subplots(rows=1, cols=1)
|
|
1267
1266
|
|
|
1267
|
+
# Randomly select one column to be initially visible
|
|
1268
|
+
import random
|
|
1269
|
+
|
|
1270
|
+
initial_visible_column = random.choice(self.numerical_columns)
|
|
1271
|
+
|
|
1268
1272
|
for numeric in self.numerical_columns:
|
|
1269
1273
|
fig.add_trace(
|
|
1270
1274
|
go.Box(
|
|
@@ -1272,7 +1276,7 @@ class Visualizations:
|
|
|
1272
1276
|
x=self.data[self.target_column],
|
|
1273
1277
|
name=numeric,
|
|
1274
1278
|
boxmean=True,
|
|
1275
|
-
visible="legendonly",
|
|
1279
|
+
visible=True if numeric == initial_visible_column else "legendonly",
|
|
1276
1280
|
)
|
|
1277
1281
|
)
|
|
1278
1282
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Feature selection public API with lazy imports to avoid heavy dependencies at import time.
|
|
2
|
+
|
|
3
|
+
This module exposes a stable public API while delaying imports of optional heavy
|
|
4
|
+
backends (e.g., LightGBM, XGBoost, CatBoost, Matplotlib) until the corresponding
|
|
5
|
+
symbols are actually accessed. This prevents import-time failures in
|
|
6
|
+
environments where optional system libraries are not present and speeds up
|
|
7
|
+
package import for utilities.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib import import_module
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
_EXPORTS: Dict[str, str] = {
|
|
14
|
+
# Core/base and meta
|
|
15
|
+
"BaseFeatureSelector": "classifier_toolkit.feature_selection.base",
|
|
16
|
+
"MetaSelector": "classifier_toolkit.feature_selection.meta_selector",
|
|
17
|
+
"FeatureStability": "classifier_toolkit.feature_selection.feature_stability",
|
|
18
|
+
# Embedded methods
|
|
19
|
+
"ElasticNetLogisticSelector": "classifier_toolkit.feature_selection.embedded_methods.elastic_net",
|
|
20
|
+
# Utils
|
|
21
|
+
"plot_feature_importances": "classifier_toolkit.feature_selection.utils.plottings",
|
|
22
|
+
"plot_rfecv_results": "classifier_toolkit.feature_selection.utils.plottings",
|
|
23
|
+
"get_scorer": "classifier_toolkit.feature_selection.utils.scoring",
|
|
24
|
+
"false_positive_rate": "classifier_toolkit.feature_selection.utils.scoring",
|
|
25
|
+
"true_positive_rate": "classifier_toolkit.feature_selection.utils.scoring",
|
|
26
|
+
# Wrapper methods
|
|
27
|
+
"BayesianFeatureSelector": "classifier_toolkit.feature_selection.wrapper_methods.bayesian_search",
|
|
28
|
+
"BorutaSelector": "classifier_toolkit.feature_selection.wrapper_methods.boruta",
|
|
29
|
+
"RFESelector": "classifier_toolkit.feature_selection.wrapper_methods.rfe",
|
|
30
|
+
"RFECatBoostSelector": "classifier_toolkit.feature_selection.wrapper_methods.rfe_catboost",
|
|
31
|
+
"SequentialSelector": "classifier_toolkit.feature_selection.wrapper_methods.sequential_selection",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
__all__ = list(_EXPORTS.keys())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __getattr__(name: str) -> Any: # pragma: no cover - simple lazy loader
|
|
38
|
+
"""Dynamically import and return the requested symbol on first access.
|
|
39
|
+
|
|
40
|
+
This keeps the package import light and avoids importing optional
|
|
41
|
+
dependencies unless they are actually needed by the user.
|
|
42
|
+
"""
|
|
43
|
+
module_path = _EXPORTS.get(name)
|
|
44
|
+
if module_path is None:
|
|
45
|
+
raise AttributeError(
|
|
46
|
+
f"module 'classifier_toolkit.feature_selection' has no attribute '{name}'"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
module = import_module(module_path)
|
|
50
|
+
try:
|
|
51
|
+
return getattr(module, name)
|
|
52
|
+
except AttributeError as exc: # Provide a clearer error if symbol moved/renamed
|
|
53
|
+
raise AttributeError(
|
|
54
|
+
f"'{name}' not found in '{module_path}'. The public API mapping may be outdated."
|
|
55
|
+
) from exc
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def __dir__(): # pragma: no cover - trivial
|
|
59
|
+
return sorted(list(globals().keys()) + __all__)
|
{classifier_toolkit-0.2.0 → classifier_toolkit-0.2.2}/classifier_toolkit/feature_selection/base.py
RENAMED
|
@@ -271,13 +271,20 @@ class BaseFeatureSelector(BaseEstimator, TransformerMixin, ABC):
|
|
|
271
271
|
"""
|
|
272
272
|
Fit the selector using RFECV.
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
X : pd.DataFrame
|
|
277
|
+
The input features.
|
|
278
|
+
y : pd.Series
|
|
279
|
+
The target variable.
|
|
280
|
+
rfecv_params : dict, optional
|
|
281
|
+
Additional parameters to pass to RFECV.
|
|
282
|
+
See :class:`sklearn.feature_selection.RFECV` for details.
|
|
278
283
|
|
|
279
|
-
Returns
|
|
280
|
-
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
self : BaseFeatureSelector
|
|
287
|
+
The fitted selector.
|
|
281
288
|
"""
|
|
282
289
|
if self.estimator is None:
|
|
283
290
|
raise ValueError("Estimator is not set. Please provide an estimator.")
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from tqdm import tqdm
|
|
7
|
+
|
|
8
|
+
from classifier_toolkit.feature_selection.utils.data_handling import (
|
|
9
|
+
prepare_data_for_modeling,
|
|
10
|
+
)
|
|
11
|
+
from classifier_toolkit.feature_selection.wrapper_methods.rfe import RFESelector
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FeatureStability:
|
|
15
|
+
"""
|
|
16
|
+
A class for assessing feature stability through various resampling methods.
|
|
17
|
+
|
|
18
|
+
This class implements feature stability assessment using different resampling techniques
|
|
19
|
+
like jackknife, and evaluates feature importance across different subsets of data.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
X : pd.DataFrame
|
|
24
|
+
The feature matrix.
|
|
25
|
+
y : pd.Series
|
|
26
|
+
The target variable.
|
|
27
|
+
target_col : str
|
|
28
|
+
Name of the target column.
|
|
29
|
+
model_params : dict
|
|
30
|
+
Parameters for the model to be used.
|
|
31
|
+
method_params : dict
|
|
32
|
+
Parameters for the resampling method.
|
|
33
|
+
model : {'xgboost', 'lightgbm', 'catboost', 'random_forest'}, optional
|
|
34
|
+
The model to use for feature selection, by default 'xgboost'.
|
|
35
|
+
method : str, optional
|
|
36
|
+
The resampling method to use, by default 'jackknife'.
|
|
37
|
+
seed : int, optional
|
|
38
|
+
Random seed for reproducibility, by default 42.
|
|
39
|
+
scoring : {'accuracy', 'f1', 'precision', 'recall', 'roc_auc', 'average_precision'}, optional
|
|
40
|
+
Scoring metric to use, by default 'average_precision'.
|
|
41
|
+
cutoff_ratio : float, optional
|
|
42
|
+
Ratio of features to select in each iteration, by default 0.33.
|
|
43
|
+
|
|
44
|
+
Attributes
|
|
45
|
+
----------
|
|
46
|
+
importances : list
|
|
47
|
+
List of feature importance DataFrames from each iteration.
|
|
48
|
+
models : list
|
|
49
|
+
List of fitted RFE models from each iteration.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
X: pd.DataFrame,
|
|
55
|
+
y: pd.Series,
|
|
56
|
+
target_col: str,
|
|
57
|
+
model_params: dict,
|
|
58
|
+
method_params: dict,
|
|
59
|
+
model: Literal["xgboost", "lightgbm", "catboost", "random_forest"] = "xgboost",
|
|
60
|
+
method: str = "jackknife",
|
|
61
|
+
seed: int = 42,
|
|
62
|
+
scoring: Literal[
|
|
63
|
+
"accuracy", "f1", "precision", "recall", "roc_auc", "average_precision"
|
|
64
|
+
] = "average_precision",
|
|
65
|
+
cutoff_ratio: float = 0.33,
|
|
66
|
+
):
|
|
67
|
+
self.X = X
|
|
68
|
+
self.y = y
|
|
69
|
+
self.model = model
|
|
70
|
+
self.target_col = target_col
|
|
71
|
+
self.model_params = model_params
|
|
72
|
+
self.method = method
|
|
73
|
+
self.method_params = method_params
|
|
74
|
+
self.seed = seed
|
|
75
|
+
self.scoring = scoring
|
|
76
|
+
self.cutoff_ratio = cutoff_ratio
|
|
77
|
+
|
|
78
|
+
def jackknife(self, **kwargs):
|
|
79
|
+
"""
|
|
80
|
+
Implementation of the Delete-d Jackknife method for feature selection.
|
|
81
|
+
|
|
82
|
+
This method creates n_sets from the original dataset by removing d random samples.
|
|
83
|
+
Theoretically, it can create d chooses n_sets different datasets.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
**kwargs : dict
|
|
88
|
+
Must include:
|
|
89
|
+
d : int
|
|
90
|
+
Number of samples to be removed
|
|
91
|
+
n_sets : int
|
|
92
|
+
Number of sets to be created
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
dict
|
|
97
|
+
Dictionary of boolean masks for each jackknife sample
|
|
98
|
+
|
|
99
|
+
Raises
|
|
100
|
+
------
|
|
101
|
+
ValueError
|
|
102
|
+
If d or n_sets are not provided
|
|
103
|
+
If d is larger than the number of samples
|
|
104
|
+
If n_sets is larger than possible combinations
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
d = kwargs.get("d")
|
|
108
|
+
n_sets = kwargs.get("n_sets")
|
|
109
|
+
|
|
110
|
+
if d is None or n_sets is None:
|
|
111
|
+
raise ValueError("d and n_sets should be provided.")
|
|
112
|
+
|
|
113
|
+
indices = {}
|
|
114
|
+
|
|
115
|
+
if d > len(self.X):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
"d should be smaller than number of samples in the dataframe."
|
|
118
|
+
)
|
|
119
|
+
elif n_sets > math.comb(len(self.X), d):
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"n_sets should be smaller than d chooses n_sets, which is math.comb(len(X), d)."
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
random.seed(self.seed)
|
|
125
|
+
theta = len(self.X) - d
|
|
126
|
+
# Use range(len(X)) instead of X.index
|
|
127
|
+
positions = list(range(len(self.X) - 1))
|
|
128
|
+
|
|
129
|
+
for i in range(n_sets):
|
|
130
|
+
# Sample positions instead of index values
|
|
131
|
+
sampled_positions = random.sample(positions, theta)
|
|
132
|
+
# Convert positions to boolean mask
|
|
133
|
+
mask = pd.Series(False, index=range(len(self.X)))
|
|
134
|
+
mask[sampled_positions] = True
|
|
135
|
+
indices[i] = mask
|
|
136
|
+
|
|
137
|
+
return indices
|
|
138
|
+
|
|
139
|
+
def fit(self):
|
|
140
|
+
"""
|
|
141
|
+
Fit the feature stability model using the specified resampling method.
|
|
142
|
+
|
|
143
|
+
This method performs feature selection on multiple subsets of the data
|
|
144
|
+
to assess feature stability. For each subset:
|
|
145
|
+
|
|
146
|
+
1. Creates a mask using the specified resampling method
|
|
147
|
+
2. Selects data using the mask
|
|
148
|
+
3. Prepares the data for modeling
|
|
149
|
+
4. Fits an RFE selector
|
|
150
|
+
5. Stores feature importances and the fitted model
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
FeatureStability
|
|
155
|
+
The fitted FeatureStability instance
|
|
156
|
+
|
|
157
|
+
Raises
|
|
158
|
+
------
|
|
159
|
+
ValueError
|
|
160
|
+
If the specified resampling method is not implemented
|
|
161
|
+
"""
|
|
162
|
+
self.importances = []
|
|
163
|
+
self.models = []
|
|
164
|
+
|
|
165
|
+
if self.method == "jackknife":
|
|
166
|
+
indices = self.jackknife(**self.method_params)
|
|
167
|
+
else:
|
|
168
|
+
raise ValueError("Method not implemented.")
|
|
169
|
+
|
|
170
|
+
for i in tqdm(range(len(indices)), desc="Processing indices", unit="index"):
|
|
171
|
+
rfe_selector = RFESelector(
|
|
172
|
+
estimator_name=self.model, # type: ignore
|
|
173
|
+
n_features_to_select=-1,
|
|
174
|
+
scoring=self.scoring, # type: ignore
|
|
175
|
+
cv=5,
|
|
176
|
+
verbose=1,
|
|
177
|
+
model_params=self.model_params,
|
|
178
|
+
random_state=self.seed,
|
|
179
|
+
cross_validate_prauc=True,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
print("Preparing the data for modeling...")
|
|
183
|
+
|
|
184
|
+
# Use boolean mask to select rows
|
|
185
|
+
mask = indices[i]
|
|
186
|
+
current_X = self.X[mask]
|
|
187
|
+
current_y = self.y[mask]
|
|
188
|
+
|
|
189
|
+
current_X = current_X.reset_index(drop=True)
|
|
190
|
+
current_y = current_y.reset_index(drop=True)
|
|
191
|
+
|
|
192
|
+
X_encoded, y_encoded, cv_folds = prepare_data_for_modeling(
|
|
193
|
+
current_X,
|
|
194
|
+
current_y,
|
|
195
|
+
y_name=self.target_col,
|
|
196
|
+
n_splits=5,
|
|
197
|
+
test_size=0.2,
|
|
198
|
+
random_state=self.seed,
|
|
199
|
+
encoding_method="distribution",
|
|
200
|
+
verbose=False,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
print("Fitting the model...")
|
|
204
|
+
rfe_selector.fit(X_encoded, y_encoded, cv_folds)
|
|
205
|
+
|
|
206
|
+
feature_importances = rfe_selector.get_feature_importances()
|
|
207
|
+
|
|
208
|
+
self.importances.append(feature_importances)
|
|
209
|
+
|
|
210
|
+
self.models.append(rfe_selector)
|
|
211
|
+
|
|
212
|
+
def calculate_stability(self):
|
|
213
|
+
"""
|
|
214
|
+
Calculate the stability scores for features across all iterations.
|
|
215
|
+
|
|
216
|
+
This method analyzes how consistently features are selected across different
|
|
217
|
+
subsets of the data. It:
|
|
218
|
+
|
|
219
|
+
1. Determines the cutoff point based on cutoff_ratio
|
|
220
|
+
2. Identifies selected features in each iteration
|
|
221
|
+
3. Calculates how many times each feature was selected
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
pd.DataFrame
|
|
226
|
+
DataFrame containing feature names and their selection frequency,
|
|
227
|
+
sorted by frequency in descending order
|
|
228
|
+
"""
|
|
229
|
+
cutoff = int(len(self.importances[0]) * self.cutoff_ratio)
|
|
230
|
+
|
|
231
|
+
selected = {}
|
|
232
|
+
|
|
233
|
+
for i in range(len(self.importances)):
|
|
234
|
+
selected[i] = list(self.importances[i][:cutoff].index)
|
|
235
|
+
|
|
236
|
+
selected_frequency = dict.fromkeys(list(self.X.columns), 0)
|
|
237
|
+
|
|
238
|
+
for i in range(len(selected)):
|
|
239
|
+
for col in selected[i]:
|
|
240
|
+
selected_frequency[col] += 1
|
|
241
|
+
|
|
242
|
+
df = pd.DataFrame(
|
|
243
|
+
selected_frequency.items(), columns=["Feature Name", "Times Selected"]
|
|
244
|
+
)
|
|
245
|
+
df = df.sort_values(by="Times Selected", ascending=False)
|
|
246
|
+
return df
|
|
@@ -2,7 +2,30 @@ from typing import Dict, List, Literal, Optional, Tuple
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
# Optional progress bar dependency. Fall back to a no-op if tqdm is unavailable.
|
|
7
|
+
try:
|
|
8
|
+
from tqdm import tqdm # type: ignore
|
|
9
|
+
except Exception: # pragma: no cover - fallback only used in limited envs
|
|
10
|
+
|
|
11
|
+
class tqdm: # type: ignore
|
|
12
|
+
def __init__(self, total=None, desc=None):
|
|
13
|
+
"""No-op progress bar placeholder."""
|
|
14
|
+
self.total = total
|
|
15
|
+
self.desc = desc
|
|
16
|
+
|
|
17
|
+
def update(self, _):
|
|
18
|
+
"""No-op update."""
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
def set_description(self, *_args, **_kwargs) -> None:
|
|
22
|
+
"""No-op description setter."""
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
def close(self):
|
|
26
|
+
"""No-op close."""
|
|
27
|
+
return None
|
|
28
|
+
|
|
6
29
|
|
|
7
30
|
from classifier_toolkit.feature_selection.base import (
|
|
8
31
|
BaseFeatureSelector,
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
# TODO: Implement the prequential validation shit
|
|
2
|
-
# Allow users to select their train and test populations
|
|
3
|
-
|
|
4
1
|
from typing import Dict, List, Literal, Optional, Tuple
|
|
5
2
|
|
|
6
3
|
import numpy as np
|
|
@@ -167,19 +164,30 @@ def prepare_data_for_modeling(
|
|
|
167
164
|
categorical_features: Optional[List[str]] = None,
|
|
168
165
|
verbose: bool = False,
|
|
169
166
|
) -> Tuple[pd.DataFrame, pd.Series, Dict]:
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
167
|
+
"""Prepare data for modeling by creating cross-validation folds and encoding datasets.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
X : pd.DataFrame
|
|
172
|
+
The feature dataframe.
|
|
173
|
+
y : pd.Series
|
|
174
|
+
The target series.
|
|
175
|
+
n_splits : int
|
|
176
|
+
Number of cross-validation splits.
|
|
177
|
+
test_size : float
|
|
178
|
+
Proportion of data to use for testing.
|
|
179
|
+
random_state : int
|
|
180
|
+
Random state for reproducibility.
|
|
181
|
+
encoding_method : str
|
|
182
|
+
Method to use for encoding categorical variables.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
tuple
|
|
187
|
+
A tuple containing:
|
|
188
|
+
- pd.DataFrame : Encoded X
|
|
189
|
+
- pd.Series : Encoded y
|
|
190
|
+
- dict : Encoded cv_folds
|
|
183
191
|
"""
|
|
184
192
|
cv_folds = create_cross_validation_folds(X, y, n_splits, test_size, random_state)
|
|
185
193
|
|
|
@@ -242,3 +250,38 @@ def create_catboost_pools(
|
|
|
242
250
|
"eval_pool": eval_pool,
|
|
243
251
|
"test_pool": test_pool,
|
|
244
252
|
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def basic_encoder(
|
|
256
|
+
X_train: pd.DataFrame,
|
|
257
|
+
X_test: pd.DataFrame,
|
|
258
|
+
y_train: pd.Series,
|
|
259
|
+
y_test: pd.Series,
|
|
260
|
+
target_col: str,
|
|
261
|
+
features_num: list,
|
|
262
|
+
features_cat: list,
|
|
263
|
+
encoding_method: Literal[
|
|
264
|
+
"one_hot", "distribution", "woe", "catboost"
|
|
265
|
+
] = "distribution",
|
|
266
|
+
verbose: bool = False,
|
|
267
|
+
):
|
|
268
|
+
encoded_train = X_train.copy()
|
|
269
|
+
encoded_train[target_col] = y_train
|
|
270
|
+
|
|
271
|
+
encoded_test = X_test.copy()
|
|
272
|
+
encoded_test[target_col] = y_test
|
|
273
|
+
|
|
274
|
+
fe = FeatureEngineering(encoded_train, target_col, features_num, features_cat)
|
|
275
|
+
|
|
276
|
+
# Encode train data
|
|
277
|
+
encoded_train = fe.encoding_categorical(encoding_method, fit=True, verbose=verbose)
|
|
278
|
+
encoded_train[features_cat] = encoded_train[features_cat]
|
|
279
|
+
|
|
280
|
+
# Encode test data
|
|
281
|
+
encoded_test = fe.encoding_categorical(
|
|
282
|
+
encoding_method, data=encoded_test, fit=False, verbose=verbose
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
encoded_test[features_cat] = encoded_test[features_cat]
|
|
286
|
+
|
|
287
|
+
return encoded_train, encoded_test
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
from classifier_toolkit.feature_selection.wrapper_methods.bayesian_search import (
|
|
2
|
+
BayesianFeatureSelector,
|
|
3
|
+
)
|
|
4
|
+
from classifier_toolkit.feature_selection.wrapper_methods.boruta import (
|
|
5
|
+
BorutaSelector,
|
|
6
|
+
)
|
|
1
7
|
from classifier_toolkit.feature_selection.wrapper_methods.rfe import (
|
|
2
8
|
RFESelector,
|
|
3
9
|
)
|
|
4
10
|
from classifier_toolkit.feature_selection.wrapper_methods.rfe_catboost import (
|
|
5
|
-
|
|
11
|
+
RFECatBoostSelector,
|
|
6
12
|
)
|
|
7
13
|
from classifier_toolkit.feature_selection.wrapper_methods.sequential_selection import (
|
|
8
14
|
SequentialSelector,
|
|
9
15
|
)
|
|
10
16
|
|
|
11
17
|
__all__ = [
|
|
18
|
+
"BayesianFeatureSelector",
|
|
19
|
+
"BorutaSelector",
|
|
12
20
|
"RFESelector",
|
|
13
|
-
"
|
|
21
|
+
"RFECatBoostSelector",
|
|
14
22
|
"SequentialSelector",
|
|
15
23
|
]
|