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,421 @@
1
+ """[STEP] Permutation Importance Selector."""
2
+ import textwrap
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
7
+ from sklearn.inspection import permutation_importance
8
+ from sklearn.linear_model import LinearRegression, LogisticRegression
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 ActPermutationImportanceSelector(Actionable):
19
+ """[STEP] Permutation Importance Selector."""
20
+
21
+ name: str = 'Permutation Importance Selector'
22
+ _description: str = textwrap.dedent('''\
23
+ Select numeric features with permutation importance above {threshold}
24
+ using a {estimator} estimator.''')
25
+ _description_long: str = textwrap.dedent('''\
26
+ Permutation importance measures the decrease in model performance when
27
+ a single feature's values are randomly shuffled. Features that cause
28
+ little or no drop in score are considered less informative and can be
29
+ removed to simplify the model.''')
30
+ _usage: str = 'Use when you need post-fit, model-agnostic importance on numeric features; compare ActSelectFromModel for embedded selection. Applicable to supervised numeric targets with a supported estimator. Avoid when data is tiny or you need filters like ActRemoveLowVarianceColumn.'
31
+
32
+ def __init__(self):
33
+ self.configuration = {
34
+ 'estimator': {
35
+ 'description': 'Base estimator used to compute permutation importance.',
36
+ 'default': 'tree',
37
+ 'categorical': ['linear', 'tree']
38
+ },
39
+ 'threshold': {
40
+ 'description': textwrap.dedent('''\
41
+ Importance threshold. Accepts "mean", "median", or a numeric
42
+ value.'''),
43
+ 'default': 'median'
44
+ },
45
+ 'max_features': {
46
+ 'description': 'Maximum number of features to keep (None for no limit).',
47
+ 'default': None
48
+ },
49
+ 'n_repeats': {
50
+ 'description': 'Number of permutations used to estimate importance.',
51
+ 'default': 5,
52
+ 'range': [1, 50]
53
+ },
54
+ 'scoring': {
55
+ 'description': 'Scoring metric ("auto" to use estimator.score).',
56
+ 'default': 'auto'
57
+ },
58
+ 'random_state': {
59
+ 'description': 'Random seed for estimator and permutations.',
60
+ 'default': 42
61
+ },
62
+ 'tree_n_estimators': {
63
+ 'description': 'Number of trees in the ensemble.',
64
+ 'default': 50,
65
+ 'range': [10, 500]
66
+ },
67
+ 'tree_max_depth': {
68
+ 'description': 'Maximum depth of each tree.',
69
+ 'default': 10,
70
+ 'range': [1, 100]
71
+ },
72
+ 'tree_min_samples_leaf': {
73
+ 'description': 'Minimum number of samples required at a leaf node.',
74
+ 'default': 1,
75
+ 'range': [1, 20]
76
+ }
77
+ }
78
+
79
+ self.optimizable: bool = True
80
+ self.columns: list[str] = []
81
+ self.selected_columns: list[str] = []
82
+ self.columns_to_drop: list[str] = []
83
+ self.importances: dict[str, float] = {}
84
+ self.importances_std: dict[str, float] = {}
85
+ self.threshold_value: float | None = None
86
+ self.estimator = None
87
+
88
+ def _resolve_positive_int(self, key: str) -> int | None:
89
+ value = self.get_config(key)
90
+ try:
91
+ value = int(value)
92
+ except (TypeError, ValueError):
93
+ return None
94
+ if value < 1:
95
+ return None
96
+ return value
97
+
98
+ def _resolve_max_depth(self) -> int | None:
99
+ value = self.get_config('tree_max_depth')
100
+ if value is None:
101
+ return None
102
+ if isinstance(value, str) and value.strip().lower() in ['none', '']:
103
+ return None
104
+ try:
105
+ value = int(value)
106
+ except (TypeError, ValueError):
107
+ return None
108
+ if value < 1:
109
+ return None
110
+ return value
111
+
112
+ def _resolve_max_features(self, n_features: int) -> int | None:
113
+ value = self.get_config('max_features')
114
+ if value is None:
115
+ return None
116
+ if isinstance(value, str) and value.strip().lower() in ['none', '']:
117
+ return None
118
+ try:
119
+ value = int(value)
120
+ except (TypeError, ValueError):
121
+ return None
122
+ if value < 1:
123
+ return None
124
+ return min(value, n_features)
125
+
126
+ def _resolve_n_repeats(self) -> int | None:
127
+ value = self.get_config('n_repeats')
128
+ try:
129
+ value = int(value)
130
+ except (TypeError, ValueError):
131
+ return None
132
+ if value < 1:
133
+ return None
134
+ return value
135
+
136
+ def _resolve_scoring(self) -> str | None:
137
+ scoring = self.get_config('scoring')
138
+ if scoring is None:
139
+ return None
140
+ if isinstance(scoring, str):
141
+ value = scoring.strip()
142
+ if value == '' or value.lower() in ['auto', 'none']:
143
+ return None
144
+ return value
145
+ return None
146
+
147
+ def _threshold_config_valid(self) -> bool:
148
+ threshold = self.get_config('threshold')
149
+ if threshold is None:
150
+ return True
151
+ if isinstance(threshold, str):
152
+ value = threshold.strip().lower()
153
+ if value in ['mean', 'median', 'none', '']:
154
+ return True
155
+ try:
156
+ float(value)
157
+ return True
158
+ except ValueError:
159
+ return False
160
+ try:
161
+ float(threshold)
162
+ return True
163
+ except (TypeError, ValueError):
164
+ return False
165
+
166
+ def _resolve_threshold(self, importances: np.ndarray) -> float | None:
167
+ threshold = self.get_config('threshold')
168
+ if threshold is None:
169
+ return None
170
+ if isinstance(threshold, str):
171
+ value = threshold.strip().lower()
172
+ if value in ['none', '']:
173
+ return None
174
+ if value == 'mean':
175
+ if importances.size == 0:
176
+ return None
177
+ return float(np.nanmean(importances))
178
+ if value == 'median':
179
+ if importances.size == 0:
180
+ return None
181
+ return float(np.nanmedian(importances))
182
+ try:
183
+ return float(value)
184
+ except ValueError:
185
+ return None
186
+ try:
187
+ return float(threshold)
188
+ except (TypeError, ValueError):
189
+ return None
190
+
191
+ def _build_estimator(self, dataset: Dataset):
192
+ estimator_type = self.get_config('estimator')
193
+ random_state = self.get_config('random_state')
194
+
195
+ if dataset.type_of_target in ['continuous', 'continuous-multioutput']:
196
+ if estimator_type == 'linear':
197
+ return LinearRegression()
198
+ if estimator_type == 'tree':
199
+ n_estimators = self._resolve_positive_int('tree_n_estimators')
200
+ min_samples_leaf = self._resolve_positive_int('tree_min_samples_leaf')
201
+ max_depth = self._resolve_max_depth()
202
+ if None in [n_estimators, min_samples_leaf]:
203
+ return None
204
+ return RandomForestRegressor(
205
+ n_estimators=n_estimators,
206
+ max_depth=max_depth,
207
+ min_samples_leaf=min_samples_leaf,
208
+ random_state=random_state,
209
+ n_jobs=1
210
+ )
211
+ return None
212
+
213
+ if dataset.type_of_target in ['binary', 'multiclass']:
214
+ if estimator_type == 'linear':
215
+ return LogisticRegression(
216
+ solver='liblinear',
217
+ max_iter=1000,
218
+ random_state=random_state,
219
+ multi_class='ovr'
220
+ )
221
+ if estimator_type == 'tree':
222
+ n_estimators = self._resolve_positive_int('tree_n_estimators')
223
+ min_samples_leaf = self._resolve_positive_int('tree_min_samples_leaf')
224
+ max_depth = self._resolve_max_depth()
225
+ if None in [n_estimators, min_samples_leaf]:
226
+ return None
227
+ return RandomForestClassifier(
228
+ n_estimators=n_estimators,
229
+ max_depth=max_depth,
230
+ min_samples_leaf=min_samples_leaf,
231
+ random_state=random_state,
232
+ n_jobs=1
233
+ )
234
+ return None
235
+
236
+ return None
237
+
238
+ def fit(self, dataset: Dataset) -> Actionable:
239
+ self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
240
+ self.selected_columns = []
241
+ self.columns_to_drop = []
242
+ self.importances = {}
243
+ self.importances_std = {}
244
+ self.threshold_value = None
245
+ self.estimator = None
246
+ self.explanations = []
247
+
248
+ if dataset.y is None or dataset.type_of_target is None:
249
+ return self
250
+
251
+ if dataset.type_of_target == 'survival':
252
+ return self
253
+
254
+ if not self.columns or dataset.X.empty:
255
+ return self
256
+
257
+ estimator = self._build_estimator(dataset)
258
+ if estimator is None:
259
+ return self
260
+
261
+ n_repeats = self._resolve_n_repeats()
262
+ if n_repeats is None:
263
+ return self
264
+
265
+ scoring = self._resolve_scoring()
266
+
267
+ max_features = self._resolve_max_features(len(self.columns))
268
+ if max_features is not None and max_features != self.get_config('max_features'):
269
+ self.configure('max_features', max_features) # pylint: disable=too-many-function-args
270
+
271
+ X = dataset.X[self.columns]
272
+ estimator.fit(X, dataset.y)
273
+ self.estimator = estimator
274
+
275
+ result = permutation_importance(
276
+ estimator,
277
+ X,
278
+ dataset.y,
279
+ scoring=scoring,
280
+ n_repeats=n_repeats,
281
+ random_state=self.get_config('random_state'),
282
+ n_jobs=1
283
+ )
284
+
285
+ importances = np.asarray(result.importances_mean)
286
+ stds = np.asarray(result.importances_std)
287
+
288
+ self.importances = {
289
+ column: float(value)
290
+ for column, value in zip(self.columns, importances)
291
+ }
292
+ if stds.size:
293
+ self.importances_std = {
294
+ column: float(value)
295
+ for column, value in zip(self.columns, stds)
296
+ }
297
+
298
+ threshold_value = self._resolve_threshold(importances)
299
+ if threshold_value is not None and not np.isfinite(threshold_value):
300
+ threshold_value = None
301
+ self.threshold_value = threshold_value
302
+
303
+ if threshold_value is None:
304
+ candidate_mask = np.ones(len(self.columns), dtype=bool)
305
+ else:
306
+ candidate_mask = np.isfinite(importances) & (importances > threshold_value)
307
+
308
+ candidate_indices = np.where(candidate_mask)[0].tolist()
309
+ selected_indices = candidate_indices
310
+
311
+ if max_features is not None:
312
+ ranking = np.where(np.isfinite(importances), importances, -np.inf)
313
+ order = np.argsort(ranking)[::-1]
314
+ order = [idx for idx in order if candidate_mask[idx]]
315
+ selected_indices = list(order[:max_features])
316
+
317
+ selected_indices = sorted(selected_indices)
318
+ self.selected_columns = list(pd.Index(self.columns)[selected_indices])
319
+ selected_set = set(self.selected_columns)
320
+ self.columns_to_drop = [col for col in self.columns if col not in selected_set]
321
+
322
+ if self.columns_to_drop:
323
+ threshold_display = None
324
+ threshold_config = self.get_config('threshold')
325
+ if threshold_value is not None:
326
+ if isinstance(threshold_config, str) and \
327
+ threshold_config.strip().lower() in ['mean', 'median']:
328
+ threshold_display = f"{threshold_config} ({threshold_value:.6g})"
329
+ else:
330
+ threshold_display = f"{threshold_value:.6g}"
331
+
332
+ top_limit = max_features
333
+ top_set = set(self.selected_columns)
334
+
335
+ for column in self.columns_to_drop:
336
+ importance = self.importances.get(column)
337
+ if importance is None or not np.isfinite(importance):
338
+ if threshold_value is not None:
339
+ message = (
340
+ f"Dropped column **`{column}`** because its permutation "
341
+ "importance was not finite."
342
+ )
343
+ elif top_limit is not None:
344
+ message = (
345
+ f"Dropped column **`{column}`** because it was not in "
346
+ f"the top **{top_limit}** features by permutation importance."
347
+ )
348
+ else:
349
+ message = (
350
+ f"Dropped column **`{column}`** because it was not selected "
351
+ "by permutation importance."
352
+ )
353
+ else:
354
+ if threshold_value is not None and importance <= threshold_value:
355
+ message = (
356
+ f"Dropped column **`{column}`** because its permutation "
357
+ f"importance ({importance:.6g}) was below the threshold "
358
+ f"({threshold_display})."
359
+ )
360
+ elif top_limit is not None and column not in top_set:
361
+ message = (
362
+ f"Dropped column **`{column}`** because it was not in "
363
+ f"the top **{top_limit}** features by permutation importance "
364
+ f"({importance:.6g})."
365
+ )
366
+ else:
367
+ message = (
368
+ f"Dropped column **`{column}`** because it was not selected "
369
+ f"by permutation importance ({importance:.6g})."
370
+ )
371
+ self.explanations.append(message)
372
+
373
+ return self
374
+
375
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
376
+ """Drop columns that were not selected.
377
+
378
+ :param pd.DataFrame X: DataFrame to transform.
379
+ :return: Transformed dataset.
380
+ """
381
+ if not self.columns_to_drop:
382
+ return X
383
+
384
+ drop_cols = [column for column in self.columns_to_drop if column in X.columns]
385
+ if not drop_cols:
386
+ return X
387
+
388
+ return X.drop(columns=drop_cols)
389
+
390
+ def suitable(self, dataset: Dataset) -> bool:
391
+ if dataset.y is None or dataset.type_of_target is None:
392
+ return False
393
+
394
+ if dataset.type_of_target == 'survival':
395
+ return False
396
+
397
+ columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
398
+ if not columns or dataset.X.empty:
399
+ return False
400
+
401
+ if self._build_estimator(dataset) is None:
402
+ return False
403
+
404
+ if self._resolve_n_repeats() is None:
405
+ return False
406
+
407
+ if not self._threshold_config_valid():
408
+ return False
409
+
410
+ threshold = self.get_config('threshold')
411
+ max_features = self._resolve_max_features(len(columns))
412
+ if (threshold is None or (
413
+ isinstance(threshold, str)
414
+ and threshold.strip().lower() in ['none', '']
415
+ )) and max_features is None:
416
+ return False
417
+
418
+ return True
419
+
420
+ def priorize(self, candidate: Candidate = None) -> float:
421
+ return 0.5
@@ -0,0 +1,70 @@
1
+ """[STEP] Remove High Correlated Column"""
2
+ import textwrap
3
+ import numpy as np
4
+ import pandas as pd
5
+ from ...actionable import Actionable
6
+ from ...dataset import Dataset
7
+ from ...candidate import Candidate
8
+ from ...decorators.all import is_step
9
+
10
+ @is_step('features_selection')
11
+ class ActRemoveHighCorrelatedColumn(Actionable):
12
+ """[STEP] Remove High Correlated Columns"""
13
+
14
+ name: str = "Remove High Correlated Columns"
15
+ _description: str = "Remove columns that have high correlation with each other"
16
+ _usage: str = "Use when you need quick unsupervised pruning of highly correlated numeric features vs ActVIFSelector. Applicable to tabular data with continuous or ordinal columns. Avoid when labels should drive selection (use ActSelectKBest) or features are mostly categorical or nonlinear."
17
+ _description_long: str = textwrap.dedent('''\
18
+ Removing high correlated columns helps to reduce multicollinearity
19
+ in a dataset. When two columns are highly correlated, they contain
20
+ redundant information. By removing one of them, we can simplify the
21
+ model and improve its performance. This step typically involves
22
+ computing a correlation matrix and removing columns that have a
23
+ correlation above {threshold:.0%}.
24
+ ''')
25
+
26
+ def __init__(self):
27
+ self.configuration = {
28
+ 'threshold': {
29
+ 'description': textwrap.dedent('''\
30
+ If two columns is correlated over this value, only one
31
+ will be kept.'''),
32
+ 'default': 0.9
33
+ }
34
+ }
35
+ self.to_drop: list[str] = None
36
+
37
+ def fit(self, dataset: Dataset) -> Actionable:
38
+ self.to_drop, corr = self.__get_columns(dataset)
39
+
40
+ self.explanations = [
41
+ f"""Dropped column **`{c}`** because it was too correlated with
42
+ {", ".join([ f"**`{i}`**" for i in corr[c] ])}."""
43
+ for c in self.to_drop
44
+ ]
45
+
46
+ return self
47
+
48
+ def __get_columns(self, dataset: Dataset) -> list:
49
+ # Compute correlation matrix
50
+ corr_matrix = dataset.X.corr().abs()
51
+ upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool_))
52
+
53
+ # Find features with above-threshold correlation
54
+ corr = { c: (upper[upper[c] >= self.get_config('threshold')].index) for c in upper.columns }
55
+
56
+ return ([ c for c, v in corr.items() if len(v) > 0 ], corr)
57
+
58
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
59
+ """Drop high correlated column
60
+
61
+ :param pd.DataFrame X: DataFrame to transform
62
+ :return: Transformed dataset
63
+ """
64
+ return X.drop(self.to_drop, axis=1)
65
+
66
+ def priorize(self, candidate: Candidate = None) -> float:
67
+ return 0.5
68
+
69
+ def suitable(self, dataset: Dataset) -> bool:
70
+ return self.__get_columns(dataset)[0]
@@ -0,0 +1,74 @@
1
+ """[STEP] Remove Low Variance Column"""
2
+ import textwrap
3
+ from sklearn.feature_selection import VarianceThreshold
4
+ import pandas as pd
5
+ from ...actionable import Actionable
6
+ from ...dataset import Dataset
7
+ from ...candidate import Candidate
8
+ from ...decorators.all import is_step
9
+
10
+ @is_step('features_selection')
11
+ class ActRemoveLowVarianceColumn(Actionable):
12
+ """[STEP] Remove Low Variance Column"""
13
+
14
+ name: str = 'Remove Low Variance Column'
15
+ _description: str = 'Remove columns with variance lower than the threshold.'
16
+ _description_long: str = textwrap.dedent('''\
17
+ Remove features from the dataset that have variance lower than
18
+ the specified threshold. Low variance columns do not contribute
19
+ significantly to the predictive power of models and can lead to
20
+ overfitting.''')
21
+ _usage: str = 'Use when you need a fast filter for near-constant features. Applicable to numeric or encoded features; for supervised use ActSelectKBest or ActSelectFromModel. Avoid when scaling changes variance meaning or you want redundancy control like ActRemoveHighCorrelatedColumn.'
22
+
23
+ def __init__(self):
24
+ self.configuration = {
25
+ 'threshold': {
26
+ 'description': 'Columns with variance lower than this value will be dropped.',
27
+ 'default': 1e-10
28
+ }
29
+ }
30
+ self.selector: VarianceThreshold = None
31
+ self.to_drop: list[str] = None
32
+
33
+ def fit(self, dataset: Dataset) -> Actionable:
34
+ threshold_value = self.get_config('threshold')
35
+
36
+ # Identify the columns that are being dropped (features with low variance)
37
+ self.to_drop = self.__get_columns(dataset)
38
+
39
+ # Create explanations for each dropped feature
40
+ self.explanations = [
41
+ f"""Dropped column **`{col}`** because its variance was too low \
42
+ (below threshold {threshold_value})."""
43
+ for col in self.to_drop
44
+ ]
45
+
46
+ return self
47
+
48
+ def __get_columns(self, dataset: Dataset) -> list:
49
+ # Set up VarianceThreshold selector with the user-defined threshold
50
+ threshold_value = self.get_config('threshold')
51
+ selector = VarianceThreshold(threshold=threshold_value)
52
+
53
+ # Apply selector to dataset to identify features to keep
54
+ selector.fit(dataset.X)
55
+
56
+ # Get the boolean mask of features to keep (features with sufficient variance)
57
+ feature_mask = selector.get_support()
58
+
59
+ # Identify the columns that are being dropped (features with low variance)
60
+ return list(dataset.X.columns[~feature_mask])
61
+
62
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
63
+ """Drop low variance columns from the DataFrame.
64
+
65
+ :param pd.DataFrame X: The dataset to transform
66
+ :return: Transformed dataset without low variance columns
67
+ """
68
+ return X.drop(columns=self.to_drop)
69
+
70
+ def priorize(self, candidate: Candidate = None) -> float:
71
+ return 0.5
72
+
73
+ def suitable(self, dataset: Dataset) -> bool:
74
+ return dataset.type_of_target == 'survival' and self.__get_columns(dataset)