classifier-toolkit 0.1.3__tar.gz → 0.2.0__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/LICENSE +21 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/PKG-INFO +1 -1
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/eda_toolkit.py +62 -66
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/feature_engineering.py +182 -64
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/first_glance.py +1 -1
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/univariate_analysis.py +176 -238
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/visualizations.py +135 -2
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/__init__.py +9 -4
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/base.py +128 -7
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/embedded_methods/__init__.py +2 -2
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +297 -0
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/meta_selector.py +323 -0
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/utils/data_handling.py +244 -0
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/utils/plottings.py +209 -0
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/utils/scoring.py +121 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/wrapper_methods/__init__.py +6 -2
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/wrapper_methods/rfe.py +433 -0
- classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/wrapper_methods/rfe_catboost.py +337 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/wrapper_methods/sequential_selection.py +179 -51
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/pyproject.toml +13 -1
- classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +0 -178
- classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/meta_selector.py +0 -329
- classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/utils/plottings.py +0 -65
- classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/utils/scoring.py +0 -86
- classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/wrapper_methods/rfe.py +0 -345
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/README.md +0 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/__init__.py +0 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/bivariate_analysis.py +0 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/warnings/__init__.py +0 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/warnings/automated_warnings.py +0 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/warnings/default_warnings.py +0 -0
- {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/utils/__init__.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Olinda SAS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from datetime import date, datetime
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
from typing import Any, Dict, List, Literal, Optional, Union
|
|
4
3
|
|
|
@@ -35,8 +34,8 @@ class EDAToolkit:
|
|
|
35
34
|
"Please make sure to specify the path in the constants file."
|
|
36
35
|
)
|
|
37
36
|
else:
|
|
38
|
-
path_to_save = f"{path}/df_{fname}.
|
|
39
|
-
interm.
|
|
37
|
+
path_to_save = f"{path}/df_{fname}.parquet"
|
|
38
|
+
interm.to_parquet(path_to_save, index=False)
|
|
40
39
|
|
|
41
40
|
def __init__(
|
|
42
41
|
self,
|
|
@@ -88,6 +87,9 @@ class EDAToolkit:
|
|
|
88
87
|
# Initialize an empty list for transformations
|
|
89
88
|
self.transformations: TransformationType = []
|
|
90
89
|
|
|
90
|
+
# Create the empty transformer object for normality
|
|
91
|
+
self.normality_transformer = None
|
|
92
|
+
|
|
91
93
|
# Initialize the warning system
|
|
92
94
|
default_warning_system = get_default_warnings(
|
|
93
95
|
df=self.dataframe,
|
|
@@ -153,6 +155,17 @@ class EDAToolkit:
|
|
|
153
155
|
]
|
|
154
156
|
return self.transformations
|
|
155
157
|
|
|
158
|
+
def get_normality_transformer(self):
|
|
159
|
+
"""
|
|
160
|
+
Get the normality transformer object.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
Any
|
|
165
|
+
The normality transformer object.
|
|
166
|
+
"""
|
|
167
|
+
return self.normality_transformer
|
|
168
|
+
|
|
156
169
|
def register_custom_warning(self, name: str, warning_obj: EDAWarning):
|
|
157
170
|
"""
|
|
158
171
|
Register a custom warning function.
|
|
@@ -206,12 +219,20 @@ class EDAToolkit:
|
|
|
206
219
|
self.first_glance.super_vision()
|
|
207
220
|
self.first_glance.check_duplicates()
|
|
208
221
|
|
|
209
|
-
def plot_target_evolution(self):
|
|
222
|
+
def plot_target_evolution(self, time_column: str, evolving_numeric: str):
|
|
210
223
|
"""
|
|
211
224
|
Plot the target balance and evolution over time.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
time_column : str
|
|
229
|
+
The name of the time column.
|
|
230
|
+
evolving_numeric : str
|
|
231
|
+
The name of the numerical column to plot that we want to see the evolution of
|
|
232
|
+
which helps us understanding the behavior of the target column.
|
|
212
233
|
"""
|
|
213
234
|
self.visualizations.plot_target_balance()
|
|
214
|
-
self.visualizations.plot_target_evolution(
|
|
235
|
+
self.visualizations.plot_target_evolution(time_column, evolving_numeric)
|
|
215
236
|
|
|
216
237
|
def plot_numerical_distributions(self, sub_plots: bool = False):
|
|
217
238
|
"""
|
|
@@ -232,7 +253,7 @@ class EDAToolkit:
|
|
|
232
253
|
"""
|
|
233
254
|
self.visualizations.plot_categorical_value_counts()
|
|
234
255
|
self.visualizations.visualize_high_cardinality(
|
|
235
|
-
|
|
256
|
+
excluded_columns=self.exclusion, id_column=self.id_column
|
|
236
257
|
)
|
|
237
258
|
self.visualizations.visualize_low_cardinality()
|
|
238
259
|
|
|
@@ -272,74 +293,33 @@ class EDAToolkit:
|
|
|
272
293
|
time_column, number_of_partitions
|
|
273
294
|
)
|
|
274
295
|
|
|
275
|
-
def
|
|
296
|
+
def plot_psi(
|
|
276
297
|
self,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
start_date: Union[str, datetime, date],
|
|
283
|
-
test_data: Optional[pd.DataFrame] = None,
|
|
298
|
+
train: pd.DataFrame,
|
|
299
|
+
test: pd.DataFrame,
|
|
300
|
+
feature_cols: List[str],
|
|
301
|
+
buckets: int = 10,
|
|
302
|
+
bucket_type: Literal["quantile", "fixed_width"] = "quantile",
|
|
284
303
|
):
|
|
285
304
|
"""
|
|
286
305
|
Print Population Stability Index (PSI) for numerical features.
|
|
287
306
|
|
|
288
307
|
Parameters
|
|
289
308
|
----------
|
|
309
|
+
train : pd.DataFrame
|
|
310
|
+
Training dataset.
|
|
311
|
+
test : pd.DataFrame
|
|
312
|
+
Testing dataset.
|
|
313
|
+
feature_cols : List[str]
|
|
314
|
+
List of feature columns to analyze.
|
|
290
315
|
buckets : int
|
|
291
|
-
Number of buckets for binning.
|
|
316
|
+
Number of buckets to use for binning. Default is 10.
|
|
292
317
|
bucket_type : Literal["quantile", "fixed_width"]
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
Name of the time column.
|
|
296
|
-
time_expected : Union[str, datetime, date]
|
|
297
|
-
Expected time for PSI calculation.
|
|
298
|
-
end_date : Union[str, datetime, date]
|
|
299
|
-
End date for PSI calculation.
|
|
300
|
-
start_date : Union[str, datetime, date]
|
|
301
|
-
Start date for PSI calculation.
|
|
302
|
-
test_data : Optional[pd.DataFrame], optional
|
|
303
|
-
Test dataset for PSI calculation, by default None.
|
|
304
|
-
"""
|
|
305
|
-
print("Calculating PSI with the following parameters:")
|
|
306
|
-
print(f"Time column: {time_column}")
|
|
307
|
-
print(f"Start date: {start_date}")
|
|
308
|
-
print(f"End date: {end_date}")
|
|
309
|
-
print(f"Expected time (split point): {time_expected}")
|
|
310
|
-
print(f"Number of buckets: {buckets}")
|
|
311
|
-
print(f"Bucket type: {bucket_type}")
|
|
312
|
-
print(f"Using separate test data: {'Yes' if test_data is not None else 'No'}")
|
|
313
|
-
print("=" * 60)
|
|
314
|
-
|
|
315
|
-
# Print data info before PSI calculation
|
|
316
|
-
print(f"Data shape before PSI calculation: {self.dataframe.shape}")
|
|
317
|
-
print(
|
|
318
|
-
f"Date range in data: {self.dataframe[time_column].min()} to {self.dataframe[time_column].max()}"
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
if test_data is not None:
|
|
322
|
-
print(f"Test data shape: {test_data.shape}")
|
|
323
|
-
print(
|
|
324
|
-
f"Date range in test data: {test_data[time_column].min()} to {test_data[time_column].max()}"
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
self.univariate_analysis.compute_psi(
|
|
328
|
-
time_column,
|
|
329
|
-
time_expected,
|
|
330
|
-
start_date,
|
|
331
|
-
end_date,
|
|
332
|
-
test_data,
|
|
333
|
-
buckets,
|
|
334
|
-
bucket_type,
|
|
335
|
-
)
|
|
318
|
+
Type of bucketing to use. Default is "quantile".
|
|
319
|
+
"""
|
|
336
320
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
f"Data shape after PSI calculation: {self.univariate_analysis.data.shape}"
|
|
340
|
-
)
|
|
341
|
-
print(
|
|
342
|
-
f"Date range in filtered data: {self.univariate_analysis.data[time_column].min()} to {self.univariate_analysis.data[time_column].max()}"
|
|
321
|
+
self.univariate_analysis.visualize_psi_for_features(
|
|
322
|
+
train, test, feature_cols, buckets, bucket_type
|
|
343
323
|
)
|
|
344
324
|
|
|
345
325
|
def plot_num_cat_anova(self):
|
|
@@ -377,7 +357,7 @@ class EDAToolkit:
|
|
|
377
357
|
"""
|
|
378
358
|
_, filtered_cramers = self.univariate_analysis.plot_cramers_v(
|
|
379
359
|
self.numerical_columns,
|
|
380
|
-
|
|
360
|
+
excluded_columns=self.exclusion,
|
|
381
361
|
filter_threshold=threshold_c,
|
|
382
362
|
)
|
|
383
363
|
|
|
@@ -417,7 +397,8 @@ class EDAToolkit:
|
|
|
417
397
|
transformations=transforms,
|
|
418
398
|
)
|
|
419
399
|
|
|
420
|
-
self.feature_engineering.diagnose_normality_transform()
|
|
400
|
+
_, transformer = self.feature_engineering.diagnose_normality_transform()
|
|
401
|
+
self.normality_transformer = transformer
|
|
421
402
|
|
|
422
403
|
def normalize_numericals(self, to_transformed):
|
|
423
404
|
"""
|
|
@@ -513,6 +494,21 @@ class EDAToolkit:
|
|
|
513
494
|
"""
|
|
514
495
|
self.visualizations.plot_numerical_target_frequency_line(scale=scaling)
|
|
515
496
|
|
|
497
|
+
def plot_boxplots(self):
|
|
498
|
+
"""
|
|
499
|
+
Plot boxplots for numerical columns.
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
num_rows : int
|
|
504
|
+
Number of rows to display in the output.
|
|
505
|
+
scale : bool
|
|
506
|
+
If True, apply scaling to the columns before plotting.
|
|
507
|
+
log_scale : bool
|
|
508
|
+
If True, apply log scaling to the columns before plotting.
|
|
509
|
+
"""
|
|
510
|
+
self.visualizations.plot_boxplots()
|
|
511
|
+
|
|
516
512
|
def print_crosstab_freqs(self, num_rows):
|
|
517
513
|
"""
|
|
518
514
|
Print cross-tabulation frequencies for categorical columns.
|
{classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/feature_engineering.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import math
|
|
2
|
-
from typing import List, Literal, NamedTuple, Optional
|
|
2
|
+
from typing import List, Literal, NamedTuple, Optional, Union
|
|
3
3
|
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
5
|
import numpy as np
|
|
@@ -228,7 +228,7 @@ class FeatureEngineering:
|
|
|
228
228
|
method: Literal["power", "standard"] = "power",
|
|
229
229
|
alpha: float = 0.05,
|
|
230
230
|
test_mode: bool = False,
|
|
231
|
-
) -> dict:
|
|
231
|
+
) -> tuple[dict, Union[PowerTransformer, StandardScaler]]:
|
|
232
232
|
transformed_data = self.data.copy()
|
|
233
233
|
results = {}
|
|
234
234
|
for column in self.numerical_columns:
|
|
@@ -332,7 +332,7 @@ class FeatureEngineering:
|
|
|
332
332
|
self._log_transformation(
|
|
333
333
|
"normality_transform", {"method": method, "alpha": alpha}
|
|
334
334
|
)
|
|
335
|
-
return results
|
|
335
|
+
return results, transformer
|
|
336
336
|
|
|
337
337
|
def apply_normality_transformations(
|
|
338
338
|
self, columns_to_keep: List[str]
|
|
@@ -364,9 +364,11 @@ class FeatureEngineering:
|
|
|
364
364
|
raise ValueError(f"Column {column} not found in transformed data")
|
|
365
365
|
|
|
366
366
|
self.data = updated_data
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
|
|
368
|
+
for transform in self.transformations:
|
|
369
|
+
if transform["method"] == "normality_transform":
|
|
370
|
+
transform["selected_columns"] = columns_to_keep
|
|
371
|
+
|
|
370
372
|
return self.data
|
|
371
373
|
|
|
372
374
|
def handle_high_cardinality(
|
|
@@ -935,63 +937,156 @@ class FeatureEngineering:
|
|
|
935
937
|
def encoding_categorical(
|
|
936
938
|
self,
|
|
937
939
|
method: Literal["one_hot", "distribution", "woe", "catboost"] = "one_hot",
|
|
940
|
+
fit: bool = True,
|
|
941
|
+
data: Optional[pd.DataFrame] = None,
|
|
942
|
+
verbose: bool = False,
|
|
938
943
|
) -> pd.DataFrame:
|
|
939
944
|
"""
|
|
940
945
|
Perform encoding for the specified categorical columns using the chosen method.
|
|
941
946
|
|
|
942
|
-
The columns to be encoded are specified during the initialization of the FeatureEngineering class.
|
|
943
|
-
|
|
944
947
|
Parameters
|
|
945
948
|
----------
|
|
946
949
|
method : {'one_hot', 'distribution', 'woe', 'catboost'}, optional
|
|
947
950
|
The encoding method to use, by default 'one_hot'.
|
|
951
|
+
fit : bool, optional
|
|
952
|
+
Whether to fit the encoder (True) or just transform (False), by default True.
|
|
953
|
+
data : Optional[pd.DataFrame], optional
|
|
954
|
+
The data to encode. If None, uses self.data, by default None.
|
|
955
|
+
|
|
956
|
+
Returns
|
|
957
|
+
-------
|
|
958
|
+
pd.DataFrame
|
|
959
|
+
DataFrame with encoded columns.
|
|
948
960
|
"""
|
|
949
961
|
if self.encode_list is None:
|
|
950
962
|
print("No columns specified for encoding.")
|
|
963
|
+
return self.data if data is None else data
|
|
951
964
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
]
|
|
965
|
+
data = self.data.copy() if data is None else data.copy()
|
|
966
|
+
|
|
967
|
+
missing_columns = [col for col in self.encode_list if col not in data.columns]
|
|
955
968
|
if missing_columns:
|
|
956
969
|
raise ValueError(
|
|
957
970
|
f"The following columns are not in the DataFrame: {missing_columns}"
|
|
958
971
|
)
|
|
959
972
|
|
|
960
|
-
data = self.data.copy()
|
|
961
|
-
|
|
962
973
|
if method == "one_hot":
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
974
|
+
if fit:
|
|
975
|
+
print("Fitting OneHotEncoder...") if verbose else None
|
|
976
|
+
self.encoder = OneHotEncoder(
|
|
977
|
+
sparse_output=False, drop="first", handle_unknown="ignore"
|
|
978
|
+
)
|
|
979
|
+
encoded_array = self.encoder.fit_transform(data[self.encode_list])
|
|
980
|
+
else:
|
|
981
|
+
print(
|
|
982
|
+
"Transforming data with OneHotEncoder fit before..."
|
|
983
|
+
) if verbose else None
|
|
984
|
+
encoded_array = self.encoder.transform(data[self.encode_list])
|
|
985
|
+
|
|
986
|
+
encoded_df = pd.DataFrame(
|
|
987
|
+
encoded_array, # type: ignore
|
|
988
|
+
columns=self.encoder.get_feature_names_out(self.encode_list),
|
|
989
|
+
index=data.index,
|
|
990
|
+
) # type: ignore
|
|
991
|
+
|
|
992
|
+
data = data.drop(columns=self.encode_list)
|
|
993
|
+
data = pd.concat([data, encoded_df], axis=1)
|
|
994
|
+
|
|
971
995
|
elif method == "distribution":
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
996
|
+
if fit:
|
|
997
|
+
print("Fitting distribution maps...") if verbose else None
|
|
998
|
+
self.distribution_maps = {
|
|
999
|
+
col: data[col].value_counts(normalize=True)
|
|
1000
|
+
for col in self.encode_list
|
|
1001
|
+
}
|
|
1002
|
+
for column in self.encode_list:
|
|
1003
|
+
data[column] = (
|
|
1004
|
+
data[column].map(self.distribution_maps[column]).fillna(0)
|
|
1005
|
+
)
|
|
1006
|
+
else:
|
|
1007
|
+
print(
|
|
1008
|
+
"Transforming data with distribution maps fit before..."
|
|
1009
|
+
) if verbose else None
|
|
1010
|
+
if self.distribution_maps:
|
|
1011
|
+
for column in self.encode_list:
|
|
1012
|
+
data[column] = (
|
|
1013
|
+
data[column].map(self.distribution_maps[column]).fillna(0)
|
|
1014
|
+
)
|
|
1015
|
+
else:
|
|
1016
|
+
raise ValueError(
|
|
1017
|
+
"Distribution maps not found. Please fit the encoder first."
|
|
1018
|
+
)
|
|
1019
|
+
|
|
976
1020
|
elif method == "woe":
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1021
|
+
if fit:
|
|
1022
|
+
print("Fitting WOE maps...") if verbose else None
|
|
1023
|
+
self.woe_maps = {}
|
|
1024
|
+
for column in self.encode_list:
|
|
1025
|
+
target = data[self.target_column]
|
|
1026
|
+
df = pd.DataFrame({column: data[column], "target": target})
|
|
1027
|
+
total_good = df["target"].sum()
|
|
1028
|
+
total_bad = df["target"].count() - total_good
|
|
1029
|
+
grouped = df.groupby(column, observed=False)["target"].agg(
|
|
1030
|
+
["sum", "count"]
|
|
1031
|
+
)
|
|
1032
|
+
grouped["good"] = grouped["sum"]
|
|
1033
|
+
grouped["bad"] = grouped["count"] - grouped["sum"]
|
|
1034
|
+
epsilon = 1e-10
|
|
1035
|
+
grouped["good_pct"] = (grouped["good"] + epsilon) / (
|
|
1036
|
+
total_good + epsilon
|
|
1037
|
+
)
|
|
1038
|
+
grouped["bad_pct"] = (grouped["bad"] + epsilon) / (
|
|
1039
|
+
total_bad + epsilon
|
|
1040
|
+
)
|
|
1041
|
+
grouped["woe"] = np.log(grouped["good_pct"] / grouped["bad_pct"])
|
|
1042
|
+
self.woe_maps[column] = grouped["woe"].to_dict()
|
|
1043
|
+
|
|
1044
|
+
for column in self.encode_list:
|
|
1045
|
+
data[column] = (
|
|
1046
|
+
data[column].astype(str).map(self.woe_maps[column]).fillna(0)
|
|
1047
|
+
)
|
|
1048
|
+
else:
|
|
1049
|
+
print(
|
|
1050
|
+
"Transforming data with WOE maps fit before..."
|
|
1051
|
+
) if verbose else None
|
|
1052
|
+
if self.woe_maps:
|
|
1053
|
+
for column in self.encode_list:
|
|
1054
|
+
data[column] = (
|
|
1055
|
+
data[column]
|
|
1056
|
+
.astype(str)
|
|
1057
|
+
.map(self.woe_maps[column])
|
|
1058
|
+
.fillna(0)
|
|
1059
|
+
)
|
|
1060
|
+
else:
|
|
1061
|
+
raise ValueError(
|
|
1062
|
+
"WOE maps not found. Please fit the encoder first."
|
|
1063
|
+
)
|
|
1064
|
+
|
|
981
1065
|
elif method == "catboost":
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1066
|
+
if fit:
|
|
1067
|
+
print("Fitting CatBoostEncoder...") if verbose else None
|
|
1068
|
+
self.encoder = CatBoostEncoder(cols=self.encode_list)
|
|
1069
|
+
data[self.encode_list] = self.encoder.fit_transform(
|
|
1070
|
+
data[self.encode_list], data[self.target_column]
|
|
1071
|
+
)
|
|
1072
|
+
else:
|
|
1073
|
+
print(
|
|
1074
|
+
"Transforming data with CatBoostEncoder fit before..."
|
|
1075
|
+
) if verbose else None
|
|
1076
|
+
data[self.encode_list] = self.encoder.transform(data[self.encode_list])
|
|
1077
|
+
|
|
986
1078
|
else:
|
|
987
1079
|
raise ValueError(
|
|
988
1080
|
f"Encoding method '{method}' is not supported. Choose from 'one_hot', 'distribution', 'woe', or 'catboost'."
|
|
989
1081
|
)
|
|
990
1082
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1083
|
+
if fit:
|
|
1084
|
+
self._log_transformation(
|
|
1085
|
+
"encoding_categorical",
|
|
1086
|
+
{"method": method, "selected_columns": self.encode_list},
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
return data
|
|
995
1090
|
|
|
996
1091
|
####### FEATURE ENGINEERING: OPTIONAL FUNCTIONS #######
|
|
997
1092
|
def search_pca_components(self):
|
|
@@ -1267,44 +1362,67 @@ class FeatureEngineering:
|
|
|
1267
1362
|
|
|
1268
1363
|
|
|
1269
1364
|
'''
|
|
1270
|
-
def
|
|
1271
|
-
self,
|
|
1365
|
+
def encoding_categorical(
|
|
1366
|
+
self,
|
|
1367
|
+
method: Literal["one_hot", "distribution", "woe", "catboost"] = "one_hot",
|
|
1272
1368
|
) -> pd.DataFrame:
|
|
1273
1369
|
"""
|
|
1274
|
-
|
|
1370
|
+
Perform encoding for the specified categorical columns using the chosen method.
|
|
1371
|
+
|
|
1372
|
+
The columns to be encoded are specified during the initialization of the FeatureEngineering class.
|
|
1275
1373
|
|
|
1276
1374
|
Parameters
|
|
1277
1375
|
----------
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
percentile : float, optional
|
|
1281
|
-
The percentage of data to be considered as extreme values on each side, by default 0.05.
|
|
1282
|
-
|
|
1283
|
-
Returns
|
|
1284
|
-
-------
|
|
1285
|
-
pd.DataFrame
|
|
1286
|
-
DataFrame with winsorized columns.
|
|
1376
|
+
method : {'one_hot', 'distribution', 'woe', 'catboost'}, optional
|
|
1377
|
+
The encoding method to use, by default 'one_hot'.
|
|
1287
1378
|
"""
|
|
1288
|
-
if
|
|
1289
|
-
|
|
1379
|
+
if self.encode_list is None:
|
|
1380
|
+
print("No columns specified for encoding.")
|
|
1290
1381
|
|
|
1291
|
-
|
|
1292
|
-
if
|
|
1293
|
-
|
|
1294
|
-
|
|
1382
|
+
missing_columns = [
|
|
1383
|
+
col for col in self.encode_list if col not in self.data.columns
|
|
1384
|
+
]
|
|
1385
|
+
if missing_columns:
|
|
1386
|
+
raise ValueError(
|
|
1387
|
+
f"The following columns are not in the DataFrame: {missing_columns}"
|
|
1388
|
+
)
|
|
1295
1389
|
|
|
1296
|
-
|
|
1297
|
-
upper_bound = np.percentile(self.data[column], (1 - percentile) * 100)
|
|
1298
|
-
self.data[column] = np.clip(self.data[column], lower_bound, upper_bound)
|
|
1390
|
+
data = self.data.copy()
|
|
1299
1391
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1392
|
+
if method == "one_hot":
|
|
1393
|
+
data = self._encoding_categorical_one_hot(data)
|
|
1394
|
+
print(f"One-hot encoding performed on columns: {self.encode_list}")
|
|
1395
|
+
# Get the names of the new encoded columns
|
|
1396
|
+
new_columns = [
|
|
1397
|
+
col for col in data.columns if col.startswith(tuple(self.encode_list))
|
|
1398
|
+
]
|
|
1399
|
+
print("\nHead of encoded columns:")
|
|
1400
|
+
print(data[new_columns].head())
|
|
1401
|
+
elif method == "distribution":
|
|
1402
|
+
data = self._encoding_categorical_distribution(data)
|
|
1403
|
+
print(f"Distribution encoding performed on columns: {self.encode_list}")
|
|
1404
|
+
print("\nHead of encoded columns:")
|
|
1405
|
+
print(data[self.encode_list].head())
|
|
1406
|
+
elif method == "woe":
|
|
1407
|
+
data = self._encoding_categorical_woe(data)
|
|
1408
|
+
print(f"WOE encoding performed on columns: {self.encode_list}")
|
|
1409
|
+
print("\nHead of encoded columns:")
|
|
1410
|
+
print(data[self.encode_list].head())
|
|
1411
|
+
elif method == "catboost":
|
|
1412
|
+
data = self._encoding_categorical_catboost(data)
|
|
1413
|
+
print(f"CatBoost encoding performed on columns: {self.encode_list}")
|
|
1414
|
+
print("\nHead of encoded columns:")
|
|
1415
|
+
print(data[self.encode_list].head())
|
|
1416
|
+
else:
|
|
1417
|
+
raise ValueError(
|
|
1418
|
+
f"Encoding method '{method}' is not supported. Choose from 'one_hot', 'distribution', 'woe', or 'catboost'."
|
|
1419
|
+
)
|
|
1302
1420
|
|
|
1421
|
+
self.data = data
|
|
1422
|
+
self.data.info()
|
|
1303
1423
|
self._log_transformation(
|
|
1304
|
-
"
|
|
1305
|
-
{"
|
|
1424
|
+
"encoding_categorical",
|
|
1425
|
+
{"method": method, "selected_columns": self.encode_list},
|
|
1306
1426
|
)
|
|
1307
1427
|
return self.data
|
|
1308
|
-
|
|
1309
|
-
percentile_capping = apply_winsorization
|
|
1310
1428
|
'''
|
{classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/first_glance.py
RENAMED
|
@@ -112,7 +112,7 @@ values in more detail, please call the see_duplicates method
|
|
|
112
112
|
top_n : int, optional
|
|
113
113
|
Number of top values to display, by default 10.
|
|
114
114
|
"""
|
|
115
|
-
for col in self.
|
|
115
|
+
for col in self.categorical_columns:
|
|
116
116
|
print(Fore.GREEN + f"\nColumn: {col}")
|
|
117
117
|
value_counts = self.data[col].value_counts().head(top_n)
|
|
118
118
|
for value, count in value_counts.items():
|