classifier-toolkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. classifier_toolkit/eda/__init__.py +19 -0
  2. classifier_toolkit/eda/bivariate_analysis.py +413 -0
  3. classifier_toolkit/eda/eda_toolkit.py +549 -0
  4. classifier_toolkit/eda/feature_engineering.py +1310 -0
  5. classifier_toolkit/eda/first_glance.py +253 -0
  6. classifier_toolkit/eda/univariate_analysis.py +778 -0
  7. classifier_toolkit/eda/visualizations.py +1248 -0
  8. classifier_toolkit/eda/warnings/__init__.py +4 -0
  9. classifier_toolkit/eda/warnings/automated_warnings.py +20 -0
  10. classifier_toolkit/eda/warnings/default_warnings.py +286 -0
  11. classifier_toolkit/feature_selection/__init__.py +33 -0
  12. classifier_toolkit/feature_selection/base.py +182 -0
  13. classifier_toolkit/feature_selection/embedded_methods/__init__.py +7 -0
  14. classifier_toolkit/feature_selection/embedded_methods/elastic_net.py +178 -0
  15. classifier_toolkit/feature_selection/meta_selector.py +329 -0
  16. classifier_toolkit/feature_selection/utils/__init__.py +17 -0
  17. classifier_toolkit/feature_selection/utils/plottings.py +65 -0
  18. classifier_toolkit/feature_selection/utils/scoring.py +86 -0
  19. classifier_toolkit/feature_selection/wrapper_methods/__init__.py +11 -0
  20. classifier_toolkit/feature_selection/wrapper_methods/rfe.py +345 -0
  21. classifier_toolkit/feature_selection/wrapper_methods/sequential_selection.py +566 -0
  22. classifier_toolkit/model_fitting/__init__.py +0 -0
  23. classifier_toolkit/model_fitting/model_search.py +3 -0
  24. classifier_toolkit-0.1.0.dist-info/METADATA +99 -0
  25. classifier_toolkit-0.1.0.dist-info/RECORD +26 -0
  26. classifier_toolkit-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,549 @@
1
+ from datetime import date, datetime
2
+ from pathlib import Path
3
+ from typing import Any, Dict, List, Literal, Optional, Union
4
+
5
+ import pandas as pd
6
+
7
+ from examples.constants import PATH_DATA_PROCESSED
8
+
9
+ from .bivariate_analysis import BivariateAnalysis
10
+ from .feature_engineering import FeatureEngineering
11
+ from .first_glance import FirstGlance
12
+ from .univariate_analysis import UnivariateAnalysis
13
+ from .visualizations import Visualizations
14
+ from .warnings import EDAWarning, WarningSystem, get_default_warnings
15
+
16
+ # Define the type for the transformations list
17
+ TransformationType = List[Dict[str, Union[str, Dict[str, Any]]]]
18
+
19
+
20
+ class EDAToolkit:
21
+ @staticmethod
22
+ def save_intermediary(path: Path, interm: pd.DataFrame, fname: str):
23
+ """
24
+ Save an intermediary DataFrame to a CSV file.
25
+
26
+ Parameters
27
+ ----------
28
+ path : Path
29
+ The directory path where the CSV file will be saved.
30
+ interm : pd.DataFrame
31
+ The DataFrame to save.
32
+ fname : str
33
+ The file name for the CSV file (without extension).
34
+ """
35
+ if not path:
36
+ raise ValueError(
37
+ "Please make sure to specify the path in the constants file."
38
+ )
39
+ else:
40
+ path_to_save = f"{path}/df_{fname}.csv"
41
+ interm.to_csv(path_to_save, index=False)
42
+
43
+ def __init__(
44
+ self,
45
+ dataframe: pd.DataFrame,
46
+ can_be_negative: List[str],
47
+ target_column: Optional[str] = None,
48
+ exclusion: Optional[List[str]] = None,
49
+ numerical_columns: Optional[List[str]] = None,
50
+ categorical_columns: Optional[List[str]] = None,
51
+ id_column: Optional[str] = None,
52
+ to_be_enc: Optional[List[str]] = None,
53
+ warning_system: Optional[WarningSystem] = None,
54
+ ) -> None:
55
+ """
56
+ Initialize the EDAToolkit class with data and configuration settings.
57
+
58
+ Parameters
59
+ ----------
60
+ dataframe : pd.DataFrame
61
+ The dataset to be analyzed.
62
+ can_be_negative : List[str]
63
+ List of columns where negative values are permissible.
64
+ target_column : Optional[str], optional
65
+ The target column for analysis, by default None.
66
+ exclusion : Optional[List[str]], optional
67
+ List of columns to exclude from analysis, by default None.
68
+ numerical_columns : Optional[List[str]], optional
69
+ List of numerical columns in the data, by default None.
70
+ categorical_columns : Optional[List[str]], optional
71
+ List of categorical columns in the data, by default None.
72
+ id_column : Optional[str], optional
73
+ Column used as an identifier, by default None.
74
+ to_be_enc : Optional[List[str]], optional
75
+ List of columns to be encoded, by default None.
76
+ warning_system : Optional[WarningSystem], optional
77
+ The warning system to use, by default None.
78
+ """
79
+ self.dataframe = dataframe
80
+ self.target_column = target_column or ""
81
+ self.exclusion = exclusion or []
82
+ self.can_be_negative = can_be_negative
83
+ self.numerical_columns = numerical_columns or []
84
+ self.categorical_columns = categorical_columns or []
85
+ self.id_column = id_column or ""
86
+ self.to_be_enc = to_be_enc or []
87
+
88
+ # Initialize an empty list for transformations
89
+ self.transformations: TransformationType = []
90
+
91
+ # Initialize the warning system
92
+ default_warning_system = get_default_warnings(
93
+ df=self.dataframe,
94
+ target_column=self.target_column,
95
+ numerical_columns=self.numerical_columns,
96
+ can_be_negative=self.can_be_negative,
97
+ cardinality_exclusion=self.exclusion,
98
+ id_column=self.id_column,
99
+ )
100
+ self.warning_system = warning_system or WarningSystem(default_warning_system)
101
+
102
+ self.first_glance = FirstGlance(
103
+ self.dataframe,
104
+ self.numerical_columns,
105
+ self.categorical_columns,
106
+ exclusion,
107
+ id_column,
108
+ )
109
+
110
+ self.visualizations = Visualizations(
111
+ self.dataframe,
112
+ self.target_column,
113
+ self.numerical_columns,
114
+ self.categorical_columns,
115
+ )
116
+ self.univariate_analysis = UnivariateAnalysis(
117
+ self.dataframe, self.target_column, self.numerical_columns
118
+ )
119
+ self.bivariate_analysis = BivariateAnalysis(
120
+ self.dataframe, self.numerical_columns
121
+ )
122
+ self.feature_engineering = FeatureEngineering(
123
+ self.dataframe,
124
+ self.target_column,
125
+ self.numerical_columns,
126
+ self.categorical_columns,
127
+ self.to_be_enc,
128
+ self.transformations,
129
+ )
130
+
131
+ def get_transformations(
132
+ self, unwanted_features: Optional[List] = None
133
+ ) -> TransformationType:
134
+ """
135
+ Get the list of transformations applied to the data.
136
+
137
+ Parameters
138
+ ----------
139
+ unwanted_features : Optional[List], optional
140
+ List of features to exclude from the transformations, by default None.
141
+
142
+ Returns
143
+ -------
144
+ TransformationType
145
+ A list of transformations.
146
+ """
147
+ if unwanted_features:
148
+ for unwanted_feature in unwanted_features:
149
+ self.transformations = [
150
+ transform
151
+ for transform in self.transformations
152
+ if transform["feature"] != unwanted_feature
153
+ ]
154
+ return self.transformations
155
+
156
+ def register_custom_warning(self, name: str, warning_obj: EDAWarning):
157
+ """
158
+ Register a custom warning function.
159
+
160
+ Parameters
161
+ ----------
162
+ name : str
163
+ The name of the custom warning.
164
+ func : WarningFunction
165
+ The warning function to register.
166
+ """
167
+ self.warning_system.register_warning(name, warning_obj)
168
+
169
+ def print_warnings(self):
170
+ """
171
+ Print all warnings for the dataset.
172
+ """
173
+ warnings = self.warning_system.run_warnings()
174
+
175
+ for warning_name, warning_list in warnings.items():
176
+ if warning_list:
177
+ print(f"\n{warning_name.replace('_', ' ').title()} Warnings:")
178
+ print("-" * 50)
179
+
180
+ # Group warnings by type
181
+ grouped_warnings = {}
182
+ for warning in warning_list:
183
+ warning_type = warning["warning_type"]
184
+ if warning_type not in grouped_warnings:
185
+ grouped_warnings[warning_type] = []
186
+ grouped_warnings[warning_type].append(warning["message"])
187
+
188
+ for warning_type, messages in grouped_warnings.items():
189
+ print(f"{warning_type}:")
190
+ if len(messages) == 1 and ":" in messages[0]:
191
+ # For warnings with a single message containing a colon (like missing values)
192
+ header, values = messages[0].split(":", 1)
193
+ print(header.strip() + ":")
194
+ for item in values.strip().split(","):
195
+ print(f" {item.strip()}")
196
+ else:
197
+ # For other types of warnings
198
+ for message in messages:
199
+ print(f" {message}")
200
+ print("-" * 50)
201
+
202
+ def perform_checks(self):
203
+ """
204
+ Perform exploratory data analysis on the dataset.
205
+ """
206
+ self.first_glance.super_vision()
207
+ self.first_glance.check_duplicates()
208
+
209
+ def plot_target_evolution(self):
210
+ """
211
+ Plot the target balance and evolution over time.
212
+ """
213
+ self.visualizations.plot_target_balance()
214
+ self.visualizations.plot_target_evolution("scheduled_at", "min_balance_3m")
215
+
216
+ def plot_numerical_distributions(self, sub_plots: bool = False):
217
+ """
218
+ Plot distributions of numerical columns.
219
+
220
+ Parameters
221
+ ----------
222
+ sub_plots : bool, optional
223
+ If True, create subplots for each distribution, by default False.
224
+ """
225
+ self.univariate_analysis.plot_distributions(
226
+ self.numerical_columns, grouped=sub_plots
227
+ )
228
+
229
+ def plot_categorical_cardinalities(self):
230
+ """
231
+ Plot cardinalities of categorical columns.
232
+ """
233
+ self.visualizations.plot_categorical_value_counts()
234
+ self.visualizations.visualize_high_cardinality(
235
+ excluded_vars=self.exclusion, id_column=self.id_column
236
+ )
237
+ self.visualizations.visualize_low_cardinality()
238
+
239
+ def plot_bivariate_numnum(self, columns: Optional[List[str]] = None):
240
+ """
241
+ Plot bivariate analysis for numerical-numerical column pairs.
242
+
243
+ Parameters
244
+ ----------
245
+ columns : List[str], optional
246
+ List of columns to include in the analysis, by default all numerical columns.
247
+ """
248
+ if columns is None:
249
+ columns = []
250
+ self.bivariate_analysis.generate_correlation_heatmap(columns)
251
+
252
+ def plot_vif(self):
253
+ """
254
+ Plot Variance Inflation Factor (VIF) for numerical columns.
255
+ """
256
+ self.visualizations.plot_vif_failsafe()
257
+
258
+ def plot_numerical_repartitioning(
259
+ self, number_of_partitions: int, time_column: str
260
+ ):
261
+ """
262
+ Plot numerical feature repartitioning over time.
263
+
264
+ Parameters
265
+ ----------
266
+ number_of_partitions : int
267
+ Number of partitions to use for binning.
268
+ time_column : str
269
+ Name of the time column.
270
+ """
271
+ self.univariate_analysis.get_num_feature_repartition(
272
+ time_column, number_of_partitions
273
+ )
274
+
275
+ def print_psi(
276
+ 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,
284
+ ):
285
+ """
286
+ Print Population Stability Index (PSI) for numerical features.
287
+
288
+ Parameters
289
+ ----------
290
+ buckets : int
291
+ Number of buckets for binning.
292
+ 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
+ )
336
+
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()}"
343
+ )
344
+
345
+ def plot_num_cat_anova(self):
346
+ """
347
+ Plot ANOVA results for numerical-categorical column pairs.
348
+ """
349
+ fig, _ = self.bivariate_analysis.perform_anova_numeric_categorical(
350
+ cat_cols=self.categorical_columns
351
+ )
352
+ fig.show()
353
+
354
+ def plot_cat_cat_cramersv(self):
355
+ """
356
+ Plot Cramer's V for categorical-categorical column pairs.
357
+ """
358
+ self.bivariate_analysis.plot_pairwise_cramers_v(
359
+ categorical_features=self.categorical_columns
360
+ )
361
+
362
+ def plot_num_univariate(self, threshold_c: float, threshold_iv: float):
363
+ """
364
+ Plot univariate analysis for numerical columns.
365
+
366
+ Parameters
367
+ ----------
368
+ threshold_c : float
369
+ Threshold for Cramer's V filtering.
370
+ threshold_iv : float
371
+ Threshold for Information Value filtering.
372
+
373
+ Returns
374
+ -------
375
+ Tuple[List[str], List[str]]
376
+ Filtered variables based on Cramer's V and Information Value.
377
+ """
378
+ _, filtered_cramers = self.univariate_analysis.plot_cramers_v(
379
+ self.numerical_columns,
380
+ excluded_vars=self.exclusion,
381
+ filter_threshold=threshold_c,
382
+ )
383
+
384
+ _, filtered_iv = self.univariate_analysis.plot_information_value(
385
+ numerical_features=self.numerical_columns,
386
+ filter_threshold=threshold_iv,
387
+ target_column=self.target_column,
388
+ )
389
+ return filtered_cramers, filtered_iv
390
+
391
+ def impute_preview_normalization_numerics(
392
+ self,
393
+ imputation_method,
394
+ eliminated: Optional[List[str]] = None,
395
+ ):
396
+ """
397
+ Impute missing values and preview normalization for numerical columns.
398
+
399
+ Parameters
400
+ ----------
401
+ imputation_method : Literal["knn", "mean", "median", "most_frequent", "quantile"]
402
+ Method to use for imputation.
403
+ eliminated : Optional[List[str]], optional
404
+ List of columns to eliminate, by default None.
405
+ """
406
+ imputed = self.feature_engineering.handle_missing_values(imputation_method)
407
+ transforms = self.get_transformations(unwanted_features=eliminated)
408
+
409
+ EDAToolkit.save_intermediary(PATH_DATA_PROCESSED, imputed, "imputed")
410
+ print("Imputed dataframe saved in the processed directory.")
411
+
412
+ self.feature_engineering = FeatureEngineering(
413
+ imputed,
414
+ self.target_column,
415
+ self.numerical_columns,
416
+ self.to_be_enc,
417
+ transformations=transforms,
418
+ )
419
+
420
+ self.feature_engineering.diagnose_normality_transform()
421
+
422
+ def normalize_numericals(self, to_transformed):
423
+ """
424
+ Apply normality transformations to numerical columns.
425
+
426
+ Parameters
427
+ ----------
428
+ to_transformed : List[str]
429
+ List of columns to be transformed.
430
+ """
431
+ transformed = self.feature_engineering.apply_normality_transformations(
432
+ to_transformed
433
+ )
434
+ transforms = self.get_transformations()
435
+ EDAToolkit.save_intermediary(PATH_DATA_PROCESSED, transformed, "transformed")
436
+ print("Transformed dataframe saved in the processed directory.")
437
+
438
+ self.feature_engineering = FeatureEngineering(
439
+ transformed,
440
+ self.target_column,
441
+ self.numerical_columns,
442
+ self.to_be_enc,
443
+ transformations=transforms,
444
+ )
445
+
446
+ def process_low_cardinality_cols(
447
+ self,
448
+ handling_method: Literal[
449
+ "kmeans", "fixed_width", "exponential", "quantile", "equal_frequency"
450
+ ],
451
+ ):
452
+ """
453
+ Process low cardinality columns.
454
+
455
+ Parameters
456
+ ----------
457
+ handling_method : Literal["kmeans", "fixed_width", "exponential", "quantile", "equal_frequency"]
458
+ Method to use for handling low cardinality columns.
459
+ """
460
+ binned = self.feature_engineering.binnings(methods=handling_method)
461
+ EDAToolkit.save_intermediary(PATH_DATA_PROCESSED, binned, "binned")
462
+ print(
463
+ "Variables with low cardinality are treated and saved in the processed directory."
464
+ )
465
+
466
+ def process_high_cardinality(
467
+ self,
468
+ selected_cols,
469
+ handling_method: Literal["gathering", "woe", "perlich"],
470
+ ):
471
+ """
472
+ Process high cardinality columns.
473
+
474
+ Parameters
475
+ ----------
476
+ selected_cols : List[str]
477
+ List of columns to process.
478
+ handling_method : Literal["gathering", "woe", "perlich"]
479
+ Method to use for handling high cardinality columns.
480
+ """
481
+ high_card = (
482
+ self.feature_engineering.transform_and_plot_high_cardinality_histograms(
483
+ selected_cols, method=handling_method
484
+ )
485
+ )
486
+ if high_card is not None:
487
+ EDAToolkit.save_intermediary(
488
+ PATH_DATA_PROCESSED, high_card, "high_cardinality_treated"
489
+ )
490
+ print(
491
+ "High cardinality variables are treated and dataframe saved in the processed directory."
492
+ )
493
+ transforms = self.get_transformations()
494
+
495
+ self.feature_engineering = FeatureEngineering(
496
+ high_card,
497
+ self.target_column,
498
+ self.numerical_columns,
499
+ self.to_be_enc,
500
+ transformations=transforms,
501
+ )
502
+ else:
503
+ print("High cardinality treatment failed.")
504
+
505
+ def plot_target_frequency_cols(self, scaling: bool):
506
+ """
507
+ Plot target frequency for numerical columns.
508
+
509
+ Parameters
510
+ ----------
511
+ scaling : bool
512
+ If True, apply scaling to the columns before plotting.
513
+ """
514
+ self.visualizations.plot_numerical_target_frequency_line(scale=scaling)
515
+
516
+ def print_crosstab_freqs(self, num_rows):
517
+ """
518
+ Print cross-tabulation frequencies for categorical columns.
519
+
520
+ Parameters
521
+ ----------
522
+ num_rows : int
523
+ Number of rows to display in the output.
524
+ """
525
+ freqs = self.visualizations.calc_cat_target_freq()
526
+
527
+ print(freqs.head(n=num_rows))
528
+
529
+ def encode_categoricals(self, method):
530
+ """
531
+ Encode categorical columns.
532
+
533
+ Parameters
534
+ ----------
535
+ method : Literal["one_hot", "distribution", "woe", "catboost"]
536
+ Encoding method to use.
537
+ """
538
+ self.feature_engineering.encoding_categorical(method)
539
+
540
+ def winsorize_numericals(self, columns_to_transformed):
541
+ """
542
+ Apply winsorization to numerical columns.
543
+
544
+ Parameters
545
+ ----------
546
+ columns_to_transformed : List[str]
547
+ List of columns to transform.
548
+ """
549
+ self.feature_engineering.apply_winsorization(columns_to_transformed)