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,214 @@
1
+ """[STEP] Recursive Feature Elimination."""
2
+ import textwrap
3
+
4
+ import pandas as pd
5
+ from sklearn.feature_selection import RFE
6
+ from sklearn.linear_model import LinearRegression, LogisticRegression
7
+ from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
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('features_selection')
17
+ class ActRFE(Actionable):
18
+ """[STEP] Recursive Feature Elimination."""
19
+
20
+ name: str = 'Recursive Feature Elimination'
21
+ _usage: str = "Use when you need elimination to keep a set count of numeric features vs ActSelectKBest. Applicable to regression/classification with numeric inputs and linear/tree estimators. Avoid when feature count is huge or you need a fast filter; prefer ActRemoveLowVarianceColumn."
22
+ _description: str = textwrap.dedent('''\
23
+ Select {n_features_to_select} numeric features using RFE with
24
+ a {estimator} estimator.''')
25
+ _description_long: str = textwrap.dedent('''\
26
+ Recursive Feature Elimination (RFE) trains a base estimator and
27
+ removes the least important features at each iteration. This step
28
+ supports linear and tree estimators to rank features and keep only
29
+ the most relevant predictors.''')
30
+
31
+ def __init__(self):
32
+ self.configuration = {
33
+ 'estimator': {
34
+ 'description': 'Base estimator type used to rank features.',
35
+ 'default': 'linear',
36
+ 'categorical': ['linear', 'tree']
37
+ },
38
+ 'n_features_to_select': {
39
+ 'description': 'Number of numeric features to keep.',
40
+ 'default': 10,
41
+ 'range': [1, 1000]
42
+ },
43
+ 'step': {
44
+ 'description': 'Fraction of features removed at each iteration.',
45
+ 'default': 0.2,
46
+ 'range': [0.05, 1.0]
47
+ },
48
+ 'random_state': {
49
+ 'description': 'Random seed for the underlying estimator.',
50
+ 'default': 42
51
+ }
52
+ }
53
+
54
+ self.optimizable: bool = True
55
+ self.columns: list[str] = []
56
+ self.selected_columns: list[str] = []
57
+ self.columns_to_drop: list[str] = []
58
+ self.rankings: dict[str, int] = {}
59
+ self.selector: RFE | None = None
60
+
61
+ def _resolve_n_features_to_select(self, n_features: int) -> int:
62
+ value = self.get_config('n_features_to_select')
63
+ try:
64
+ value = int(value)
65
+ except (TypeError, ValueError):
66
+ return 0
67
+
68
+ if n_features <= 0:
69
+ return 0
70
+
71
+ return max(1, min(value, n_features))
72
+
73
+ def _resolve_step(self) -> float | int | None:
74
+ step_value = self.get_config('step')
75
+ try:
76
+ step_value = float(step_value)
77
+ except (TypeError, ValueError):
78
+ return None
79
+
80
+ if step_value <= 0:
81
+ return None
82
+
83
+ if step_value >= 1:
84
+ return int(step_value)
85
+
86
+ return step_value
87
+
88
+ def _build_estimator(self, dataset: Dataset):
89
+ estimator_type = self.get_config('estimator')
90
+ random_state = self.get_config('random_state')
91
+
92
+ if dataset.type_of_target in ['continuous', 'continuous-multioutput']:
93
+ if estimator_type == 'linear':
94
+ return LinearRegression()
95
+ if estimator_type == 'tree':
96
+ return DecisionTreeRegressor(random_state=random_state)
97
+ return None
98
+
99
+ if dataset.type_of_target in ['binary', 'multiclass']:
100
+ if estimator_type == 'linear':
101
+ return LogisticRegression(
102
+ solver='liblinear',
103
+ max_iter=1000,
104
+ random_state=random_state
105
+ )
106
+ if estimator_type == 'tree':
107
+ return DecisionTreeClassifier(random_state=random_state)
108
+ return None
109
+
110
+ return None
111
+
112
+ def fit(self, dataset: Dataset) -> Actionable:
113
+ self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
114
+ self.selected_columns = []
115
+ self.columns_to_drop = []
116
+ self.rankings = {}
117
+ self.selector = None
118
+ self.explanations = []
119
+
120
+ if dataset.y is None or dataset.type_of_target is None:
121
+ return self
122
+
123
+ if not self.columns:
124
+ return self
125
+
126
+ n_select = self._resolve_n_features_to_select(len(self.columns))
127
+ if n_select < 1:
128
+ return self
129
+
130
+ if n_select >= len(self.columns):
131
+ self.selected_columns = list(self.columns)
132
+ return self
133
+
134
+ estimator = self._build_estimator(dataset)
135
+ if estimator is None:
136
+ return self
137
+
138
+ step_value = self._resolve_step()
139
+ if step_value is None:
140
+ return self
141
+
142
+ if n_select != self.get_config('n_features_to_select'):
143
+ self.configure('n_features_to_select', n_select) # pylint: disable=too-many-function-args
144
+
145
+ self.selector = RFE(
146
+ estimator=estimator,
147
+ n_features_to_select=n_select,
148
+ step=step_value
149
+ )
150
+ self.selector.fit(dataset.X[self.columns], dataset.y)
151
+
152
+ support = self.selector.get_support()
153
+ self.selected_columns = list(pd.Index(self.columns)[support])
154
+ self.columns_to_drop = list(pd.Index(self.columns)[~support])
155
+
156
+ ranking = getattr(self.selector, 'ranking_', None)
157
+ if ranking is not None:
158
+ self.rankings = {
159
+ column: int(rank) for column, rank in zip(self.columns, ranking)
160
+ }
161
+
162
+ for column in self.columns_to_drop:
163
+ rank = self.rankings.get(column)
164
+ if rank is None:
165
+ self.explanations.append(
166
+ f"Dropped column **`{column}`** because it was not selected by RFE."
167
+ )
168
+ else:
169
+ self.explanations.append(
170
+ f"Dropped column **`{column}`** because RFE ranked it **{rank}** "
171
+ f"(kept top **{n_select}**)."
172
+ )
173
+
174
+ return self
175
+
176
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
177
+ """Drop columns that were not selected.
178
+
179
+ :param pd.DataFrame X: DataFrame to transform.
180
+ :return: Transformed dataset.
181
+ """
182
+ if not self.columns_to_drop:
183
+ return X
184
+
185
+ drop_cols = [column for column in self.columns_to_drop if column in X.columns]
186
+ if not drop_cols:
187
+ return X
188
+ return X.drop(columns=drop_cols)
189
+
190
+ def suitable(self, dataset: Dataset) -> bool:
191
+ if dataset.y is None or dataset.type_of_target is None:
192
+ return False
193
+
194
+ if dataset.type_of_target == 'survival':
195
+ return False
196
+
197
+ columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
198
+ if not columns or dataset.X.empty:
199
+ return False
200
+
201
+ n_select = self._resolve_n_features_to_select(len(columns))
202
+ if n_select < 1 or n_select >= len(columns):
203
+ return False
204
+
205
+ if self._build_estimator(dataset) is None:
206
+ return False
207
+
208
+ if self._resolve_step() is None:
209
+ return False
210
+
211
+ return True
212
+
213
+ def priorize(self, candidate: Candidate = None) -> float:
214
+ return 0.5
@@ -0,0 +1,325 @@
1
+ """[STEP] Select From Model."""
2
+ import textwrap
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
7
+ from sklearn.feature_selection import SelectFromModel
8
+ from sklearn.linear_model import Lasso, 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 ActSelectFromModel(Actionable):
19
+ """[STEP] Select From Model."""
20
+
21
+ name: str = 'Select From Model'
22
+ _description: str = textwrap.dedent('''\
23
+ Select numeric features using a {estimator} model and an
24
+ importance threshold of {threshold}.''')
25
+ _description_long: str = textwrap.dedent('''\
26
+ SelectFromModel trains a base estimator and removes features with low
27
+ importance (tree-based) or low absolute coefficients (L1-regularized
28
+ linear models). This step supports L1 and tree estimators to reduce
29
+ dimensionality and keep the most informative predictors.''')
30
+ _usage: str = 'Use when you need model-based selection via L1 or trees over ActSelectKBest. Applicable to supervised numeric features for regression or classification. Avoid when no target, mostly categorical data, or you prefer ActRemoveLowVarianceColumn.'
31
+
32
+ def __init__(self):
33
+ self.configuration = {
34
+ 'estimator': {
35
+ 'description': 'Base estimator type used for feature importance.',
36
+ 'default': 'l1',
37
+ 'categorical': ['l1', '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
+ 'l1_alpha': {
50
+ 'description': 'Regularization strength for Lasso (regression).',
51
+ 'default': 0.01,
52
+ 'range': [1e-4, 10.0]
53
+ },
54
+ 'l1_C': {
55
+ 'description': 'Inverse regularization strength for LogisticRegression.',
56
+ 'default': 1.0,
57
+ 'range': [0.01, 100.0]
58
+ },
59
+ 'tree_n_estimators': {
60
+ 'description': 'Number of trees in the ensemble.',
61
+ 'default': 50,
62
+ 'range': [10, 500]
63
+ },
64
+ 'tree_max_depth': {
65
+ 'description': 'Maximum depth of each tree.',
66
+ 'default': 10,
67
+ 'range': [1, 100]
68
+ },
69
+ 'tree_min_samples_leaf': {
70
+ 'description': 'Minimum number of samples required at a leaf node.',
71
+ 'default': 1,
72
+ 'range': [1, 20]
73
+ },
74
+ 'random_state': {
75
+ 'description': 'Random seed for estimators that support it.',
76
+ 'default': 42
77
+ }
78
+ }
79
+
80
+ self.optimizable: bool = True
81
+ self.columns: list[str] = []
82
+ self.selected_columns: list[str] = []
83
+ self.columns_to_drop: list[str] = []
84
+ self.importances: dict[str, float] = {}
85
+ self.selector: SelectFromModel | None = None
86
+ self.threshold_value: float | None = None
87
+
88
+ def _resolve_threshold(self):
89
+ threshold = self.get_config('threshold')
90
+ if threshold is None:
91
+ return None
92
+
93
+ if isinstance(threshold, str):
94
+ value = threshold.strip().lower()
95
+ if value in ['mean', 'median']:
96
+ return value
97
+ try:
98
+ return float(value)
99
+ except ValueError:
100
+ return None
101
+
102
+ try:
103
+ return float(threshold)
104
+ except (TypeError, ValueError):
105
+ return None
106
+
107
+ def _resolve_max_features(self, n_features: int) -> int | None:
108
+ value = self.get_config('max_features')
109
+ if value is None:
110
+ return None
111
+ if isinstance(value, str) and value.strip().lower() in ['none', '']:
112
+ return None
113
+ try:
114
+ value = int(value)
115
+ except (TypeError, ValueError):
116
+ return None
117
+ if value < 1:
118
+ return None
119
+ return min(value, n_features)
120
+
121
+ def _resolve_positive_float(self, key: str) -> float | None:
122
+ value = self.get_config(key)
123
+ try:
124
+ value = float(value)
125
+ except (TypeError, ValueError):
126
+ return None
127
+ if value <= 0:
128
+ return None
129
+ return value
130
+
131
+ def _resolve_positive_int(self, key: str) -> int | None:
132
+ value = self.get_config(key)
133
+ try:
134
+ value = int(value)
135
+ except (TypeError, ValueError):
136
+ return None
137
+ if value < 1:
138
+ return None
139
+ return value
140
+
141
+ def _resolve_max_depth(self) -> int | None:
142
+ value = self.get_config('tree_max_depth')
143
+ if value is None:
144
+ return None
145
+ if isinstance(value, str) and value.strip().lower() in ['none', '']:
146
+ return None
147
+ try:
148
+ value = int(value)
149
+ except (TypeError, ValueError):
150
+ return None
151
+ if value < 1:
152
+ return None
153
+ return value
154
+
155
+ def _build_estimator(self, dataset: Dataset):
156
+ estimator_type = self.get_config('estimator')
157
+ random_state = self.get_config('random_state')
158
+
159
+ if dataset.type_of_target == 'continuous':
160
+ if estimator_type == 'l1':
161
+ alpha = self._resolve_positive_float('l1_alpha')
162
+ if alpha is None:
163
+ return None
164
+ return Lasso(alpha=alpha, max_iter=2000)
165
+ if estimator_type == 'tree':
166
+ n_estimators = self._resolve_positive_int('tree_n_estimators')
167
+ min_samples_leaf = self._resolve_positive_int('tree_min_samples_leaf')
168
+ max_depth = self._resolve_max_depth()
169
+ if None in [n_estimators, min_samples_leaf]:
170
+ return None
171
+ return RandomForestRegressor(
172
+ n_estimators=n_estimators,
173
+ max_depth=max_depth,
174
+ min_samples_leaf=min_samples_leaf,
175
+ random_state=random_state
176
+ )
177
+ return None
178
+
179
+ if dataset.type_of_target in ['binary', 'multiclass']:
180
+ if estimator_type == 'l1':
181
+ c_value = self._resolve_positive_float('l1_C')
182
+ if c_value is None:
183
+ return None
184
+ return LogisticRegression(
185
+ penalty='l1',
186
+ solver='liblinear',
187
+ max_iter=1000,
188
+ random_state=random_state,
189
+ C=c_value,
190
+ multi_class='ovr'
191
+ )
192
+ if estimator_type == 'tree':
193
+ n_estimators = self._resolve_positive_int('tree_n_estimators')
194
+ min_samples_leaf = self._resolve_positive_int('tree_min_samples_leaf')
195
+ max_depth = self._resolve_max_depth()
196
+ if None in [n_estimators, min_samples_leaf]:
197
+ return None
198
+ return RandomForestClassifier(
199
+ n_estimators=n_estimators,
200
+ max_depth=max_depth,
201
+ min_samples_leaf=min_samples_leaf,
202
+ random_state=random_state
203
+ )
204
+ return None
205
+
206
+ def _extract_importances(self, estimator, columns: list[str]) -> dict[str, float]:
207
+ if estimator is None or not columns:
208
+ return {}
209
+
210
+ values = None
211
+ if hasattr(estimator, 'coef_'):
212
+ coefs = np.asarray(estimator.coef_)
213
+ if coefs.ndim == 1:
214
+ values = np.abs(coefs)
215
+ else:
216
+ values = np.mean(np.abs(coefs), axis=0)
217
+ elif hasattr(estimator, 'feature_importances_'):
218
+ values = np.asarray(estimator.feature_importances_)
219
+
220
+ if values is None:
221
+ return {}
222
+
223
+ return {
224
+ column: float(value)
225
+ for column, value in zip(columns, values)
226
+ }
227
+
228
+ def fit(self, dataset: Dataset) -> Actionable:
229
+ self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
230
+ self.selected_columns = []
231
+ self.columns_to_drop = []
232
+ self.importances = {}
233
+ self.selector = None
234
+ self.threshold_value = None
235
+ self.explanations = []
236
+
237
+ if dataset.y is None or dataset.type_of_target is None:
238
+ return self
239
+
240
+ if not self.columns:
241
+ return self
242
+
243
+ threshold = self._resolve_threshold()
244
+ if threshold is None:
245
+ return self
246
+
247
+ estimator = self._build_estimator(dataset)
248
+ if estimator is None:
249
+ return self
250
+
251
+ max_features = self._resolve_max_features(len(self.columns))
252
+ if max_features is not None and max_features != self.get_config('max_features'):
253
+ self.configure('max_features', max_features) # pylint: disable=too-many-function-args
254
+
255
+ self.selector = SelectFromModel(
256
+ estimator=estimator,
257
+ threshold=threshold,
258
+ max_features=max_features
259
+ )
260
+ self.selector.fit(dataset.X[self.columns], dataset.y)
261
+
262
+ support = self.selector.get_support()
263
+ self.selected_columns = list(pd.Index(self.columns)[support])
264
+ self.columns_to_drop = list(pd.Index(self.columns)[~support])
265
+
266
+ self.importances = self._extract_importances(
267
+ getattr(self.selector, 'estimator_', None),
268
+ self.columns
269
+ )
270
+ self.threshold_value = getattr(self.selector, 'threshold_', None)
271
+
272
+ if self.columns_to_drop:
273
+ threshold_display = threshold
274
+ if isinstance(self.threshold_value, (float, int)):
275
+ threshold_display = f"{self.threshold_value:.6g}"
276
+ for column in self.columns_to_drop:
277
+ importance = self.importances.get(column)
278
+ if importance is None or np.isnan(importance):
279
+ self.explanations.append(
280
+ f"Dropped column **`{column}`** because its importance "
281
+ f"was below the threshold ({threshold_display})."
282
+ )
283
+ else:
284
+ self.explanations.append(
285
+ f"Dropped column **`{column}`** because its importance "
286
+ f"({importance:.6g}) was below the threshold ({threshold_display})."
287
+ )
288
+
289
+ return self
290
+
291
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
292
+ """Drop columns that were not selected.
293
+
294
+ :param pd.DataFrame X: DataFrame to transform.
295
+ :return: Transformed dataset.
296
+ """
297
+ if not self.columns_to_drop:
298
+ return X
299
+
300
+ drop_cols = [column for column in self.columns_to_drop if column in X.columns]
301
+ if not drop_cols:
302
+ return X
303
+ return X.drop(columns=drop_cols)
304
+
305
+ def suitable(self, dataset: Dataset) -> bool:
306
+ if dataset.y is None or dataset.type_of_target is None:
307
+ return False
308
+
309
+ if dataset.type_of_target == 'survival':
310
+ return False
311
+
312
+ columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
313
+ if not columns or dataset.X.empty:
314
+ return False
315
+
316
+ if self._resolve_threshold() is None:
317
+ return False
318
+
319
+ if self._build_estimator(dataset) is None:
320
+ return False
321
+
322
+ return True
323
+
324
+ def priorize(self, candidate: Candidate = None) -> float:
325
+ return 0.5