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.
- iaml/__init__.py +56 -0
- iaml/actionable.py +11 -0
- iaml/actionables/__init__.py +21 -0
- iaml/actionables/boosting/__init__.py +4 -0
- iaml/actionables/boosting/act_adaboost.py +59 -0
- iaml/actionables/cleaning/__init__.py +26 -0
- iaml/actionables/cleaning/act_categorical_imputer.py +124 -0
- iaml/actionables/cleaning/act_count_vectorizer.py +204 -0
- iaml/actionables/cleaning/act_drop_categorical_column.py +51 -0
- iaml/actionables/cleaning/act_drop_date_column.py +48 -0
- iaml/actionables/cleaning/act_drop_high_cardinality_categorical.py +337 -0
- iaml/actionables/cleaning/act_drop_numerical_column.py +75 -0
- iaml/actionables/cleaning/act_drop_textual_column.py +51 -0
- iaml/actionables/cleaning/act_encode_target_column.py +56 -0
- iaml/actionables/cleaning/act_frequency_encoder.py +127 -0
- iaml/actionables/cleaning/act_hashing_vectorizer.py +186 -0
- iaml/actionables/cleaning/act_knn_imputer.py +152 -0
- iaml/actionables/cleaning/act_mean_column.py +79 -0
- iaml/actionables/cleaning/act_mice.py +464 -0
- iaml/actionables/cleaning/act_missing_count_feature.py +109 -0
- iaml/actionables/cleaning/act_missing_indicator.py +124 -0
- iaml/actionables/cleaning/act_onehot.py +65 -0
- iaml/actionables/cleaning/act_ordinal_encoder.py +177 -0
- iaml/actionables/cleaning/act_rare_category_grouper.py +173 -0
- iaml/actionables/cleaning/act_simple_imputer.py +109 -0
- iaml/actionables/cleaning/act_split_date.py +68 -0
- iaml/actionables/cleaning/act_target_encoder.py +274 -0
- iaml/actionables/cleaning/act_text_normalizer.py +241 -0
- iaml/actionables/cleaning/act_tf_idf.py +80 -0
- iaml/actionables/cleaning/act_word2vec.py +150 -0
- iaml/actionables/features_precleaning/__init__.py +12 -0
- iaml/actionables/features_precleaning/act_coerce_numeric_strings.py +194 -0
- iaml/actionables/features_precleaning/act_date_converter.py +99 -0
- iaml/actionables/features_precleaning/act_drop_bad_quality_rows.py +77 -0
- iaml/actionables/features_precleaning/act_drop_duplicate_rows.py +131 -0
- iaml/actionables/features_precleaning/act_drop_high_missing_columns.py +94 -0
- iaml/actionables/features_precleaning/act_drop_id_like_columns.py +294 -0
- iaml/actionables/features_precleaning/act_normalize_column_names.py +157 -0
- iaml/actionables/features_precleaning/act_sentinel_to_na_n.py +270 -0
- iaml/actionables/features_precleaning/act_trim_space.py +79 -0
- iaml/actionables/features_preprocessing/__init__.py +18 -0
- iaml/actionables/features_preprocessing/act_cyclical_date_encoding.py +212 -0
- iaml/actionables/features_preprocessing/act_fast_ica.py +161 -0
- iaml/actionables/features_preprocessing/act_feature_agglomeration.py +90 -0
- iaml/actionables/features_preprocessing/act_k_bins_discretizer.py +207 -0
- iaml/actionables/features_preprocessing/act_k_means_features.py +296 -0
- iaml/actionables/features_preprocessing/act_kernel_pca.py +143 -0
- iaml/actionables/features_preprocessing/act_log_transformer.py +122 -0
- iaml/actionables/features_preprocessing/act_nystroem.py +100 -0
- iaml/actionables/features_preprocessing/act_pca.py +77 -0
- iaml/actionables/features_preprocessing/act_polynomial_features.py +86 -0
- iaml/actionables/features_preprocessing/act_power_transformer.py +106 -0
- iaml/actionables/features_preprocessing/act_quantile_transformer.py +114 -0
- iaml/actionables/features_preprocessing/act_rbf_sampler.py +88 -0
- iaml/actionables/features_preprocessing/act_select_percentile.py +112 -0
- iaml/actionables/features_preprocessing/act_sparse_random_projection.py +157 -0
- iaml/actionables/features_preprocessing/act_truncated_svd.py +137 -0
- iaml/actionables/features_selection/__init__.py +8 -0
- iaml/actionables/features_selection/act_permutation_importance_selector.py +421 -0
- iaml/actionables/features_selection/act_remove_high_correlated_column.py +70 -0
- iaml/actionables/features_selection/act_remove_low_variance_column.py +74 -0
- iaml/actionables/features_selection/act_rfe.py +214 -0
- iaml/actionables/features_selection/act_select_from_model.py +325 -0
- iaml/actionables/features_selection/act_select_k_best.py +181 -0
- iaml/actionables/features_selection/act_vif_selector.py +130 -0
- iaml/actionables/imbalance/__init__.py +10 -0
- iaml/actionables/imbalance/act_adasyn.py +150 -0
- iaml/actionables/imbalance/act_borderline_smote.py +171 -0
- iaml/actionables/imbalance/act_near_miss.py +158 -0
- iaml/actionables/imbalance/act_random_over_sampling.py +60 -0
- iaml/actionables/imbalance/act_random_under_sampler.py +135 -0
- iaml/actionables/imbalance/act_smote.py +162 -0
- iaml/actionables/imbalance/act_smote_tomek.py +182 -0
- iaml/actionables/imbalance/act_smoteenn.py +193 -0
- iaml/actionables/imbalance/act_tomek_links.py +138 -0
- iaml/actionables/normalize/__init__.py +6 -0
- iaml/actionables/normalize/act_max_abs_scaler.py +78 -0
- iaml/actionables/normalize/act_minmax_scaler.py +56 -0
- iaml/actionables/normalize/act_normalizer.py +95 -0
- iaml/actionables/normalize/act_robust_scaler.py +111 -0
- iaml/actionables/normalize/act_standard_scaler.py +55 -0
- iaml/actionables/predictors/__init__.py +6 -0
- iaml/actionables/predictors/_xgboost.py +16 -0
- iaml/actionables/predictors/classifier/__init__.py +26 -0
- iaml/actionables/predictors/classifier/act_bagging_classifier.py +113 -0
- iaml/actionables/predictors/classifier/act_bernoulli_nb.py +89 -0
- iaml/actionables/predictors/classifier/act_catboost_classifier.py +135 -0
- iaml/actionables/predictors/classifier/act_complement_nb.py +106 -0
- iaml/actionables/predictors/classifier/act_decision_tree_classifier.py +117 -0
- iaml/actionables/predictors/classifier/act_extra_trees_classifier.py +115 -0
- iaml/actionables/predictors/classifier/act_gaussian_nb.py +53 -0
- iaml/actionables/predictors/classifier/act_hist_gradient_boosting_classifier.py +144 -0
- iaml/actionables/predictors/classifier/act_knn.py +86 -0
- iaml/actionables/predictors/classifier/act_light_gbm_classifier.py +211 -0
- iaml/actionables/predictors/classifier/act_linear_discriminant_analysis.py +63 -0
- iaml/actionables/predictors/classifier/act_linear_svc.py +134 -0
- iaml/actionables/predictors/classifier/act_logistic_regression.py +92 -0
- iaml/actionables/predictors/classifier/act_mlp_classifier.py +107 -0
- iaml/actionables/predictors/classifier/act_multinomial_nb.py +76 -0
- iaml/actionables/predictors/classifier/act_passive_aggressive_classifier.py +141 -0
- iaml/actionables/predictors/classifier/act_quadratic_discriminant_analysis.py +72 -0
- iaml/actionables/predictors/classifier/act_randomforest.py +113 -0
- iaml/actionables/predictors/classifier/act_ridge_classifier.py +116 -0
- iaml/actionables/predictors/classifier/act_sgd_classifier.py +149 -0
- iaml/actionables/predictors/classifier/act_svm_svc.py +88 -0
- iaml/actionables/predictors/classifier/act_xgboost.py +111 -0
- iaml/actionables/predictors/regressor/__init__.py +27 -0
- iaml/actionables/predictors/regressor/act_ada_boost_regressor.py +75 -0
- iaml/actionables/predictors/regressor/act_ard_regression.py +95 -0
- iaml/actionables/predictors/regressor/act_catboost_regressor.py +134 -0
- iaml/actionables/predictors/regressor/act_decision_tree_regressor.py +111 -0
- iaml/actionables/predictors/regressor/act_elastic_net_regressor.py +109 -0
- iaml/actionables/predictors/regressor/act_extra_trees_regressor.py +113 -0
- iaml/actionables/predictors/regressor/act_gaussian_process_regressor.py +55 -0
- iaml/actionables/predictors/regressor/act_gboost_regressor.py +95 -0
- iaml/actionables/predictors/regressor/act_hist_gradient_boosting_regressor.py +105 -0
- iaml/actionables/predictors/regressor/act_huber_regressor.py +101 -0
- iaml/actionables/predictors/regressor/act_knn_regressor.py +86 -0
- iaml/actionables/predictors/regressor/act_lasso_regressor.py +103 -0
- iaml/actionables/predictors/regressor/act_light_gbm_regressor.py +201 -0
- iaml/actionables/predictors/regressor/act_linear_regression.py +43 -0
- iaml/actionables/predictors/regressor/act_mlp_regressor.py +104 -0
- iaml/actionables/predictors/regressor/act_poisson_regressor.py +111 -0
- iaml/actionables/predictors/regressor/act_quantile_regressor.py +87 -0
- iaml/actionables/predictors/regressor/act_randomforest_regressor.py +116 -0
- iaml/actionables/predictors/regressor/act_ransac_regressor.py +106 -0
- iaml/actionables/predictors/regressor/act_ridge_regressor.py +107 -0
- iaml/actionables/predictors/regressor/act_sgd_regressor.py +106 -0
- iaml/actionables/predictors/regressor/act_svm_svr.py +81 -0
- iaml/actionables/predictors/regressor/act_xgboost_regressor.py +97 -0
- iaml/actionables/predictors/survival/__init__.py +12 -0
- iaml/actionables/predictors/survival/act_aalen_additive_model.py +83 -0
- iaml/actionables/predictors/survival/act_cox.py +110 -0
- iaml/actionables/predictors/survival/act_coxnet_survival_analysis.py +134 -0
- iaml/actionables/predictors/survival/act_extra_survival_trees.py +101 -0
- iaml/actionables/predictors/survival/act_fast_survival_svm.py +102 -0
- iaml/actionables/predictors/survival/act_gradient_boosting_survival_analysis.py +93 -0
- iaml/actionables/predictors/survival/act_random_survival_forest.py +91 -0
- iaml/actionables/predictors/survival/act_survival_component_wise_gboost.py +80 -0
- iaml/actionables/predictors/survival/act_survival_tree.py +120 -0
- iaml/actionables/predictors/survival/act_survival_xgboost.py +9 -0
- iaml/actionables/predictors/survival/act_weibull_aft.py +230 -0
- iaml/cache.py +61 -0
- iaml/cache_keys.py +57 -0
- iaml/candidate.py +736 -0
- iaml/core_dispatcher.py +125 -0
- iaml/data_type.py +11 -0
- iaml/dataset.py +506 -0
- iaml/decorators/__init__.py +3 -0
- iaml/decorators/all.py +4 -0
- iaml/decorators/is_step.py +45 -0
- iaml/decorators/runner.py +100 -0
- iaml/explanation.py +112 -0
- iaml/iaml.py +1072 -0
- iaml/iaml_pipeline.py +600 -0
- iaml/logger.py +138 -0
- iaml/meta_explorer_step.py +62 -0
- iaml/meta_ordered_step.py +28 -0
- iaml/meta_partial_explorer_step.py +34 -0
- iaml/meta_singleton.py +24 -0
- iaml/metastep.py +211 -0
- iaml/metric.py +111 -0
- iaml/metric_plot.py +82 -0
- iaml/metrics/__init__.py +21 -0
- iaml/metrics/_classification.py +28 -0
- iaml/metrics/_survival_times.py +22 -0
- iaml/metrics/accuracy_metric.py +59 -0
- iaml/metrics/balanced_accuracy_metric.py +67 -0
- iaml/metrics/brier_score.py +90 -0
- iaml/metrics/classification_error_metric.py +66 -0
- iaml/metrics/concordance_index_ipcw.py +84 -0
- iaml/metrics/concordance_index_metric.py +67 -0
- iaml/metrics/cumulative_dynamic_auc.py +119 -0
- iaml/metrics/f1_score_metric.py +71 -0
- iaml/metrics/integrated_brier_score.py +98 -0
- iaml/metrics/integrated_brier_score_loss.py +41 -0
- iaml/metrics/mean_absolute_error_metric.py +46 -0
- iaml/metrics/mean_squared_error_metric.py +46 -0
- iaml/metrics/mean_squared_log_error_metric.py +49 -0
- iaml/metrics/median_absolute_error_metric.py +48 -0
- iaml/metrics/precision_metric.py +63 -0
- iaml/metrics/r2_score_metric.py +45 -0
- iaml/metrics/recall_metric.py +65 -0
- iaml/metrics/roc_auc_metric.py +50 -0
- iaml/metrics/specificity_metric.py +44 -0
- iaml/metrics/specificity_multiclass_metric.py +55 -0
- iaml/metrics/specificity_multilabel_metric.py +60 -0
- iaml/optimizers/__init__.py +5 -0
- iaml/optimizers/bayesian_optimizer.py +193 -0
- iaml/optimizers/genetic_optimizer.py +284 -0
- iaml/optimizers/optimizer.py +31 -0
- iaml/optimizers/random_optimizer.py +101 -0
- iaml/plot.py +138 -0
- iaml/plots/__init__.py +32 -0
- iaml/plots/bar_plot.py +141 -0
- iaml/plots/box_plot.py +166 -0
- iaml/plots/class_prediction_error_plot.py +37 -0
- iaml/plots/classification_report_plot.py +35 -0
- iaml/plots/confusion_matrix_plot.py +34 -0
- iaml/plots/correlation_heatmap_plot.py +201 -0
- iaml/plots/cumulative_hazard_plot.py +72 -0
- iaml/plots/density_plot.py +210 -0
- iaml/plots/histogram_plot.py +179 -0
- iaml/plots/kaplan_meier_comparison_plot.py +89 -0
- iaml/plots/line_plot.py +70 -0
- iaml/plots/missingness_heatmap_plot.py +203 -0
- iaml/plots/outlier_plot.py +217 -0
- iaml/plots/pair_plot.py +228 -0
- iaml/plots/precision_recall_curve_plot.py +86 -0
- iaml/plots/prediction_error_plot.py +34 -0
- iaml/plots/qq_plot.py +220 -0
- iaml/plots/residual_plot.py +38 -0
- iaml/plots/roc_dynamique_curve_plot.py +79 -0
- iaml/plots/rocauc_plot.py +96 -0
- iaml/plots/shap_plot.py +187 -0
- iaml/plots/target_distribution_plot.py +241 -0
- iaml/plots/violin_plot.py +206 -0
- iaml/predictor.py +139 -0
- iaml/reference.py +65 -0
- iaml/shared_cache.py +90 -0
- iaml/sklearn_preprocessor.py +74 -0
- iaml/splitters/__init__.py +3 -0
- iaml/splitters/kfold_splitter.py +32 -0
- iaml/splitters/random_splitter.py +26 -0
- iaml/stack.py +39 -0
- iaml/statistic.py +66 -0
- iaml/statistics/__init__.py +77 -0
- iaml/statistics/anova_statistic.py +80 -0
- iaml/statistics/cardinality_ratio_statistic.py +63 -0
- iaml/statistics/category_cooccurrence_statistic.py +79 -0
- iaml/statistics/chi_square_statistic.py +81 -0
- iaml/statistics/coef_variation_statistic.py +72 -0
- iaml/statistics/correlation_with_target.py +105 -0
- iaml/statistics/count.py +72 -0
- iaml/statistics/data_type_summary_statistic.py +74 -0
- iaml/statistics/duplicate_row_statistic.py +56 -0
- iaml/statistics/effect_size_statistic.py +129 -0
- iaml/statistics/entropy_statistic.py +69 -0
- iaml/statistics/event_rate_statistic.py +52 -0
- iaml/statistics/grouped_mean_statistic.py +60 -0
- iaml/statistics/iqr_statistic.py +66 -0
- iaml/statistics/kurtosis.py +50 -0
- iaml/statistics/mad_statistic.py +66 -0
- iaml/statistics/mean.py +61 -0
- iaml/statistics/median_statistic.py +61 -0
- iaml/statistics/minmax.py +60 -0
- iaml/statistics/missing_rate_statistic.py +62 -0
- iaml/statistics/mode.py +47 -0
- iaml/statistics/most_frequent_ratio.py +81 -0
- iaml/statistics/outlier_count_iqr_statistic.py +76 -0
- iaml/statistics/quantile.py +59 -0
- iaml/statistics/range.py +53 -0
- iaml/statistics/rare_category_rate.py +92 -0
- iaml/statistics/skewness.py +53 -0
- iaml/statistics/stdev.py +50 -0
- iaml/statistics/summary_table_statistic.py +60 -0
- iaml/statistics/time_by_group_statistic.py +83 -0
- iaml/statistics/time_summary_statistic.py +56 -0
- iaml/statistics/top_k_value_counts.py +68 -0
- iaml/statistics/unique_count_statistic.py +57 -0
- iaml/statistics/value_counts.py +63 -0
- iaml/statistics/variance.py +51 -0
- iaml/statistics/violin.py +63 -0
- iaml/step.py +600 -0
- iaml/step_cache.py +87 -0
- iaml/step_wrapper.py +79 -0
- iaml/timed_pool_executor.py +492 -0
- iaml/type_of_target.py +68 -0
- iaml/void_step.py +101 -0
- iaml/worker_manager.py +169 -0
- iaml/wrapper/__init__.py +4 -0
- iaml/wrapper/wrap_basic_gridsearch.py +68 -0
- iaml/wrapper/wrap_genetic_gridsearch.py +293 -0
- iaml/wrapper/wrap_iterative_gridsearch.py +399 -0
- pyiaml-1.0.0.dist-info/METADATA +802 -0
- pyiaml-1.0.0.dist-info/RECORD +279 -0
- pyiaml-1.0.0.dist-info/WHEEL +5 -0
- pyiaml-1.0.0.dist-info/licenses/LICENSE +674 -0
- 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
|