PyIAML 1.0.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 (279) hide show
  1. iaml/__init__.py +56 -0
  2. iaml/actionable.py +11 -0
  3. iaml/actionables/__init__.py +21 -0
  4. iaml/actionables/boosting/__init__.py +4 -0
  5. iaml/actionables/boosting/act_adaboost.py +59 -0
  6. iaml/actionables/cleaning/__init__.py +26 -0
  7. iaml/actionables/cleaning/act_categorical_imputer.py +124 -0
  8. iaml/actionables/cleaning/act_count_vectorizer.py +204 -0
  9. iaml/actionables/cleaning/act_drop_categorical_column.py +51 -0
  10. iaml/actionables/cleaning/act_drop_date_column.py +48 -0
  11. iaml/actionables/cleaning/act_drop_high_cardinality_categorical.py +337 -0
  12. iaml/actionables/cleaning/act_drop_numerical_column.py +75 -0
  13. iaml/actionables/cleaning/act_drop_textual_column.py +51 -0
  14. iaml/actionables/cleaning/act_encode_target_column.py +56 -0
  15. iaml/actionables/cleaning/act_frequency_encoder.py +127 -0
  16. iaml/actionables/cleaning/act_hashing_vectorizer.py +186 -0
  17. iaml/actionables/cleaning/act_knn_imputer.py +152 -0
  18. iaml/actionables/cleaning/act_mean_column.py +79 -0
  19. iaml/actionables/cleaning/act_mice.py +464 -0
  20. iaml/actionables/cleaning/act_missing_count_feature.py +109 -0
  21. iaml/actionables/cleaning/act_missing_indicator.py +124 -0
  22. iaml/actionables/cleaning/act_onehot.py +65 -0
  23. iaml/actionables/cleaning/act_ordinal_encoder.py +177 -0
  24. iaml/actionables/cleaning/act_rare_category_grouper.py +173 -0
  25. iaml/actionables/cleaning/act_simple_imputer.py +109 -0
  26. iaml/actionables/cleaning/act_split_date.py +68 -0
  27. iaml/actionables/cleaning/act_target_encoder.py +274 -0
  28. iaml/actionables/cleaning/act_text_normalizer.py +241 -0
  29. iaml/actionables/cleaning/act_tf_idf.py +80 -0
  30. iaml/actionables/cleaning/act_word2vec.py +150 -0
  31. iaml/actionables/features_precleaning/__init__.py +12 -0
  32. iaml/actionables/features_precleaning/act_coerce_numeric_strings.py +194 -0
  33. iaml/actionables/features_precleaning/act_date_converter.py +99 -0
  34. iaml/actionables/features_precleaning/act_drop_bad_quality_rows.py +77 -0
  35. iaml/actionables/features_precleaning/act_drop_duplicate_rows.py +131 -0
  36. iaml/actionables/features_precleaning/act_drop_high_missing_columns.py +94 -0
  37. iaml/actionables/features_precleaning/act_drop_id_like_columns.py +294 -0
  38. iaml/actionables/features_precleaning/act_normalize_column_names.py +157 -0
  39. iaml/actionables/features_precleaning/act_sentinel_to_na_n.py +270 -0
  40. iaml/actionables/features_precleaning/act_trim_space.py +79 -0
  41. iaml/actionables/features_preprocessing/__init__.py +18 -0
  42. iaml/actionables/features_preprocessing/act_cyclical_date_encoding.py +212 -0
  43. iaml/actionables/features_preprocessing/act_fast_ica.py +161 -0
  44. iaml/actionables/features_preprocessing/act_feature_agglomeration.py +90 -0
  45. iaml/actionables/features_preprocessing/act_k_bins_discretizer.py +207 -0
  46. iaml/actionables/features_preprocessing/act_k_means_features.py +296 -0
  47. iaml/actionables/features_preprocessing/act_kernel_pca.py +143 -0
  48. iaml/actionables/features_preprocessing/act_log_transformer.py +122 -0
  49. iaml/actionables/features_preprocessing/act_nystroem.py +100 -0
  50. iaml/actionables/features_preprocessing/act_pca.py +77 -0
  51. iaml/actionables/features_preprocessing/act_polynomial_features.py +86 -0
  52. iaml/actionables/features_preprocessing/act_power_transformer.py +106 -0
  53. iaml/actionables/features_preprocessing/act_quantile_transformer.py +114 -0
  54. iaml/actionables/features_preprocessing/act_rbf_sampler.py +88 -0
  55. iaml/actionables/features_preprocessing/act_select_percentile.py +112 -0
  56. iaml/actionables/features_preprocessing/act_sparse_random_projection.py +157 -0
  57. iaml/actionables/features_preprocessing/act_truncated_svd.py +137 -0
  58. iaml/actionables/features_selection/__init__.py +8 -0
  59. iaml/actionables/features_selection/act_permutation_importance_selector.py +421 -0
  60. iaml/actionables/features_selection/act_remove_high_correlated_column.py +70 -0
  61. iaml/actionables/features_selection/act_remove_low_variance_column.py +74 -0
  62. iaml/actionables/features_selection/act_rfe.py +214 -0
  63. iaml/actionables/features_selection/act_select_from_model.py +325 -0
  64. iaml/actionables/features_selection/act_select_k_best.py +181 -0
  65. iaml/actionables/features_selection/act_vif_selector.py +130 -0
  66. iaml/actionables/imbalance/__init__.py +10 -0
  67. iaml/actionables/imbalance/act_adasyn.py +150 -0
  68. iaml/actionables/imbalance/act_borderline_smote.py +171 -0
  69. iaml/actionables/imbalance/act_near_miss.py +158 -0
  70. iaml/actionables/imbalance/act_random_over_sampling.py +60 -0
  71. iaml/actionables/imbalance/act_random_under_sampler.py +135 -0
  72. iaml/actionables/imbalance/act_smote.py +162 -0
  73. iaml/actionables/imbalance/act_smote_tomek.py +182 -0
  74. iaml/actionables/imbalance/act_smoteenn.py +193 -0
  75. iaml/actionables/imbalance/act_tomek_links.py +138 -0
  76. iaml/actionables/normalize/__init__.py +6 -0
  77. iaml/actionables/normalize/act_max_abs_scaler.py +78 -0
  78. iaml/actionables/normalize/act_minmax_scaler.py +56 -0
  79. iaml/actionables/normalize/act_normalizer.py +95 -0
  80. iaml/actionables/normalize/act_robust_scaler.py +111 -0
  81. iaml/actionables/normalize/act_standard_scaler.py +55 -0
  82. iaml/actionables/predictors/__init__.py +6 -0
  83. iaml/actionables/predictors/_xgboost.py +16 -0
  84. iaml/actionables/predictors/classifier/__init__.py +26 -0
  85. iaml/actionables/predictors/classifier/act_bagging_classifier.py +113 -0
  86. iaml/actionables/predictors/classifier/act_bernoulli_nb.py +89 -0
  87. iaml/actionables/predictors/classifier/act_catboost_classifier.py +135 -0
  88. iaml/actionables/predictors/classifier/act_complement_nb.py +106 -0
  89. iaml/actionables/predictors/classifier/act_decision_tree_classifier.py +117 -0
  90. iaml/actionables/predictors/classifier/act_extra_trees_classifier.py +115 -0
  91. iaml/actionables/predictors/classifier/act_gaussian_nb.py +53 -0
  92. iaml/actionables/predictors/classifier/act_hist_gradient_boosting_classifier.py +144 -0
  93. iaml/actionables/predictors/classifier/act_knn.py +86 -0
  94. iaml/actionables/predictors/classifier/act_light_gbm_classifier.py +211 -0
  95. iaml/actionables/predictors/classifier/act_linear_discriminant_analysis.py +63 -0
  96. iaml/actionables/predictors/classifier/act_linear_svc.py +134 -0
  97. iaml/actionables/predictors/classifier/act_logistic_regression.py +92 -0
  98. iaml/actionables/predictors/classifier/act_mlp_classifier.py +107 -0
  99. iaml/actionables/predictors/classifier/act_multinomial_nb.py +76 -0
  100. iaml/actionables/predictors/classifier/act_passive_aggressive_classifier.py +141 -0
  101. iaml/actionables/predictors/classifier/act_quadratic_discriminant_analysis.py +72 -0
  102. iaml/actionables/predictors/classifier/act_randomforest.py +113 -0
  103. iaml/actionables/predictors/classifier/act_ridge_classifier.py +116 -0
  104. iaml/actionables/predictors/classifier/act_sgd_classifier.py +149 -0
  105. iaml/actionables/predictors/classifier/act_svm_svc.py +88 -0
  106. iaml/actionables/predictors/classifier/act_xgboost.py +111 -0
  107. iaml/actionables/predictors/regressor/__init__.py +27 -0
  108. iaml/actionables/predictors/regressor/act_ada_boost_regressor.py +75 -0
  109. iaml/actionables/predictors/regressor/act_ard_regression.py +95 -0
  110. iaml/actionables/predictors/regressor/act_catboost_regressor.py +134 -0
  111. iaml/actionables/predictors/regressor/act_decision_tree_regressor.py +111 -0
  112. iaml/actionables/predictors/regressor/act_elastic_net_regressor.py +109 -0
  113. iaml/actionables/predictors/regressor/act_extra_trees_regressor.py +113 -0
  114. iaml/actionables/predictors/regressor/act_gaussian_process_regressor.py +55 -0
  115. iaml/actionables/predictors/regressor/act_gboost_regressor.py +95 -0
  116. iaml/actionables/predictors/regressor/act_hist_gradient_boosting_regressor.py +105 -0
  117. iaml/actionables/predictors/regressor/act_huber_regressor.py +101 -0
  118. iaml/actionables/predictors/regressor/act_knn_regressor.py +86 -0
  119. iaml/actionables/predictors/regressor/act_lasso_regressor.py +103 -0
  120. iaml/actionables/predictors/regressor/act_light_gbm_regressor.py +201 -0
  121. iaml/actionables/predictors/regressor/act_linear_regression.py +43 -0
  122. iaml/actionables/predictors/regressor/act_mlp_regressor.py +104 -0
  123. iaml/actionables/predictors/regressor/act_poisson_regressor.py +111 -0
  124. iaml/actionables/predictors/regressor/act_quantile_regressor.py +87 -0
  125. iaml/actionables/predictors/regressor/act_randomforest_regressor.py +116 -0
  126. iaml/actionables/predictors/regressor/act_ransac_regressor.py +106 -0
  127. iaml/actionables/predictors/regressor/act_ridge_regressor.py +107 -0
  128. iaml/actionables/predictors/regressor/act_sgd_regressor.py +106 -0
  129. iaml/actionables/predictors/regressor/act_svm_svr.py +81 -0
  130. iaml/actionables/predictors/regressor/act_xgboost_regressor.py +97 -0
  131. iaml/actionables/predictors/survival/__init__.py +12 -0
  132. iaml/actionables/predictors/survival/act_aalen_additive_model.py +83 -0
  133. iaml/actionables/predictors/survival/act_cox.py +110 -0
  134. iaml/actionables/predictors/survival/act_coxnet_survival_analysis.py +134 -0
  135. iaml/actionables/predictors/survival/act_extra_survival_trees.py +101 -0
  136. iaml/actionables/predictors/survival/act_fast_survival_svm.py +102 -0
  137. iaml/actionables/predictors/survival/act_gradient_boosting_survival_analysis.py +93 -0
  138. iaml/actionables/predictors/survival/act_random_survival_forest.py +91 -0
  139. iaml/actionables/predictors/survival/act_survival_component_wise_gboost.py +80 -0
  140. iaml/actionables/predictors/survival/act_survival_tree.py +120 -0
  141. iaml/actionables/predictors/survival/act_survival_xgboost.py +9 -0
  142. iaml/actionables/predictors/survival/act_weibull_aft.py +230 -0
  143. iaml/cache.py +61 -0
  144. iaml/cache_keys.py +57 -0
  145. iaml/candidate.py +736 -0
  146. iaml/core_dispatcher.py +125 -0
  147. iaml/data_type.py +11 -0
  148. iaml/dataset.py +506 -0
  149. iaml/decorators/__init__.py +3 -0
  150. iaml/decorators/all.py +4 -0
  151. iaml/decorators/is_step.py +45 -0
  152. iaml/decorators/runner.py +100 -0
  153. iaml/explanation.py +112 -0
  154. iaml/iaml.py +1072 -0
  155. iaml/iaml_pipeline.py +600 -0
  156. iaml/logger.py +138 -0
  157. iaml/meta_explorer_step.py +62 -0
  158. iaml/meta_ordered_step.py +28 -0
  159. iaml/meta_partial_explorer_step.py +34 -0
  160. iaml/meta_singleton.py +24 -0
  161. iaml/metastep.py +211 -0
  162. iaml/metric.py +111 -0
  163. iaml/metric_plot.py +82 -0
  164. iaml/metrics/__init__.py +21 -0
  165. iaml/metrics/_classification.py +28 -0
  166. iaml/metrics/_survival_times.py +22 -0
  167. iaml/metrics/accuracy_metric.py +59 -0
  168. iaml/metrics/balanced_accuracy_metric.py +67 -0
  169. iaml/metrics/brier_score.py +90 -0
  170. iaml/metrics/classification_error_metric.py +66 -0
  171. iaml/metrics/concordance_index_ipcw.py +84 -0
  172. iaml/metrics/concordance_index_metric.py +67 -0
  173. iaml/metrics/cumulative_dynamic_auc.py +119 -0
  174. iaml/metrics/f1_score_metric.py +71 -0
  175. iaml/metrics/integrated_brier_score.py +98 -0
  176. iaml/metrics/integrated_brier_score_loss.py +41 -0
  177. iaml/metrics/mean_absolute_error_metric.py +46 -0
  178. iaml/metrics/mean_squared_error_metric.py +46 -0
  179. iaml/metrics/mean_squared_log_error_metric.py +49 -0
  180. iaml/metrics/median_absolute_error_metric.py +48 -0
  181. iaml/metrics/precision_metric.py +63 -0
  182. iaml/metrics/r2_score_metric.py +45 -0
  183. iaml/metrics/recall_metric.py +65 -0
  184. iaml/metrics/roc_auc_metric.py +50 -0
  185. iaml/metrics/specificity_metric.py +44 -0
  186. iaml/metrics/specificity_multiclass_metric.py +55 -0
  187. iaml/metrics/specificity_multilabel_metric.py +60 -0
  188. iaml/optimizers/__init__.py +5 -0
  189. iaml/optimizers/bayesian_optimizer.py +193 -0
  190. iaml/optimizers/genetic_optimizer.py +284 -0
  191. iaml/optimizers/optimizer.py +31 -0
  192. iaml/optimizers/random_optimizer.py +101 -0
  193. iaml/plot.py +138 -0
  194. iaml/plots/__init__.py +32 -0
  195. iaml/plots/bar_plot.py +141 -0
  196. iaml/plots/box_plot.py +166 -0
  197. iaml/plots/class_prediction_error_plot.py +37 -0
  198. iaml/plots/classification_report_plot.py +35 -0
  199. iaml/plots/confusion_matrix_plot.py +34 -0
  200. iaml/plots/correlation_heatmap_plot.py +201 -0
  201. iaml/plots/cumulative_hazard_plot.py +72 -0
  202. iaml/plots/density_plot.py +210 -0
  203. iaml/plots/histogram_plot.py +179 -0
  204. iaml/plots/kaplan_meier_comparison_plot.py +89 -0
  205. iaml/plots/line_plot.py +70 -0
  206. iaml/plots/missingness_heatmap_plot.py +203 -0
  207. iaml/plots/outlier_plot.py +217 -0
  208. iaml/plots/pair_plot.py +228 -0
  209. iaml/plots/precision_recall_curve_plot.py +86 -0
  210. iaml/plots/prediction_error_plot.py +34 -0
  211. iaml/plots/qq_plot.py +220 -0
  212. iaml/plots/residual_plot.py +38 -0
  213. iaml/plots/roc_dynamique_curve_plot.py +79 -0
  214. iaml/plots/rocauc_plot.py +96 -0
  215. iaml/plots/shap_plot.py +187 -0
  216. iaml/plots/target_distribution_plot.py +241 -0
  217. iaml/plots/violin_plot.py +206 -0
  218. iaml/predictor.py +139 -0
  219. iaml/reference.py +65 -0
  220. iaml/shared_cache.py +90 -0
  221. iaml/sklearn_preprocessor.py +74 -0
  222. iaml/splitters/__init__.py +3 -0
  223. iaml/splitters/kfold_splitter.py +32 -0
  224. iaml/splitters/random_splitter.py +26 -0
  225. iaml/stack.py +39 -0
  226. iaml/statistic.py +66 -0
  227. iaml/statistics/__init__.py +77 -0
  228. iaml/statistics/anova_statistic.py +80 -0
  229. iaml/statistics/cardinality_ratio_statistic.py +63 -0
  230. iaml/statistics/category_cooccurrence_statistic.py +79 -0
  231. iaml/statistics/chi_square_statistic.py +81 -0
  232. iaml/statistics/coef_variation_statistic.py +72 -0
  233. iaml/statistics/correlation_with_target.py +105 -0
  234. iaml/statistics/count.py +72 -0
  235. iaml/statistics/data_type_summary_statistic.py +74 -0
  236. iaml/statistics/duplicate_row_statistic.py +56 -0
  237. iaml/statistics/effect_size_statistic.py +129 -0
  238. iaml/statistics/entropy_statistic.py +69 -0
  239. iaml/statistics/event_rate_statistic.py +52 -0
  240. iaml/statistics/grouped_mean_statistic.py +60 -0
  241. iaml/statistics/iqr_statistic.py +66 -0
  242. iaml/statistics/kurtosis.py +50 -0
  243. iaml/statistics/mad_statistic.py +66 -0
  244. iaml/statistics/mean.py +61 -0
  245. iaml/statistics/median_statistic.py +61 -0
  246. iaml/statistics/minmax.py +60 -0
  247. iaml/statistics/missing_rate_statistic.py +62 -0
  248. iaml/statistics/mode.py +47 -0
  249. iaml/statistics/most_frequent_ratio.py +81 -0
  250. iaml/statistics/outlier_count_iqr_statistic.py +76 -0
  251. iaml/statistics/quantile.py +59 -0
  252. iaml/statistics/range.py +53 -0
  253. iaml/statistics/rare_category_rate.py +92 -0
  254. iaml/statistics/skewness.py +53 -0
  255. iaml/statistics/stdev.py +50 -0
  256. iaml/statistics/summary_table_statistic.py +60 -0
  257. iaml/statistics/time_by_group_statistic.py +83 -0
  258. iaml/statistics/time_summary_statistic.py +56 -0
  259. iaml/statistics/top_k_value_counts.py +68 -0
  260. iaml/statistics/unique_count_statistic.py +57 -0
  261. iaml/statistics/value_counts.py +63 -0
  262. iaml/statistics/variance.py +51 -0
  263. iaml/statistics/violin.py +63 -0
  264. iaml/step.py +600 -0
  265. iaml/step_cache.py +87 -0
  266. iaml/step_wrapper.py +79 -0
  267. iaml/timed_pool_executor.py +492 -0
  268. iaml/type_of_target.py +68 -0
  269. iaml/void_step.py +101 -0
  270. iaml/worker_manager.py +169 -0
  271. iaml/wrapper/__init__.py +4 -0
  272. iaml/wrapper/wrap_basic_gridsearch.py +68 -0
  273. iaml/wrapper/wrap_genetic_gridsearch.py +293 -0
  274. iaml/wrapper/wrap_iterative_gridsearch.py +399 -0
  275. pyiaml-1.0.0.dist-info/METADATA +802 -0
  276. pyiaml-1.0.0.dist-info/RECORD +279 -0
  277. pyiaml-1.0.0.dist-info/WHEEL +5 -0
  278. pyiaml-1.0.0.dist-info/licenses/LICENSE +674 -0
  279. pyiaml-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,181 @@
1
+ """[STEP] Select K Best Features."""
2
+ import textwrap
3
+
4
+ from collections.abc import Callable
5
+
6
+ import pandas as pd
7
+ from sklearn.feature_selection import SelectKBest, chi2, f_classif
8
+ from sklearn.feature_selection import mutual_info_classif, mutual_info_regression
9
+
10
+ from ...actionable import Actionable
11
+ from ...candidate import Candidate
12
+ from ...data_type import DataType
13
+ from ...dataset import Dataset
14
+ from ...decorators.all import is_step
15
+
16
+
17
+ @is_step('features_selection')
18
+ class ActSelectKBest(Actionable):
19
+ """[STEP] Select K Best Features."""
20
+
21
+ name: str = "Select K Best Features"
22
+ _usage: str = "Use when you want a fast univariate top-k filter instead of ActRFE or ActSelectFromModel. Applicable to numeric features with a labeled target; for continuous targets use mutual_info. Avoid when you need multivariate interactions, stability, or chi2 with negative values."
23
+ _description: str = textwrap.dedent('''\
24
+ Select the {k} best numeric features using {score_func} scoring.''')
25
+ _description_long: str = textwrap.dedent('''\
26
+ SelectKBest ranks features based on a scoring function and keeps only
27
+ the top k features. This step supports chi2, f_classif, and mutual_info
28
+ scoring to reduce dimensionality and keep the most informative
29
+ predictors for the target.''')
30
+
31
+ def __init__(self):
32
+ self.configuration = {
33
+ 'score_func': {
34
+ 'description': 'Score function used to rank features.',
35
+ 'default': 'f_classif',
36
+ 'categorical': ['chi2', 'f_classif', 'mutual_info']
37
+ },
38
+ 'k': {
39
+ 'description': 'Number of top features to keep.',
40
+ 'default': 10,
41
+ 'range': [1, 1000]
42
+ },
43
+ 'random_state': {
44
+ 'description': 'Random seed used for mutual_info scoring.',
45
+ 'default': 42
46
+ }
47
+ }
48
+
49
+ self.optimizable: bool = True
50
+ self.columns: list[str] = []
51
+ self.selected_columns: list[str] = []
52
+ self.columns_to_drop: list[str] = []
53
+ self.selector: SelectKBest | None = None
54
+ self._scores: dict[str, float] = {}
55
+
56
+ def _resolve_score_func(self, dataset: Dataset) -> Callable | None:
57
+ score_func = self.get_config('score_func')
58
+ if score_func == 'chi2':
59
+ return chi2
60
+ if score_func == 'f_classif':
61
+ return f_classif
62
+ if score_func == 'mutual_info':
63
+ random_state = self.get_config('random_state')
64
+ if dataset.type_of_target == 'continuous':
65
+ return lambda X, y: mutual_info_regression(
66
+ X, y, random_state=random_state
67
+ )
68
+ return lambda X, y: mutual_info_classif(
69
+ X, y, random_state=random_state
70
+ )
71
+ return None
72
+
73
+ def _resolve_k(self, max_features: int) -> int:
74
+ k_value = self.get_config('k')
75
+ try:
76
+ k_value = int(k_value)
77
+ except (TypeError, ValueError):
78
+ return 0
79
+
80
+ if max_features <= 0:
81
+ return 0
82
+
83
+ return max(1, min(k_value, max_features))
84
+
85
+ def fit(self, dataset: Dataset) -> Actionable:
86
+ self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
87
+ self.selected_columns = []
88
+ self.columns_to_drop = []
89
+ self.selector = None
90
+ self._scores = {}
91
+ self.explanations = []
92
+
93
+ if not self.columns or dataset.y is None:
94
+ return self
95
+ if dataset.X[self.columns].isna().any().any():
96
+ return self
97
+
98
+ k_value = self._resolve_k(len(self.columns))
99
+ if k_value < 1:
100
+ return self
101
+
102
+ score_func = self._resolve_score_func(dataset)
103
+ if score_func is None:
104
+ return self
105
+
106
+ if k_value != self.get_config('k'):
107
+ self.configure('k', k_value) # pylint: disable=too-many-function-args
108
+
109
+ self.selector = SelectKBest(score_func=score_func, k=k_value)
110
+ self.selector.fit(dataset.X[self.columns], dataset.y)
111
+
112
+ support = self.selector.get_support()
113
+ self.selected_columns = list(pd.Index(self.columns)[support])
114
+ self.columns_to_drop = list(pd.Index(self.columns)[~support])
115
+
116
+ scores = self.selector.scores_
117
+ if scores is not None:
118
+ self._scores = {
119
+ column: float(score) if score is not None else float('nan')
120
+ for column, score in zip(self.columns, scores)
121
+ }
122
+
123
+ if self.columns_to_drop:
124
+ for column in self.columns_to_drop:
125
+ score = self._scores.get(column)
126
+ if score is None or pd.isna(score):
127
+ self.explanations.append(
128
+ f"Dropped column **`{column}`** because it was not in "
129
+ f"the top **{k_value}** features."
130
+ )
131
+ else:
132
+ self.explanations.append(
133
+ f"Dropped column **`{column}`** because it was not in "
134
+ f"the top **{k_value}** features (score={score:.4f})."
135
+ )
136
+
137
+ return self
138
+
139
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
140
+ """Drop columns that were not selected.
141
+
142
+ :param pd.DataFrame X: DataFrame to transform.
143
+ :return: Transformed dataset.
144
+ """
145
+ if not self.columns_to_drop:
146
+ return X
147
+
148
+ drop_cols = [column for column in self.columns_to_drop if column in X.columns]
149
+ if not drop_cols:
150
+ return X
151
+ return X.drop(columns=drop_cols)
152
+
153
+ def suitable(self, dataset: Dataset) -> bool:
154
+ if dataset.y is None or dataset.type_of_target is None:
155
+ return False
156
+
157
+ columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
158
+ if not columns or dataset.X.empty:
159
+ return False
160
+ if dataset.X[columns].isna().any().any():
161
+ return False
162
+
163
+ if self._resolve_k(len(columns)) < 1:
164
+ return False
165
+
166
+ score_func = self.get_config('score_func')
167
+ if dataset.type_of_target == 'continuous':
168
+ return score_func == 'mutual_info'
169
+
170
+ if dataset.type_of_target not in [
171
+ 'binary', 'multiclass', 'multilabel-indicator'
172
+ ]:
173
+ return False
174
+
175
+ if score_func == 'chi2':
176
+ return not (dataset.X[columns] < 0).any().any()
177
+
178
+ return score_func in ['f_classif', 'mutual_info', 'chi2']
179
+
180
+ def priorize(self, candidate: Candidate = None) -> float:
181
+ return 0.5
@@ -0,0 +1,130 @@
1
+ """[STEP] VIF Selector"""
2
+ import textwrap
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from ...actionable import Actionable
8
+ from ...candidate import Candidate
9
+ from ...data_type import DataType
10
+ from ...dataset import Dataset
11
+ from ...decorators.all import is_step
12
+
13
+
14
+ @is_step('features_selection')
15
+ class ActVIFSelector(Actionable):
16
+ """[STEP] VIF Selector"""
17
+
18
+ name: str = 'VIF Selector'
19
+ _description: str = textwrap.dedent('''\
20
+ Drop numeric columns with VIF above {threshold}.''')
21
+ _usage: str = "Use when reducing multicollinearity among numeric predictors, as a simpler alternative to ActRFE or ActSelectFromModel. Applicable to numeric tabular data with correlated features. Avoid when features are non-numeric, too few columns, or you need target-driven selection."
22
+ _description_long: str = textwrap.dedent('''\
23
+ Variance Inflation Factor (VIF) measures multicollinearity among
24
+ numeric predictors. This step computes VIF from the correlation
25
+ matrix and removes columns whose VIF exceeds the configured
26
+ threshold to improve model stability.''')
27
+
28
+ def __init__(self):
29
+ self.configuration = {
30
+ 'threshold': {
31
+ 'description': 'Drop columns with VIF greater than this value.',
32
+ 'default': 10.0,
33
+ 'range': [1.0, 1000.0]
34
+ }
35
+ }
36
+ self.columns: list[str] = []
37
+ self.to_drop: list[str] = []
38
+ self.vif_scores: dict[str, float] = {}
39
+
40
+ def fit(self, dataset: Dataset) -> Actionable:
41
+ self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
42
+ self.to_drop = []
43
+ self.vif_scores = {}
44
+ self.explanations = []
45
+
46
+ if len(self.columns) < 2 or dataset.X.empty:
47
+ return self
48
+
49
+ self.to_drop, self.vif_scores = self.__get_columns(dataset)
50
+
51
+ threshold = self.get_config('threshold')
52
+ for column in self.to_drop:
53
+ vif_value = self.vif_scores.get(column)
54
+ if vif_value is None or not np.isfinite(vif_value):
55
+ message = (
56
+ f"Dropped column **`{column}`** because its VIF was not finite."
57
+ )
58
+ else:
59
+ message = (
60
+ f"Dropped column **`{column}`** because its VIF ({vif_value:.2f}) "
61
+ f"exceeded {threshold}."
62
+ )
63
+ self.explanations.append(message)
64
+
65
+ return self
66
+
67
+ def __get_columns(self, dataset: Dataset) -> tuple[list[str], dict[str, float]]:
68
+ columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
69
+ if len(columns) < 2:
70
+ return [], {}
71
+
72
+ X_numeric = dataset.X[columns]
73
+ if X_numeric.empty:
74
+ return [], {}
75
+
76
+ X_numeric = X_numeric.replace([np.inf, -np.inf], np.nan)
77
+ X_numeric = X_numeric.dropna(axis=0, how='any')
78
+ if X_numeric.shape[0] < 2:
79
+ return [], {}
80
+
81
+ variances = X_numeric.var(ddof=0)
82
+ eligible_columns = variances[variances > 0].index.tolist()
83
+ if len(eligible_columns) < 2:
84
+ return [], {}
85
+
86
+ corr = X_numeric[eligible_columns].corr()
87
+ if corr.empty:
88
+ return [], {}
89
+
90
+ inv_corr = np.linalg.pinv(corr.values)
91
+ vif_values = np.diag(inv_corr)
92
+ vif_values = np.maximum(vif_values, 1.0)
93
+
94
+ vifs = {
95
+ column: float(vif)
96
+ for column, vif in zip(eligible_columns, vif_values)
97
+ }
98
+
99
+ threshold = self.get_config('threshold')
100
+ to_drop = [
101
+ column for column, vif in vifs.items()
102
+ if not np.isfinite(vif) or vif > threshold
103
+ ]
104
+
105
+ return to_drop, vifs
106
+
107
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
108
+ """Drop columns with high VIF from the DataFrame.
109
+
110
+ :param pd.DataFrame X: The dataset to transform.
111
+ :return: Transformed dataset without high VIF columns.
112
+ """
113
+ if not self.to_drop:
114
+ return X
115
+
116
+ drop_cols = [column for column in self.to_drop if column in X.columns]
117
+ if not drop_cols:
118
+ return X
119
+
120
+ return X.drop(columns=drop_cols)
121
+
122
+ def priorize(self, candidate: Candidate = None) -> float:
123
+ return 0.5
124
+
125
+ def suitable(self, dataset: Dataset) -> bool:
126
+ columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
127
+ if len(columns) < 2 or dataset.X.empty:
128
+ return False
129
+
130
+ return bool(self.__get_columns(dataset)[0])
@@ -0,0 +1,10 @@
1
+ """Normalize and scaler Actionables"""
2
+ from .act_random_over_sampling import ActRandomOverSampling
3
+ from .act_smote import ActSMOTE
4
+ from .act_borderline_smote import ActBorderlineSMOTE
5
+ from .act_adasyn import ActADASYN
6
+ from .act_smoteenn import ActSMOTEENN
7
+ from .act_smote_tomek import ActSMOTETomek
8
+ from .act_random_under_sampler import ActRandomUnderSampler
9
+ from .act_tomek_links import ActTomekLinks
10
+ from .act_near_miss import ActNearMiss
@@ -0,0 +1,150 @@
1
+ """[STEP] ADASYN"""
2
+ import textwrap
3
+ from typing import Any
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from imblearn.over_sampling import ADASYN
8
+
9
+ from ...actionable import Actionable
10
+ from ...candidate import Candidate
11
+ from ...data_type import DataType
12
+ from ...dataset import Dataset
13
+ from ...decorators.all import is_step
14
+
15
+
16
+ @is_step('imbalance')
17
+ class ActADASYN(Actionable):
18
+ """[STEP] ADASYN"""
19
+
20
+ name: str = "ADASYN"
21
+ _description: str = textwrap.dedent('''\
22
+ ADASYN creates synthetic samples in hard to learn regions
23
+ to adaptively balance classes.''')
24
+ _description_long: str = textwrap.dedent('''\
25
+ ADASYN (Adaptive Synthetic Sampling) focuses on minority samples that are
26
+ difficult to learn. It generates more synthetic data where class overlap is
27
+ higher, improving decision boundaries while keeping runtime small.''')
28
+ _usage: str = "Use when numeric imbalance needs adaptive synthetic focus rather than ActSMOTE or ActRandomOverSampling. Applicable to binary or multiclass numeric features with sufficient minority samples. Avoid when categorical/text/date features exist or minority class is extremely small."
29
+ refs: list[dict[str, Any]] = [
30
+ {
31
+ 'year': 2008,
32
+ 'name': 'ADASYN: Adaptive Synthetic Sampling Approach for Imbalanced Learning',
33
+ 'authors': [
34
+ 'Haibo He',
35
+ 'Yang Bai',
36
+ 'Edwardo A. Garcia',
37
+ 'Sheng Ma'
38
+ ],
39
+ 'doi': 'https://doi.org/10.1109/IJCNN.2008.4633969',
40
+ 'publisher': 'IEEE International Joint Conference on Neural Networks'
41
+ }
42
+ ]
43
+
44
+ def __init__(self):
45
+ self.configuration = {
46
+ 'sampling_strategy': {
47
+ 'description': 'Sampling strategy to balance classes.',
48
+ 'default': 'auto',
49
+ 'categorical': ['minority', 'auto']
50
+ },
51
+ 'n_neighbors': {
52
+ 'description': 'Number of nearest neighbors used to create synthetic samples.',
53
+ 'default': 5,
54
+ 'range': [1, 20]
55
+ },
56
+ 'random_state': {
57
+ 'description': 'Random seed used for reproducibility.',
58
+ 'default': 42
59
+ }
60
+ }
61
+ self.resampler: ADASYN | None = None
62
+ self.categorical_columns: list[str] = []
63
+ self.numeric_columns: list[str] = []
64
+ self._effective_n_neighbors: int | None = None
65
+
66
+ def fit(self, dataset: Dataset) -> Actionable:
67
+ self.resampler = None
68
+ self.categorical_columns = dataset.get_columns_names_by_type(DataType.CATEGORICAL)
69
+ self.numeric_columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
70
+ self._effective_n_neighbors = None
71
+
72
+ if dataset.X.empty or dataset.type_of_target not in ['binary', 'multiclass']:
73
+ return self
74
+
75
+ if dataset.y is None or len(dataset.y) == 0:
76
+ return self
77
+
78
+ unsupported = dataset.get_columns_names_by_type(
79
+ [DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
80
+ )
81
+ if unsupported:
82
+ return self
83
+
84
+ if self.categorical_columns:
85
+ return self
86
+
87
+ if not self.numeric_columns:
88
+ return self
89
+
90
+ _, counts = np.unique(dataset.y, return_counts=True)
91
+ if len(counts) < 2:
92
+ return self
93
+
94
+ min_count = int(counts.min())
95
+ if min_count <= 1:
96
+ return self
97
+
98
+ max_neighbors = min_count - 1
99
+ n_neighbors = min(int(self.get_config('n_neighbors')), max_neighbors)
100
+ n_neighbors = max(1, n_neighbors)
101
+ self._effective_n_neighbors = n_neighbors
102
+
103
+ params = self.passthrough_parameters()
104
+ params['n_neighbors'] = n_neighbors
105
+
106
+ self.resampler = ADASYN(**params)
107
+ self.resampler.fit(dataset.X, dataset.y)
108
+ return self
109
+
110
+ def resample(self, X: pd.DataFrame, y: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
111
+ """Apply ADASYN.
112
+
113
+ :param pd.DataFrame X: Features to resample
114
+ :param pd.DataFrame y: Labels to resample
115
+ :return: Resampled X and y
116
+ """
117
+ if self.resampler is None:
118
+ return X, y
119
+ return self.resampler.fit_resample(X, y)
120
+
121
+ def priorize(self, candidate: Candidate = None) -> float:
122
+ if candidate is None or candidate.dataset.y is None:
123
+ return 0.0
124
+ y = candidate.dataset.y
125
+ if len(y) == 0:
126
+ return 0.0
127
+ _, counts = np.unique(y, return_counts=True)
128
+ if len(counts) < 2:
129
+ return 0.0
130
+ imbalance = 1.0 - (counts.min() / counts.max())
131
+ return float(min(1.0, max(0.0, imbalance)))
132
+
133
+ def suitable(self, dataset: Dataset) -> bool:
134
+ if dataset.type_of_target not in ['binary', 'multiclass']:
135
+ return False
136
+ if dataset.X.empty or dataset.y is None or len(dataset.y) == 0:
137
+ return False
138
+ if dataset.get_columns_names_by_type(DataType.CATEGORICAL):
139
+ return False
140
+ if not dataset.get_columns_names_by_type(DataType.NUMERIC):
141
+ return False
142
+ unsupported = dataset.get_columns_names_by_type(
143
+ [DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
144
+ )
145
+ if unsupported:
146
+ return False
147
+ _, counts = np.unique(dataset.y, return_counts=True)
148
+ if len(counts) < 2:
149
+ return False
150
+ return counts.min() > 1
@@ -0,0 +1,171 @@
1
+ """[STEP] Borderline SMOTE"""
2
+ import inspect
3
+ import textwrap
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from imblearn.over_sampling import BorderlineSMOTE
9
+
10
+ from ...actionable import Actionable
11
+ from ...candidate import Candidate
12
+ from ...data_type import DataType
13
+ from ...dataset import Dataset
14
+ from ...decorators.all import is_step
15
+
16
+
17
+ @is_step('imbalance')
18
+ class ActBorderlineSMOTE(Actionable):
19
+ """[STEP] Borderline SMOTE"""
20
+
21
+ name: str = "Borderline SMOTE"
22
+ _description: str = textwrap.dedent('''\
23
+ BorderlineSMOTE generates synthetic samples for minority points that
24
+ lie close to the decision boundary.''')
25
+ _description_long: str = textwrap.dedent('''\
26
+ BorderlineSMOTE is a SMOTE variant that targets minority samples in
27
+ the danger zone near the majority class. By focusing on boundary
28
+ samples, it can reduce noise introduced by oversampling safe regions.''')
29
+ _usage: str = "Use when minority points lie near the boundary and you want targeted oversampling vs ActSMOTE or ActRandomOverSampling. Applicable to numeric-only binary or multiclass datasets. Avoid when categorical/text/date features exist or the minority class is too small for neighbors."
30
+ refs: list[dict[str, Any]] = [
31
+ {
32
+ 'year': 2005,
33
+ 'name': 'Borderline-SMOTE: A New Over-Sampling Method in Imbalanced Data Sets Learning',
34
+ 'authors': [
35
+ 'Hui Han',
36
+ 'Wen-Yuan Wang',
37
+ 'Bing-Huan Mao'
38
+ ],
39
+ 'publisher': 'ICIC 2005, Lecture Notes in Computer Science Vol.3644 page 878--887'
40
+ }
41
+ ]
42
+
43
+ def __init__(self):
44
+ self.configuration = {
45
+ 'sampling_strategy': {
46
+ 'description': 'Sampling strategy to balance classes.',
47
+ 'default': 'minority',
48
+ 'categorical': ['minority', 'auto']
49
+ },
50
+ 'k_neighbors': {
51
+ 'description': 'Number of nearest neighbors used to create synthetic samples.',
52
+ 'default': 5,
53
+ 'range': [1, 20]
54
+ },
55
+ 'm_neighbors': {
56
+ 'description': 'Number of neighbors used to detect borderline samples.',
57
+ 'default': 10,
58
+ 'range': [1, 20]
59
+ },
60
+ 'kind': {
61
+ 'description': 'Borderline variant to use when supported by imbalanced-learn.',
62
+ 'default': 'borderline-1',
63
+ 'categorical': ['borderline-1', 'borderline-2'],
64
+ 'passthrough': False
65
+ },
66
+ 'random_state': {
67
+ 'description': 'Random seed used for reproducibility.',
68
+ 'default': 42
69
+ }
70
+ }
71
+ self.resampler: BorderlineSMOTE | None = None
72
+ self.categorical_columns: list[str] = []
73
+ self.numeric_columns: list[str] = []
74
+ self._effective_k_neighbors: int | None = None
75
+ self._effective_m_neighbors: int | None = None
76
+
77
+ def fit(self, dataset: Dataset) -> Actionable:
78
+ self.resampler = None
79
+ self.categorical_columns = dataset.get_columns_names_by_type(DataType.CATEGORICAL)
80
+ self.numeric_columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
81
+ self._effective_k_neighbors = None
82
+ self._effective_m_neighbors = None
83
+
84
+ if dataset.X.empty or dataset.type_of_target not in ['binary', 'multiclass']:
85
+ return self
86
+
87
+ if dataset.y is None or len(dataset.y) == 0:
88
+ return self
89
+
90
+ unsupported = dataset.get_columns_names_by_type(
91
+ [DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
92
+ )
93
+ if unsupported:
94
+ return self
95
+
96
+ if self.categorical_columns:
97
+ return self
98
+
99
+ if not self.numeric_columns:
100
+ return self
101
+
102
+ _, counts = np.unique(dataset.y, return_counts=True)
103
+ if len(counts) < 2:
104
+ return self
105
+
106
+ min_count = int(counts.min())
107
+ if min_count <= 1:
108
+ return self
109
+
110
+ max_k = min_count - 1
111
+ k_neighbors = min(int(self.get_config('k_neighbors')), max_k)
112
+ k_neighbors = max(1, k_neighbors)
113
+ self._effective_k_neighbors = k_neighbors
114
+
115
+ total_count = int(len(dataset.y))
116
+ max_m = max(1, total_count - 1)
117
+ m_neighbors = min(int(self.get_config('m_neighbors')), max_m)
118
+ m_neighbors = max(1, m_neighbors)
119
+ self._effective_m_neighbors = m_neighbors
120
+
121
+ params = self.passthrough_parameters()
122
+ params['k_neighbors'] = k_neighbors
123
+ params['m_neighbors'] = m_neighbors
124
+ if 'kind' in inspect.signature(BorderlineSMOTE).parameters:
125
+ params['kind'] = self.get_config('kind')
126
+
127
+ self.resampler = BorderlineSMOTE(**params)
128
+ self.resampler.fit(dataset.X, dataset.y)
129
+ return self
130
+
131
+ def resample(self, X: pd.DataFrame, y: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
132
+ """Apply Borderline SMOTE.
133
+
134
+ :param pd.DataFrame X: Features to resample
135
+ :param pd.DataFrame y: Labels to resample
136
+ :return: Resampled X and y
137
+ """
138
+ if self.resampler is None:
139
+ return X, y
140
+ return self.resampler.fit_resample(X, y)
141
+
142
+ def priorize(self, candidate: Candidate = None) -> float:
143
+ if candidate is None or candidate.dataset.y is None:
144
+ return 0.0
145
+ y = candidate.dataset.y
146
+ if len(y) == 0:
147
+ return 0.0
148
+ _, counts = np.unique(y, return_counts=True)
149
+ if len(counts) < 2:
150
+ return 0.0
151
+ imbalance = 1.0 - (counts.min() / counts.max())
152
+ return float(min(1.0, max(0.0, imbalance)))
153
+
154
+ def suitable(self, dataset: Dataset) -> bool:
155
+ if dataset.type_of_target not in ['binary', 'multiclass']:
156
+ return False
157
+ if dataset.X.empty or dataset.y is None or len(dataset.y) == 0:
158
+ return False
159
+ if dataset.get_columns_names_by_type(DataType.CATEGORICAL):
160
+ return False
161
+ if not dataset.get_columns_names_by_type(DataType.NUMERIC):
162
+ return False
163
+ unsupported = dataset.get_columns_names_by_type(
164
+ [DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
165
+ )
166
+ if unsupported:
167
+ return False
168
+ _, counts = np.unique(dataset.y, return_counts=True)
169
+ if len(counts) < 2:
170
+ return False
171
+ return counts.min() > 1