classifier-toolkit 0.1.2__tar.gz → 0.1.4__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 (24) hide show
  1. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/PKG-INFO +1 -1
  2. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/eda_toolkit.py +18 -10
  3. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/feature_engineering.py +9 -48
  4. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/first_glance.py +1 -1
  5. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/univariate_analysis.py +107 -92
  6. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/visualizations.py +27 -70
  7. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/pyproject.toml +1 -1
  8. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/README.md +0 -0
  9. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/__init__.py +0 -0
  10. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/bivariate_analysis.py +0 -0
  11. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/warnings/__init__.py +0 -0
  12. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/warnings/automated_warnings.py +0 -0
  13. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/eda/warnings/default_warnings.py +0 -0
  14. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/__init__.py +0 -0
  15. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/base.py +0 -0
  16. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/embedded_methods/__init__.py +0 -0
  17. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +0 -0
  18. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/meta_selector.py +0 -0
  19. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/utils/__init__.py +0 -0
  20. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/utils/plottings.py +0 -0
  21. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/utils/scoring.py +0 -0
  22. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/wrapper_methods/__init__.py +0 -0
  23. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/wrapper_methods/rfe.py +0 -0
  24. {classifier_toolkit-0.1.2 → classifier_toolkit-0.1.4}/classifier_toolkit/feature_selection/wrapper_methods/sequential_selection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: classifier-toolkit
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary:
5
5
  Author: gauthier.marquand
6
6
  Author-email: gauthier.marquand@qonto.com
@@ -4,8 +4,6 @@ from typing import Any, Dict, List, Literal, Optional, Union
4
4
 
5
5
  import pandas as pd
6
6
 
7
- from examples.constants import PATH_DATA_PROCESSED
8
-
9
7
  from .bivariate_analysis import BivariateAnalysis
10
8
  from .feature_engineering import FeatureEngineering
11
9
  from .first_glance import FirstGlance
@@ -44,6 +42,7 @@ class EDAToolkit:
44
42
  self,
45
43
  dataframe: pd.DataFrame,
46
44
  can_be_negative: List[str],
45
+ path_data_folder: Path,
47
46
  target_column: Optional[str] = None,
48
47
  exclusion: Optional[List[str]] = None,
49
48
  numerical_columns: Optional[List[str]] = None,
@@ -84,6 +83,7 @@ class EDAToolkit:
84
83
  self.categorical_columns = categorical_columns or []
85
84
  self.id_column = id_column or ""
86
85
  self.to_be_enc = to_be_enc or []
86
+ self.path_data_folder = path_data_folder
87
87
 
88
88
  # Initialize an empty list for transformations
89
89
  self.transformations: TransformationType = []
@@ -206,12 +206,20 @@ class EDAToolkit:
206
206
  self.first_glance.super_vision()
207
207
  self.first_glance.check_duplicates()
208
208
 
209
- def plot_target_evolution(self):
209
+ def plot_target_evolution(self, time_column: str, evolving_numeric: str):
210
210
  """
211
211
  Plot the target balance and evolution over time.
212
+
213
+ Parameters
214
+ ----------
215
+ time_column : str
216
+ The name of the time column.
217
+ evolving_numeric : str
218
+ The name of the numerical column to plot that we want to see the evolution of
219
+ which helps us understanding the behavior of the target column.
212
220
  """
213
221
  self.visualizations.plot_target_balance()
214
- self.visualizations.plot_target_evolution("scheduled_at", "min_balance_3m")
222
+ self.visualizations.plot_target_evolution(time_column, evolving_numeric)
215
223
 
216
224
  def plot_numerical_distributions(self, sub_plots: bool = False):
217
225
  """
@@ -232,7 +240,7 @@ class EDAToolkit:
232
240
  """
233
241
  self.visualizations.plot_categorical_value_counts()
234
242
  self.visualizations.visualize_high_cardinality(
235
- excluded_vars=self.exclusion, id_column=self.id_column
243
+ excluded_columns=self.exclusion, id_column=self.id_column
236
244
  )
237
245
  self.visualizations.visualize_low_cardinality()
238
246
 
@@ -377,7 +385,7 @@ class EDAToolkit:
377
385
  """
378
386
  _, filtered_cramers = self.univariate_analysis.plot_cramers_v(
379
387
  self.numerical_columns,
380
- excluded_vars=self.exclusion,
388
+ excluded_columns=self.exclusion,
381
389
  filter_threshold=threshold_c,
382
390
  )
383
391
 
@@ -406,7 +414,7 @@ class EDAToolkit:
406
414
  imputed = self.feature_engineering.handle_missing_values(imputation_method)
407
415
  transforms = self.get_transformations(unwanted_features=eliminated)
408
416
 
409
- EDAToolkit.save_intermediary(PATH_DATA_PROCESSED, imputed, "imputed")
417
+ EDAToolkit.save_intermediary(self.path_data_folder, imputed, "imputed")
410
418
  print("Imputed dataframe saved in the processed directory.")
411
419
 
412
420
  self.feature_engineering = FeatureEngineering(
@@ -432,7 +440,7 @@ class EDAToolkit:
432
440
  to_transformed
433
441
  )
434
442
  transforms = self.get_transformations()
435
- EDAToolkit.save_intermediary(PATH_DATA_PROCESSED, transformed, "transformed")
443
+ EDAToolkit.save_intermediary(self.path_data_folder, transformed, "transformed")
436
444
  print("Transformed dataframe saved in the processed directory.")
437
445
 
438
446
  self.feature_engineering = FeatureEngineering(
@@ -458,7 +466,7 @@ class EDAToolkit:
458
466
  Method to use for handling low cardinality columns.
459
467
  """
460
468
  binned = self.feature_engineering.binnings(methods=handling_method)
461
- EDAToolkit.save_intermediary(PATH_DATA_PROCESSED, binned, "binned")
469
+ EDAToolkit.save_intermediary(self.path_data_folder, binned, "binned")
462
470
  print(
463
471
  "Variables with low cardinality are treated and saved in the processed directory."
464
472
  )
@@ -485,7 +493,7 @@ class EDAToolkit:
485
493
  )
486
494
  if high_card is not None:
487
495
  EDAToolkit.save_intermediary(
488
- PATH_DATA_PROCESSED, high_card, "high_cardinality_treated"
496
+ self.path_data_folder, high_card, "high_cardinality_treated"
489
497
  )
490
498
  print(
491
499
  "High cardinality variables are treated and dataframe saved in the processed directory."
@@ -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(
@@ -990,7 +992,10 @@ class FeatureEngineering:
990
992
 
991
993
  self.data = data
992
994
  self.data.info()
993
- self._log_transformation("encoding_categorical", {"method": method})
995
+ self._log_transformation(
996
+ "encoding_categorical",
997
+ {"method": method, "selected_columns": self.encode_list},
998
+ )
994
999
  return self.data
995
1000
 
996
1001
  ####### FEATURE ENGINEERING: OPTIONAL FUNCTIONS #######
@@ -1264,47 +1269,3 @@ class FeatureEngineering:
1264
1269
  return self.data
1265
1270
 
1266
1271
  percentile_capping = apply_winsorization
1267
-
1268
-
1269
- '''
1270
- def apply_winsorization(
1271
- self, selected_columns: List[str], percentile: float = 0.05
1272
- ) -> pd.DataFrame:
1273
- """
1274
- Apply winsorization to numerical columns to limit extreme values.
1275
-
1276
- Parameters
1277
- ----------
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.
1287
- """
1288
- if len(selected_columns) == 0:
1289
- selected_columns = self.numerical_columns
1290
-
1291
- for column in selected_columns:
1292
- if column == self.target_column:
1293
- print(f"{column} is the target column. Skipping winsorization.")
1294
- continue
1295
-
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)
1299
-
1300
- print(f"\nWinsorization applied with {percentile * 100}% capping on each side.")
1301
- print(self.data[selected_columns].describe())
1302
-
1303
- self._log_transformation(
1304
- "winsorization",
1305
- {"selected_columns": selected_columns, "percentile": percentile},
1306
- )
1307
- return self.data
1308
-
1309
- percentile_capping = apply_winsorization
1310
- '''
@@ -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():
@@ -31,13 +31,13 @@ class UnivariateAnalysis:
31
31
  self.numerical_features = numerical_features or []
32
32
 
33
33
  @staticmethod
34
- def _get_cramers_v(data: pd.DataFrame, var1: str, var2: str) -> float:
34
+ def _get_cramers_v(df: pd.DataFrame, var1: str, var2: str) -> float:
35
35
  """
36
36
  Calculate Cramer's V statistic for two variables.
37
37
 
38
38
  Parameters
39
39
  ----------
40
- data : pd.DataFrame
40
+ df : pd.DataFrame
41
41
  The data containing the variables.
42
42
  var1 : str
43
43
  The first variable.
@@ -49,15 +49,15 @@ class UnivariateAnalysis:
49
49
  float
50
50
  Cramer's V statistic.
51
51
  """
52
- confusion_matrix = pd.crosstab(data[var1], data[var2])
52
+ confusion_matrix = pd.crosstab(df[var1], df[var2])
53
53
  chi2 = chi2_contingency(confusion_matrix)[0]
54
54
  n = confusion_matrix.sum().sum()
55
55
  min_dim = min(confusion_matrix.shape) - 1
56
56
 
57
57
  return np.sqrt(chi2 / (n * min_dim))
58
58
 
59
- def _prepare_cramersV_features(
60
- self, numerical_features: List[str], n_bins: int = 5
59
+ def _prepare_cramers_v_features(
60
+ self, numerical_features: List[str], num_bins: int = 5
61
61
  ) -> List[str]:
62
62
  """
63
63
  Prepare features for Cramer's V calculation by binning numerical features.
@@ -66,7 +66,7 @@ class UnivariateAnalysis:
66
66
  ----------
67
67
  numerical_features : List[str]
68
68
  List of numerical feature names.
69
- n_bins : int, optional
69
+ num_bins : int, optional
70
70
  Number of bins for continuous variables, by default 5.
71
71
 
72
72
  Returns
@@ -74,13 +74,13 @@ class UnivariateAnalysis:
74
74
  List[str]
75
75
  List of features prepared for Cramer's V calculation.
76
76
  """
77
- cramersV_var_list = self.data.select_dtypes(
77
+ cramers_v_var_list = self.data.select_dtypes(
78
78
  include=["object", "category"]
79
79
  ).columns.tolist()
80
80
 
81
81
  # Exclude the target column
82
- cramersV_var_list = [
83
- var for var in cramersV_var_list if var != self.target_column
82
+ cramers_v_var_list = [
83
+ var for var in cramers_v_var_list if var != self.target_column
84
84
  ]
85
85
 
86
86
  for num in numerical_features:
@@ -91,12 +91,12 @@ class UnivariateAnalysis:
91
91
  ): # Ensure there are at least two unique values for binning
92
92
  try:
93
93
  self.data[f"{num}_bin"] = pd.qcut(
94
- self.data[num], q=n_bins, duplicates="drop"
94
+ self.data[num], q=num_bins, duplicates="drop"
95
95
  )
96
96
  # Check if the resulting bins are more than 1
97
97
  if self.data[f"{num}_bin"].nunique() <= 1:
98
98
  raise ValueError(
99
- f"Only 1 bin created for {num} with q={n_bins}."
99
+ f"Only 1 bin created for {num} with q={num_bins}."
100
100
  )
101
101
 
102
102
  except ValueError as e:
@@ -113,19 +113,19 @@ class UnivariateAnalysis:
113
113
  self.data[f"{num}_bin"] = pd.qcut(
114
114
  self.data[num], q=2, duplicates="drop"
115
115
  )
116
- cramersV_var_list.append(f"{num}_bin")
116
+ cramers_v_var_list.append(f"{num}_bin")
117
117
  else:
118
118
  # Handle the case where there is only one unique value
119
119
  print(f"Skipping {num} as it has only {unique_values} unique value(s).")
120
120
 
121
- return cramersV_var_list
121
+ return cramers_v_var_list
122
122
 
123
123
  def plot_cramers_v(
124
124
  self,
125
125
  numerical_features: List[str],
126
- excluded_vars: List[str],
126
+ excluded_columns: List[str],
127
127
  filter_threshold: float,
128
- n_bins: int = 5,
128
+ num_bins: int = 5,
129
129
  fig_width: int = 800,
130
130
  fig_height: int = 800,
131
131
  ) -> Tuple[pd.DataFrame, List[str]]:
@@ -136,9 +136,9 @@ class UnivariateAnalysis:
136
136
  ----------
137
137
  numerical_features : List[str]
138
138
  List of numerical feature names.
139
- excluded_vars : List[str]
139
+ excluded_columns : List[str]
140
140
  List of variables to exclude from the analysis.
141
- n_bins : int, optional
141
+ num_bins : int, optional
142
142
  Number of bins for continuous variables, by default 5.
143
143
  fig_width : int, optional
144
144
  Width of the figure in pixels, by default 800.
@@ -150,22 +150,24 @@ class UnivariateAnalysis:
150
150
  pd.DataFrame
151
151
  DataFrame containing Cramer's V values.
152
152
  """
153
- cramersV_var_list = self._prepare_cramersV_features(numerical_features, n_bins)
153
+ cramers_v_var_list = self._prepare_cramers_v_features(
154
+ numerical_features, num_bins
155
+ )
154
156
 
155
157
  # Exclude any specified variables
156
- cramersV_var_list = [
157
- var for var in cramersV_var_list if var not in excluded_vars
158
+ cramers_v_var_list = [
159
+ var for var in cramers_v_var_list if var not in excluded_columns
158
160
  ]
159
161
 
160
162
  rows = []
161
- for cat in cramersV_var_list:
163
+ for cat in cramers_v_var_list:
162
164
  cramers = UnivariateAnalysis._get_cramers_v(
163
165
  self.data, cat, self.target_column
164
166
  )
165
167
  rows.append(round(cramers, 2))
166
168
 
167
169
  cramers_results = pd.DataFrame(
168
- rows, columns=[self.target_column], index=cramersV_var_list
170
+ rows, columns=[self.target_column], index=cramers_v_var_list
169
171
  )
170
172
 
171
173
  # Create a Plotly heatmap
@@ -194,18 +196,22 @@ class UnivariateAnalysis:
194
196
  return cramers_results, filtered_vars
195
197
 
196
198
  def _calculate_information_value(
197
- self, feature_col: str, target_col: str, bins: int = 10, show_woe: bool = False
199
+ self,
200
+ feature_column: str,
201
+ target_column: str,
202
+ num_bins: int = 10,
203
+ show_woe: bool = False,
198
204
  ) -> Union[Tuple[float, pd.DataFrame], float]:
199
205
  """
200
206
  Calculate Information Value for a single feature.
201
207
 
202
208
  Parameters
203
209
  ----------
204
- feature_col : str
210
+ feature_column : str
205
211
  Name of the feature column.
206
- target_col : str
212
+ target_column : str
207
213
  Name of the target column.
208
- bins : int, optional
214
+ num_bins : int, optional
209
215
  Number of bins for continuous variables, by default 10.
210
216
  show_woe : bool, optional
211
217
  Boolean to show WOE values, by default False.
@@ -217,21 +223,23 @@ class UnivariateAnalysis:
217
223
  """
218
224
  df = pd.DataFrame(
219
225
  {
220
- feature_col: self.data[feature_col],
221
- target_col: self.data[target_col].astype(float),
226
+ feature_column: self.data[feature_column],
227
+ target_column: self.data[target_column].astype(float),
222
228
  }
223
229
  )
224
230
 
225
- if df[feature_col].dtype in ["int64", "float64"]:
226
- df["bins"] = pd.qcut(df[feature_col], q=bins, duplicates="drop")
231
+ if df[feature_column].dtype in ["int64", "float64"]:
232
+ df["bins"] = pd.qcut(df[feature_column], q=num_bins, duplicates="drop")
227
233
  else:
228
- df["bins"] = df[feature_col]
234
+ df["bins"] = df[feature_column]
229
235
 
230
- total_good = df[target_col].sum()
231
- total_bad = df[target_col].count() - total_good
236
+ total_good = df[target_column].sum()
237
+ total_bad = df[target_column].count() - total_good
232
238
 
233
239
  # Explicitly setting observed=False to retain current behavior
234
- grouped = df.groupby("bins", observed=False)[target_col].agg(["sum", "count"])
240
+ grouped = df.groupby("bins", observed=False)[target_column].agg(
241
+ ["sum", "count"]
242
+ )
235
243
  grouped["good"] = grouped["sum"]
236
244
  grouped["bad"] = grouped["count"] - grouped["sum"]
237
245
 
@@ -255,7 +263,7 @@ class UnivariateAnalysis:
255
263
  numerical_features: List[str],
256
264
  filter_threshold: float,
257
265
  target_column: str,
258
- bins: int = 10,
266
+ num_bins: int = 10,
259
267
  fig_width: int = 800,
260
268
  fig_height: int = 800,
261
269
  ) -> Tuple[pd.DataFrame, List[str]]:
@@ -268,7 +276,7 @@ class UnivariateAnalysis:
268
276
  List of numerical feature names.
269
277
  target_column : str
270
278
  Name of the target column.
271
- bins : int, optional
279
+ num_bins : int, optional
272
280
  Number of bins for continuous variables, by default 10.
273
281
  fig_width : int, optional
274
282
  Width of the figure in pixels, by default 800.
@@ -282,7 +290,7 @@ class UnivariateAnalysis:
282
290
  """
283
291
  iv_values = []
284
292
  for feature in numerical_features:
285
- iv = self._calculate_information_value(feature, target_column, bins)
293
+ iv = self._calculate_information_value(feature, target_column, num_bins)
286
294
  iv_values.append(iv)
287
295
 
288
296
  iv_df = pd.DataFrame(
@@ -412,7 +420,7 @@ class UnivariateAnalysis:
412
420
 
413
421
  fig.show()
414
422
 
415
- def _make_partitions(self, number_of_partitions) -> pd.DataFrame:
423
+ def _make_partitions(self, num_partitions: int) -> pd.DataFrame:
416
424
  """
417
425
  Partition the numerical features into 4 bins.
418
426
 
@@ -425,21 +433,21 @@ class UnivariateAnalysis:
425
433
  for num in self.numerical_features:
426
434
  partitioned[num + "_bin"] = pd.qcut(
427
435
  x=partitioned[num],
428
- q=number_of_partitions,
436
+ q=num_partitions,
429
437
  labels=False,
430
438
  duplicates="drop",
431
439
  ).astype("category")
432
440
  return partitioned
433
441
 
434
442
  def get_num_feature_repartition(
435
- self, time_col: str, number_of_partitions: int = 4, freq: str = "1ME"
443
+ self, time_column: str, num_partitions: int = 4, freq: str = "1ME"
436
444
  ) -> Optional[pd.DataFrame]:
437
445
  """
438
446
  Plot the risk class repartition through time and return a table of the risk class repartition through time.
439
447
 
440
448
  Parameters
441
449
  ----------
442
- time_col : str
450
+ time_column : str
443
451
  Name of the column that contains the observation date.
444
452
  freq : str, optional
445
453
  The frequency at which the risk class is aggregated ('D', 'M', or 'Y'), by default '1ME'.
@@ -449,17 +457,17 @@ class UnivariateAnalysis:
449
457
  Optional[pd.DataFrame]
450
458
  DataFrame containing the risk class repartition through time.
451
459
  """
452
- partitioned = self._make_partitions(number_of_partitions)
460
+ partitioned = self._make_partitions(num_partitions)
453
461
 
454
462
  # Drop rows where the time column is NaT
455
- if partitioned[time_col].isnull().any():
463
+ if partitioned[time_column].isnull().any():
456
464
  print(
457
- f"There are missing entries in the {time_col} column, dropping them for the functionality; however, try to mitigate the problem."
465
+ f"There are missing entries in the {time_column} column, dropping them for the functionality; however, try to mitigate the problem."
458
466
  )
459
- partitioned = partitioned.dropna(subset=[time_col])
467
+ partitioned = partitioned.dropna(subset=[time_column])
460
468
 
461
469
  row_nb = math.ceil(len(self.numerical_features) / 2)
462
- fig, ax = plt.subplots(row_nb, 2, figsize=(20, number_of_partitions * row_nb))
470
+ fig, ax = plt.subplots(row_nb, 2, figsize=(20, num_partitions * row_nb))
463
471
  i, j = 0, 0
464
472
 
465
473
  for num in self.numerical_features:
@@ -468,14 +476,15 @@ class UnivariateAnalysis:
468
476
  # Group by and count
469
477
  draw = (
470
478
  partitioned.groupby(
471
- [num + "_bin", pd.Grouper(key=time_col, freq=freq)], observed=False
479
+ [num + "_bin", pd.Grouper(key=time_column, freq=freq)],
480
+ observed=False,
472
481
  )[self.target_column]
473
482
  .count()
474
483
  .reset_index()
475
484
  )
476
485
 
477
486
  # Create 'freq' column with aligned indices
478
- draw["freq"] = draw.groupby([time_col])[self.target_column].transform(
487
+ draw["freq"] = draw.groupby([time_column])[self.target_column].transform(
479
488
  lambda x: x / x.sum()
480
489
  )
481
490
 
@@ -483,7 +492,7 @@ class UnivariateAnalysis:
483
492
  draw_pivot = pd.pivot_table(
484
493
  draw,
485
494
  values="freq",
486
- index=time_col,
495
+ index=time_column,
487
496
  columns=num + "_bin",
488
497
  observed=False,
489
498
  )
@@ -501,8 +510,8 @@ class UnivariateAnalysis:
501
510
 
502
511
  def _prepare_data_for_psi(
503
512
  self,
504
- number_of_partitions: int,
505
- time_col: str,
513
+ num_partitions: int,
514
+ time_column: str,
506
515
  start_date: Union[str, datetime, date],
507
516
  end_date: Union[str, datetime, date],
508
517
  ):
@@ -511,14 +520,14 @@ class UnivariateAnalysis:
511
520
 
512
521
  Parameters
513
522
  ----------
514
- time_col : str
523
+ time_column : str
515
524
  Name of the column that contains the observation date.
516
525
  start_date : Union[str, datetime, date]
517
526
  Start date for the PSI calculation.
518
527
  end_date : Union[str, datetime, date]
519
528
  End date for the PSI calculation.
520
529
  """
521
- self.data = self._make_partitions(number_of_partitions)
530
+ self.data = self._make_partitions(num_partitions)
522
531
 
523
532
  # Convert string dates to datetime if necessary
524
533
  if isinstance(start_date, str):
@@ -526,21 +535,21 @@ class UnivariateAnalysis:
526
535
  if isinstance(end_date, str):
527
536
  end_date = pd.to_datetime(end_date)
528
537
 
529
- # Ensure time_col is in datetime format
530
- self.data[time_col] = pd.to_datetime(self.data[time_col])
538
+ # Ensure time_column is in datetime format
539
+ self.data[time_column] = pd.to_datetime(self.data[time_column])
531
540
 
532
- _filter_date = (self.data[time_col] >= start_date) & (
533
- self.data[time_col] < end_date
541
+ _filter_date = (self.data[time_column] >= start_date) & (
542
+ self.data[time_column] < end_date
534
543
  )
535
544
  self.data = self.data.loc[_filter_date]
536
545
  self.data.reset_index(drop=True, inplace=True)
537
546
 
538
547
  def _calculate_psi_single(
539
548
  self,
540
- time_col: str,
549
+ time_column: str,
541
550
  time_expected: Union[str, datetime, date],
542
- col: str,
543
- buckets: int,
551
+ column: str,
552
+ num_buckets: int,
544
553
  bucket_type: Literal["quantile", "fixed_width"] = "quantile",
545
554
  ):
546
555
  """
@@ -548,13 +557,13 @@ class UnivariateAnalysis:
548
557
 
549
558
  Parameters
550
559
  ----------
551
- time_col : str
560
+ time_column : str
552
561
  Name of the column that contains the observation date.
553
562
  time_expected : Union[str, datetime, date]
554
563
  The expected date for the PSI calculation.
555
- col : str
564
+ column : str
556
565
  Name of the column to calculate the PSI.
557
- buckets : int
566
+ num_buckets : int
558
567
  Number of buckets for binning.
559
568
  bucket_type : Literal["quantile", "fixed_width"]
560
569
  The type of binning to use.
@@ -567,11 +576,11 @@ class UnivariateAnalysis:
567
576
  if isinstance(time_expected, str):
568
577
  time_expected = pd.to_datetime(time_expected)
569
578
 
570
- _filter_expected = self.data[time_col] < time_expected
571
- expected = self.data.loc[_filter_expected, col]
572
- observed = self.data.loc[~_filter_expected, col]
579
+ _filter_expected = self.data[time_column] < time_expected
580
+ expected = self.data.loc[_filter_expected, column]
581
+ observed = self.data.loc[~_filter_expected, column]
573
582
 
574
- bins = self._create_bins(self.data[col], buckets, bucket_type)
583
+ bins = self._create_bins(self.data[column], num_buckets, bucket_type)
575
584
 
576
585
  expected_counts, _ = np.histogram(expected, bins=bins)
577
586
  observed_counts, _ = np.histogram(observed, bins=bins)
@@ -591,13 +600,13 @@ class UnivariateAnalysis:
591
600
  def _create_bins(
592
601
  self,
593
602
  series: pd.Series,
594
- buckets: int,
603
+ num_buckets: int,
595
604
  bucket_type: Literal["quantile", "fixed_width"],
596
605
  ) -> np.ndarray:
597
606
  if bucket_type == "quantile":
598
- return pd.qcut(series, q=buckets, duplicates="drop", retbins=True)[1]
607
+ return pd.qcut(series, q=num_buckets, duplicates="drop", retbins=True)[1]
599
608
  else: # fixed_width
600
- return pd.cut(series, bins=buckets, retbins=True)[1]
609
+ return pd.cut(series, bins=num_buckets, retbins=True)[1]
601
610
 
602
611
  def _create_buckets(self, series: pd.Series, bins: np.ndarray) -> pd.Series:
603
612
  return pd.cut(
@@ -633,25 +642,25 @@ class UnivariateAnalysis:
633
642
 
634
643
  def calculate_psi(
635
644
  self,
636
- train: pd.DataFrame,
637
- test: Optional[pd.DataFrame],
638
- time_col: str,
645
+ train_df: pd.DataFrame,
646
+ test_df: Optional[pd.DataFrame],
647
+ time_column: str,
639
648
  time_expected: Union[str, datetime, date],
640
- buckets: int = 10,
649
+ num_buckets: int = 10,
641
650
  bucket_type: Literal["quantile", "fixed_width"] = "quantile",
642
651
  epsilon: float = 1e-5,
643
652
  ) -> pd.DataFrame:
644
653
  psi_values = {}
645
654
  for feature in self.numerical_features:
646
- if test is None:
647
- expected = train[train[time_col] < time_expected][feature]
648
- observed = train[train[time_col] >= time_expected][feature]
655
+ if test_df is None:
656
+ expected = train_df[train_df[time_column] < time_expected][feature]
657
+ observed = train_df[train_df[time_column] >= time_expected][feature]
649
658
  else:
650
- expected = train[feature]
651
- observed = test[feature]
659
+ expected = train_df[feature]
660
+ observed = test_df[feature]
652
661
 
653
662
  combined = pd.concat([expected, observed])
654
- bins = self._create_bins(combined, buckets, bucket_type)
663
+ bins = self._create_bins(combined, num_buckets, bucket_type)
655
664
  psi = self._calculate_psi(expected, observed, bins, epsilon)
656
665
  psi_values[feature] = psi
657
666
 
@@ -698,12 +707,12 @@ class UnivariateAnalysis:
698
707
 
699
708
  def compute_psi(
700
709
  self,
701
- time_col: str,
710
+ time_column: str,
702
711
  time_expected: Union[str, datetime, date],
703
712
  start_date: Union[str, datetime, date],
704
713
  end_date: Union[str, datetime, date],
705
714
  test_data: Optional[pd.DataFrame] = None,
706
- buckets: int = 10,
715
+ num_buckets: int = 10,
707
716
  bucket_type: Literal["quantile", "fixed_width"] = "quantile",
708
717
  test_mode: bool = False,
709
718
  ) -> pd.DataFrame:
@@ -714,21 +723,27 @@ class UnivariateAnalysis:
714
723
  if isinstance(time_expected, str):
715
724
  time_expected = pd.to_datetime(time_expected)
716
725
 
717
- self.data[time_col] = pd.to_datetime(self.data[time_col])
726
+ self.data[time_column] = pd.to_datetime(self.data[time_column])
718
727
  filtered_data = self.data[
719
- (self.data[time_col] >= start_date) & (self.data[time_col] < end_date)
728
+ (self.data[time_column] >= start_date) & (self.data[time_column] < end_date)
720
729
  ]
721
730
 
722
731
  if test_data is not None:
723
- test_data[time_col] = pd.to_datetime(test_data[time_col])
732
+ test_data[time_column] = pd.to_datetime(test_data[time_column])
724
733
  test_filtered = test_data[
725
- (test_data[time_col] >= start_date) & (test_data[time_col] < end_date)
734
+ (test_data[time_column] >= start_date)
735
+ & (test_data[time_column] < end_date)
726
736
  ]
727
737
  else:
728
738
  test_filtered = None
729
739
 
730
740
  psi_df = self.calculate_psi(
731
- filtered_data, test_filtered, time_col, time_expected, buckets, bucket_type
741
+ filtered_data,
742
+ test_filtered,
743
+ time_column,
744
+ time_expected,
745
+ num_buckets,
746
+ bucket_type,
732
747
  )
733
748
 
734
749
  fig = self.plot_psi(psi_df)
@@ -746,9 +761,9 @@ class UnivariateAnalysis:
746
761
 
747
762
  def _calculate_psi_all(
748
763
  self,
749
- time_col: str,
764
+ time_column: str,
750
765
  time_expected: Union[str, datetime, date],
751
- buckets: int,
766
+ num_buckets: int,
752
767
  bucket_type: Literal["quantile", "fixed_width"],
753
768
  ) -> pd.Series:
754
769
  """
@@ -756,11 +771,11 @@ class UnivariateAnalysis:
756
771
 
757
772
  Parameters
758
773
  ----------
759
- time_col : str
774
+ time_column : str
760
775
  Name of the column that contains the observation date.
761
776
  time_expected : Union[str, datetime, date]
762
777
  The expected date for the PSI calculation.
763
- buckets : int
778
+ num_buckets : int
764
779
  Number of buckets for binning.
765
780
  bucket_type : Literal["quantile", "fixed_width"]
766
781
  The type of binning to use.
@@ -773,6 +788,6 @@ class UnivariateAnalysis:
773
788
  dict_psi = {}
774
789
  for num in self.numerical_features:
775
790
  dict_psi[num] = self._calculate_psi_single(
776
- time_col, time_expected, num, buckets, bucket_type
791
+ time_column, time_expected, num, num_buckets, bucket_type
777
792
  )
778
793
  return pd.Series(dict_psi)
@@ -21,7 +21,7 @@ class Visualizations:
21
21
 
22
22
  def __init__(
23
23
  self,
24
- dataframe: pd.DataFrame,
24
+ df: pd.DataFrame,
25
25
  target_column: str,
26
26
  numerical_columns: List[str],
27
27
  categorical_columns: List[str],
@@ -31,7 +31,7 @@ class Visualizations:
31
31
 
32
32
  Parameters
33
33
  ----------
34
- dataframe : pd.DataFrame
34
+ df : pd.DataFrame
35
35
  The data to be analyzed.
36
36
  target_column : str
37
37
  The target column for analysis.
@@ -40,7 +40,7 @@ class Visualizations:
40
40
  categorical_columns : List[str]
41
41
  List of categorical columns.
42
42
  """
43
- self.data = dataframe
43
+ self.data = df
44
44
  self.target_column = target_column
45
45
  self.numerical_columns = numerical_columns
46
46
  self.categorical_columns = categorical_columns
@@ -292,7 +292,7 @@ class Visualizations:
292
292
 
293
293
  def visualize_high_cardinality(
294
294
  self,
295
- excluded_vars: List[str],
295
+ excluded_columns: List[str],
296
296
  top_n: int = 10,
297
297
  id_column: Optional[str] = None,
298
298
  ) -> None:
@@ -301,7 +301,7 @@ class Visualizations:
301
301
 
302
302
  Parameters
303
303
  ----------
304
- excluded_vars : List[str]
304
+ excluded_columns : List[str]
305
305
  List of columns to exclude.
306
306
  top_n : int, optional
307
307
  Number of top categories to display, by default 10.
@@ -311,10 +311,10 @@ class Visualizations:
311
311
  high_car, _ = self._check_cardinality()
312
312
 
313
313
  if id_column:
314
- excluded_vars.append(id_column)
314
+ excluded_columns.append(id_column)
315
315
 
316
316
  # Exclude specified variables from the high cardinality list
317
- high_car = [cat for cat in high_car if cat not in excluded_vars]
317
+ high_car = [cat for cat in high_car if cat not in excluded_columns]
318
318
 
319
319
  if not high_car:
320
320
  print("No high cardinality features found.")
@@ -543,7 +543,7 @@ class Visualizations:
543
543
  return vif_data
544
544
 
545
545
  def _bin_and_target_freq(
546
- self, variable_name: str, n_bins: int = 10, scale: bool = False
546
+ self, variable_name: str, num_bins: int = 10, scale: bool = False
547
547
  ) -> pd.DataFrame:
548
548
  """
549
549
  Bin a numerical variable and calculate target frequency.
@@ -552,7 +552,7 @@ class Visualizations:
552
552
  ----------
553
553
  variable_name : str
554
554
  Name of the numerical variable.
555
- n_bins : int, optional
555
+ num_bins : int, optional
556
556
  Number of bins to use, by default 10.
557
557
  scale : bool, optional
558
558
  Whether to scale the variable before binning, by default False.
@@ -562,15 +562,15 @@ class Visualizations:
562
562
  pd.DataFrame
563
563
  DataFrame with binned data and target frequency.
564
564
  """
565
- data = self.data[[variable_name, self.target_column]].copy()
565
+ df = self.data[[variable_name, self.target_column]].copy()
566
566
 
567
567
  # Ensure target column is numeric (0 or 1)
568
- data[self.target_column] = data[self.target_column].astype(int)
568
+ df[self.target_column] = df[self.target_column].astype(int)
569
569
 
570
570
  # Drop missing values and inform user
571
- original_count = len(data)
572
- data.dropna(subset=[variable_name], inplace=True)
573
- dropped_count = original_count - len(data)
571
+ original_count = len(df)
572
+ df.dropna(subset=[variable_name], inplace=True)
573
+ dropped_count = original_count - len(df)
574
574
  if dropped_count > 0:
575
575
  print(
576
576
  f"Dropped {dropped_count} rows with missing values for {variable_name}"
@@ -578,11 +578,11 @@ class Visualizations:
578
578
 
579
579
  if scale:
580
580
  # Scale the variable individually
581
- min_val = data[variable_name].min()
582
- max_val = data[variable_name].max()
583
- if min_val != max_val:
584
- data[variable_name] = (data[variable_name] - min_val) / (
585
- max_val - min_val
581
+ min_value = df[variable_name].min()
582
+ max_value = df[variable_name].max()
583
+ if min_value != max_value:
584
+ df[variable_name] = (df[variable_name] - min_value) / (
585
+ max_value - min_value
586
586
  )
587
587
  else:
588
588
  print(
@@ -591,18 +591,18 @@ class Visualizations:
591
591
 
592
592
  # Use 'drop' option to handle duplicate bin edges
593
593
  try:
594
- data["bin"] = pd.qcut(
595
- data[variable_name], q=n_bins, labels=False, duplicates="drop"
594
+ df["bin"] = pd.qcut(
595
+ df[variable_name], q=num_bins, labels=False, duplicates="drop"
596
596
  )
597
597
  except ValueError as e:
598
598
  print(f"Warning: {e}")
599
599
  print(
600
- f"Attempting to create {n_bins} bins for {variable_name}, but some may be merged due to duplicate values."
600
+ f"Attempting to create {num_bins} bins for {variable_name}, but some may be merged due to duplicate values."
601
601
  )
602
602
  # If qcut fails, fall back to cut with equal-width bins
603
- data["bin"] = pd.cut(data[variable_name], bins=n_bins, labels=False)
603
+ df["bin"] = pd.cut(df[variable_name], bins=num_bins, labels=False)
604
604
 
605
- grouped = data.groupby("bin").agg(
605
+ grouped = df.groupby("bin").agg(
606
606
  {variable_name: "mean", self.target_column: ["count", "sum"]}
607
607
  )
608
608
  grouped.columns = PandasIndex([variable_name, "total_count", "target_sum"])
@@ -610,7 +610,7 @@ class Visualizations:
610
610
  return grouped.reset_index()
611
611
 
612
612
  def plot_hexagon_target_vs_numerical(
613
- self, var1: str, var2: str, n_bins: int = 20, scale: bool = False
613
+ self, var1: str, var2: str, num_bins: int = 20, scale: bool = False
614
614
  ) -> None:
615
615
  """
616
616
  Create a hexbin plot comparing two numerical variables and their relationship to the target variable.
@@ -621,7 +621,7 @@ class Visualizations:
621
621
  Name of the first numerical variable.
622
622
  var2 : str
623
623
  Name of the second numerical variable.
624
- n_bins : int, optional
624
+ num_bins : int, optional
625
625
  Number of bins to use, by default 20.
626
626
  scale : bool, optional
627
627
  Whether to scale variables before binning, by default False.
@@ -644,7 +644,7 @@ class Visualizations:
644
644
  data[var1],
645
645
  data[var2],
646
646
  C=data[self.target_column],
647
- gridsize=n_bins,
647
+ gridsize=num_bins,
648
648
  cmap="YlOrRd",
649
649
  reduce_C_function=np.mean,
650
650
  )
@@ -1203,46 +1203,3 @@ class Visualizations:
1203
1203
 
1204
1204
  if show_plot:
1205
1205
  fig.show()
1206
-
1207
-
1208
- '''
1209
- def plot_categorical_value_counts(
1210
- self, plot_type: Literal["bar", "pie"] = "bar"
1211
- ) -> None:
1212
- """
1213
- Generate value counts for categorical features and visualize them.
1214
-
1215
- Parameters
1216
- ----------
1217
- plot_type : {'bar', 'pie'}, optional
1218
- Type of plot to display, by default 'bar'.
1219
- """
1220
- value_counts = {
1221
- col: self.data[col].value_counts() for col in self.categorical_columns
1222
- }
1223
-
1224
- for col in self.categorical_columns:
1225
- counts = value_counts[col].reset_index()
1226
- counts.columns = PandasIndex([col, "Frequency"])
1227
-
1228
- if counts.empty:
1229
- print(f"No data to plot for column: {col}")
1230
- continue
1231
-
1232
- if plot_type == "bar":
1233
- fig = px.bar(
1234
- counts, x=col, y="Frequency", title=f"Distribution of {col}"
1235
- )
1236
- elif plot_type == "pie":
1237
- fig = px.pie(
1238
- counts,
1239
- names=col,
1240
- values="Frequency",
1241
- title=f"Distribution of {col}",
1242
- )
1243
- else:
1244
- raise ValueError("plot_type should be either 'bar' or 'pie'")
1245
-
1246
- fig.update_layout(title={"x": 0.5})
1247
- fig.show()
1248
- '''
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "classifier-toolkit"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = ""
5
5
  authors = [
6
6
  "gauthier.marquand <gauthier.marquand@qonto.com>",