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.
Files changed (32) hide show
  1. classifier_toolkit-0.2.0/LICENSE +21 -0
  2. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/PKG-INFO +1 -1
  3. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/eda_toolkit.py +62 -66
  4. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/feature_engineering.py +182 -64
  5. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/first_glance.py +1 -1
  6. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/univariate_analysis.py +176 -238
  7. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/visualizations.py +135 -2
  8. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/__init__.py +9 -4
  9. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/base.py +128 -7
  10. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/embedded_methods/__init__.py +2 -2
  11. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +297 -0
  12. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/meta_selector.py +323 -0
  13. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/utils/data_handling.py +244 -0
  14. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/utils/plottings.py +209 -0
  15. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/utils/scoring.py +121 -0
  16. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/wrapper_methods/__init__.py +6 -2
  17. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/wrapper_methods/rfe.py +433 -0
  18. classifier_toolkit-0.2.0/classifier_toolkit/feature_selection/wrapper_methods/rfe_catboost.py +337 -0
  19. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/feature_selection/wrapper_methods/sequential_selection.py +179 -51
  20. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/pyproject.toml +13 -1
  21. classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +0 -178
  22. classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/meta_selector.py +0 -329
  23. classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/utils/plottings.py +0 -65
  24. classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/utils/scoring.py +0 -86
  25. classifier_toolkit-0.1.3/classifier_toolkit/feature_selection/wrapper_methods/rfe.py +0 -345
  26. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/README.md +0 -0
  27. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/__init__.py +0 -0
  28. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/bivariate_analysis.py +0 -0
  29. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/warnings/__init__.py +0 -0
  30. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/warnings/automated_warnings.py +0 -0
  31. {classifier_toolkit-0.1.3 → classifier_toolkit-0.2.0}/classifier_toolkit/eda/warnings/default_warnings.py +0 -0
  32. {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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: classifier-toolkit
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary:
5
5
  Author: gauthier.marquand
6
6
  Author-email: gauthier.marquand@qonto.com
@@ -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}.csv"
39
- interm.to_csv(path_to_save, index=False)
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("scheduled_at", "min_balance_3m")
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
- excluded_vars=self.exclusion, id_column=self.id_column
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 print_psi(
296
+ def plot_psi(
276
297
  self,
277
- buckets: int,
278
- bucket_type: Literal["quantile", "fixed_width"],
279
- time_column: str,
280
- time_expected: Union[str, datetime, date],
281
- end_date: Union[str, datetime, date],
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
- The type of binning to use.
294
- time_column : str
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
- # Print data info after PSI calculation
338
- print(
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
- excluded_vars=self.exclusion,
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.
@@ -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
- self._log_transformation(
368
- "apply_transformations", {"columns_to_keep": columns_to_keep}
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
- missing_columns = [
953
- col for col in self.encode_list if col not in self.data.columns
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
- data = self._encoding_categorical_one_hot(data)
964
- print(f"One-hot encoding performed on columns: {self.encode_list}")
965
- # Get the names of the new encoded columns
966
- new_columns = [
967
- col for col in data.columns if col.startswith(tuple(self.encode_list))
968
- ]
969
- print("\nHead of encoded columns:")
970
- print(data[new_columns].head())
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
- data = self._encoding_categorical_distribution(data)
973
- print(f"Distribution encoding performed on columns: {self.encode_list}")
974
- print("\nHead of encoded columns:")
975
- print(data[self.encode_list].head())
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
- data = self._encoding_categorical_woe(data)
978
- print(f"WOE encoding performed on columns: {self.encode_list}")
979
- print("\nHead of encoded columns:")
980
- print(data[self.encode_list].head())
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
- data = self._encoding_categorical_catboost(data)
983
- print(f"CatBoost encoding performed on columns: {self.encode_list}")
984
- print("\nHead of encoded columns:")
985
- print(data[self.encode_list].head())
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
- self.data = data
992
- self.data.info()
993
- self._log_transformation("encoding_categorical", {"method": method})
994
- return self.data
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 apply_winsorization(
1271
- self, selected_columns: List[str], percentile: float = 0.05
1365
+ def encoding_categorical(
1366
+ self,
1367
+ method: Literal["one_hot", "distribution", "woe", "catboost"] = "one_hot",
1272
1368
  ) -> pd.DataFrame:
1273
1369
  """
1274
- Apply winsorization to numerical columns to limit extreme values.
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
- selected_columns : List[str]
1279
- List of columns to apply winsorization to.
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 len(selected_columns) == 0:
1289
- selected_columns = self.numerical_columns
1379
+ if self.encode_list is None:
1380
+ print("No columns specified for encoding.")
1290
1381
 
1291
- for column in selected_columns:
1292
- if column == self.target_column:
1293
- print(f"{column} is the target column. Skipping winsorization.")
1294
- continue
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
- lower_bound = np.percentile(self.data[column], percentile * 100)
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
- print(f"\nWinsorization applied with {percentile * 100}% capping on each side.")
1301
- print(self.data[selected_columns].describe())
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
- "winsorization",
1305
- {"selected_columns": selected_columns, "percentile": percentile},
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
  '''
@@ -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.columns:
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():