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,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)
|