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,181 @@
|
|
|
1
|
+
"""[STEP] Select K Best Features."""
|
|
2
|
+
import textwrap
|
|
3
|
+
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from sklearn.feature_selection import SelectKBest, chi2, f_classif
|
|
8
|
+
from sklearn.feature_selection import mutual_info_classif, mutual_info_regression
|
|
9
|
+
|
|
10
|
+
from ...actionable import Actionable
|
|
11
|
+
from ...candidate import Candidate
|
|
12
|
+
from ...data_type import DataType
|
|
13
|
+
from ...dataset import Dataset
|
|
14
|
+
from ...decorators.all import is_step
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@is_step('features_selection')
|
|
18
|
+
class ActSelectKBest(Actionable):
|
|
19
|
+
"""[STEP] Select K Best Features."""
|
|
20
|
+
|
|
21
|
+
name: str = "Select K Best Features"
|
|
22
|
+
_usage: str = "Use when you want a fast univariate top-k filter instead of ActRFE or ActSelectFromModel. Applicable to numeric features with a labeled target; for continuous targets use mutual_info. Avoid when you need multivariate interactions, stability, or chi2 with negative values."
|
|
23
|
+
_description: str = textwrap.dedent('''\
|
|
24
|
+
Select the {k} best numeric features using {score_func} scoring.''')
|
|
25
|
+
_description_long: str = textwrap.dedent('''\
|
|
26
|
+
SelectKBest ranks features based on a scoring function and keeps only
|
|
27
|
+
the top k features. This step supports chi2, f_classif, and mutual_info
|
|
28
|
+
scoring to reduce dimensionality and keep the most informative
|
|
29
|
+
predictors for the target.''')
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.configuration = {
|
|
33
|
+
'score_func': {
|
|
34
|
+
'description': 'Score function used to rank features.',
|
|
35
|
+
'default': 'f_classif',
|
|
36
|
+
'categorical': ['chi2', 'f_classif', 'mutual_info']
|
|
37
|
+
},
|
|
38
|
+
'k': {
|
|
39
|
+
'description': 'Number of top features to keep.',
|
|
40
|
+
'default': 10,
|
|
41
|
+
'range': [1, 1000]
|
|
42
|
+
},
|
|
43
|
+
'random_state': {
|
|
44
|
+
'description': 'Random seed used for mutual_info scoring.',
|
|
45
|
+
'default': 42
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
self.optimizable: bool = True
|
|
50
|
+
self.columns: list[str] = []
|
|
51
|
+
self.selected_columns: list[str] = []
|
|
52
|
+
self.columns_to_drop: list[str] = []
|
|
53
|
+
self.selector: SelectKBest | None = None
|
|
54
|
+
self._scores: dict[str, float] = {}
|
|
55
|
+
|
|
56
|
+
def _resolve_score_func(self, dataset: Dataset) -> Callable | None:
|
|
57
|
+
score_func = self.get_config('score_func')
|
|
58
|
+
if score_func == 'chi2':
|
|
59
|
+
return chi2
|
|
60
|
+
if score_func == 'f_classif':
|
|
61
|
+
return f_classif
|
|
62
|
+
if score_func == 'mutual_info':
|
|
63
|
+
random_state = self.get_config('random_state')
|
|
64
|
+
if dataset.type_of_target == 'continuous':
|
|
65
|
+
return lambda X, y: mutual_info_regression(
|
|
66
|
+
X, y, random_state=random_state
|
|
67
|
+
)
|
|
68
|
+
return lambda X, y: mutual_info_classif(
|
|
69
|
+
X, y, random_state=random_state
|
|
70
|
+
)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
def _resolve_k(self, max_features: int) -> int:
|
|
74
|
+
k_value = self.get_config('k')
|
|
75
|
+
try:
|
|
76
|
+
k_value = int(k_value)
|
|
77
|
+
except (TypeError, ValueError):
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
if max_features <= 0:
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
return max(1, min(k_value, max_features))
|
|
84
|
+
|
|
85
|
+
def fit(self, dataset: Dataset) -> Actionable:
|
|
86
|
+
self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
87
|
+
self.selected_columns = []
|
|
88
|
+
self.columns_to_drop = []
|
|
89
|
+
self.selector = None
|
|
90
|
+
self._scores = {}
|
|
91
|
+
self.explanations = []
|
|
92
|
+
|
|
93
|
+
if not self.columns or dataset.y is None:
|
|
94
|
+
return self
|
|
95
|
+
if dataset.X[self.columns].isna().any().any():
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
k_value = self._resolve_k(len(self.columns))
|
|
99
|
+
if k_value < 1:
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
score_func = self._resolve_score_func(dataset)
|
|
103
|
+
if score_func is None:
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
if k_value != self.get_config('k'):
|
|
107
|
+
self.configure('k', k_value) # pylint: disable=too-many-function-args
|
|
108
|
+
|
|
109
|
+
self.selector = SelectKBest(score_func=score_func, k=k_value)
|
|
110
|
+
self.selector.fit(dataset.X[self.columns], dataset.y)
|
|
111
|
+
|
|
112
|
+
support = self.selector.get_support()
|
|
113
|
+
self.selected_columns = list(pd.Index(self.columns)[support])
|
|
114
|
+
self.columns_to_drop = list(pd.Index(self.columns)[~support])
|
|
115
|
+
|
|
116
|
+
scores = self.selector.scores_
|
|
117
|
+
if scores is not None:
|
|
118
|
+
self._scores = {
|
|
119
|
+
column: float(score) if score is not None else float('nan')
|
|
120
|
+
for column, score in zip(self.columns, scores)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if self.columns_to_drop:
|
|
124
|
+
for column in self.columns_to_drop:
|
|
125
|
+
score = self._scores.get(column)
|
|
126
|
+
if score is None or pd.isna(score):
|
|
127
|
+
self.explanations.append(
|
|
128
|
+
f"Dropped column **`{column}`** because it was not in "
|
|
129
|
+
f"the top **{k_value}** features."
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
self.explanations.append(
|
|
133
|
+
f"Dropped column **`{column}`** because it was not in "
|
|
134
|
+
f"the top **{k_value}** features (score={score:.4f})."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def transform(self, X: pd.DataFrame) -> pd.DataFrame:
|
|
140
|
+
"""Drop columns that were not selected.
|
|
141
|
+
|
|
142
|
+
:param pd.DataFrame X: DataFrame to transform.
|
|
143
|
+
:return: Transformed dataset.
|
|
144
|
+
"""
|
|
145
|
+
if not self.columns_to_drop:
|
|
146
|
+
return X
|
|
147
|
+
|
|
148
|
+
drop_cols = [column for column in self.columns_to_drop if column in X.columns]
|
|
149
|
+
if not drop_cols:
|
|
150
|
+
return X
|
|
151
|
+
return X.drop(columns=drop_cols)
|
|
152
|
+
|
|
153
|
+
def suitable(self, dataset: Dataset) -> bool:
|
|
154
|
+
if dataset.y is None or dataset.type_of_target is None:
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
158
|
+
if not columns or dataset.X.empty:
|
|
159
|
+
return False
|
|
160
|
+
if dataset.X[columns].isna().any().any():
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
if self._resolve_k(len(columns)) < 1:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
score_func = self.get_config('score_func')
|
|
167
|
+
if dataset.type_of_target == 'continuous':
|
|
168
|
+
return score_func == 'mutual_info'
|
|
169
|
+
|
|
170
|
+
if dataset.type_of_target not in [
|
|
171
|
+
'binary', 'multiclass', 'multilabel-indicator'
|
|
172
|
+
]:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
if score_func == 'chi2':
|
|
176
|
+
return not (dataset.X[columns] < 0).any().any()
|
|
177
|
+
|
|
178
|
+
return score_func in ['f_classif', 'mutual_info', 'chi2']
|
|
179
|
+
|
|
180
|
+
def priorize(self, candidate: Candidate = None) -> float:
|
|
181
|
+
return 0.5
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""[STEP] VIF Selector"""
|
|
2
|
+
import textwrap
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from ...actionable import Actionable
|
|
8
|
+
from ...candidate import Candidate
|
|
9
|
+
from ...data_type import DataType
|
|
10
|
+
from ...dataset import Dataset
|
|
11
|
+
from ...decorators.all import is_step
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@is_step('features_selection')
|
|
15
|
+
class ActVIFSelector(Actionable):
|
|
16
|
+
"""[STEP] VIF Selector"""
|
|
17
|
+
|
|
18
|
+
name: str = 'VIF Selector'
|
|
19
|
+
_description: str = textwrap.dedent('''\
|
|
20
|
+
Drop numeric columns with VIF above {threshold}.''')
|
|
21
|
+
_usage: str = "Use when reducing multicollinearity among numeric predictors, as a simpler alternative to ActRFE or ActSelectFromModel. Applicable to numeric tabular data with correlated features. Avoid when features are non-numeric, too few columns, or you need target-driven selection."
|
|
22
|
+
_description_long: str = textwrap.dedent('''\
|
|
23
|
+
Variance Inflation Factor (VIF) measures multicollinearity among
|
|
24
|
+
numeric predictors. This step computes VIF from the correlation
|
|
25
|
+
matrix and removes columns whose VIF exceeds the configured
|
|
26
|
+
threshold to improve model stability.''')
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.configuration = {
|
|
30
|
+
'threshold': {
|
|
31
|
+
'description': 'Drop columns with VIF greater than this value.',
|
|
32
|
+
'default': 10.0,
|
|
33
|
+
'range': [1.0, 1000.0]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
self.columns: list[str] = []
|
|
37
|
+
self.to_drop: list[str] = []
|
|
38
|
+
self.vif_scores: dict[str, float] = {}
|
|
39
|
+
|
|
40
|
+
def fit(self, dataset: Dataset) -> Actionable:
|
|
41
|
+
self.columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
42
|
+
self.to_drop = []
|
|
43
|
+
self.vif_scores = {}
|
|
44
|
+
self.explanations = []
|
|
45
|
+
|
|
46
|
+
if len(self.columns) < 2 or dataset.X.empty:
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
self.to_drop, self.vif_scores = self.__get_columns(dataset)
|
|
50
|
+
|
|
51
|
+
threshold = self.get_config('threshold')
|
|
52
|
+
for column in self.to_drop:
|
|
53
|
+
vif_value = self.vif_scores.get(column)
|
|
54
|
+
if vif_value is None or not np.isfinite(vif_value):
|
|
55
|
+
message = (
|
|
56
|
+
f"Dropped column **`{column}`** because its VIF was not finite."
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
message = (
|
|
60
|
+
f"Dropped column **`{column}`** because its VIF ({vif_value:.2f}) "
|
|
61
|
+
f"exceeded {threshold}."
|
|
62
|
+
)
|
|
63
|
+
self.explanations.append(message)
|
|
64
|
+
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def __get_columns(self, dataset: Dataset) -> tuple[list[str], dict[str, float]]:
|
|
68
|
+
columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
69
|
+
if len(columns) < 2:
|
|
70
|
+
return [], {}
|
|
71
|
+
|
|
72
|
+
X_numeric = dataset.X[columns]
|
|
73
|
+
if X_numeric.empty:
|
|
74
|
+
return [], {}
|
|
75
|
+
|
|
76
|
+
X_numeric = X_numeric.replace([np.inf, -np.inf], np.nan)
|
|
77
|
+
X_numeric = X_numeric.dropna(axis=0, how='any')
|
|
78
|
+
if X_numeric.shape[0] < 2:
|
|
79
|
+
return [], {}
|
|
80
|
+
|
|
81
|
+
variances = X_numeric.var(ddof=0)
|
|
82
|
+
eligible_columns = variances[variances > 0].index.tolist()
|
|
83
|
+
if len(eligible_columns) < 2:
|
|
84
|
+
return [], {}
|
|
85
|
+
|
|
86
|
+
corr = X_numeric[eligible_columns].corr()
|
|
87
|
+
if corr.empty:
|
|
88
|
+
return [], {}
|
|
89
|
+
|
|
90
|
+
inv_corr = np.linalg.pinv(corr.values)
|
|
91
|
+
vif_values = np.diag(inv_corr)
|
|
92
|
+
vif_values = np.maximum(vif_values, 1.0)
|
|
93
|
+
|
|
94
|
+
vifs = {
|
|
95
|
+
column: float(vif)
|
|
96
|
+
for column, vif in zip(eligible_columns, vif_values)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
threshold = self.get_config('threshold')
|
|
100
|
+
to_drop = [
|
|
101
|
+
column for column, vif in vifs.items()
|
|
102
|
+
if not np.isfinite(vif) or vif > threshold
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
return to_drop, vifs
|
|
106
|
+
|
|
107
|
+
def transform(self, X: pd.DataFrame) -> pd.DataFrame:
|
|
108
|
+
"""Drop columns with high VIF from the DataFrame.
|
|
109
|
+
|
|
110
|
+
:param pd.DataFrame X: The dataset to transform.
|
|
111
|
+
:return: Transformed dataset without high VIF columns.
|
|
112
|
+
"""
|
|
113
|
+
if not self.to_drop:
|
|
114
|
+
return X
|
|
115
|
+
|
|
116
|
+
drop_cols = [column for column in self.to_drop if column in X.columns]
|
|
117
|
+
if not drop_cols:
|
|
118
|
+
return X
|
|
119
|
+
|
|
120
|
+
return X.drop(columns=drop_cols)
|
|
121
|
+
|
|
122
|
+
def priorize(self, candidate: Candidate = None) -> float:
|
|
123
|
+
return 0.5
|
|
124
|
+
|
|
125
|
+
def suitable(self, dataset: Dataset) -> bool:
|
|
126
|
+
columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
127
|
+
if len(columns) < 2 or dataset.X.empty:
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
return bool(self.__get_columns(dataset)[0])
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Normalize and scaler Actionables"""
|
|
2
|
+
from .act_random_over_sampling import ActRandomOverSampling
|
|
3
|
+
from .act_smote import ActSMOTE
|
|
4
|
+
from .act_borderline_smote import ActBorderlineSMOTE
|
|
5
|
+
from .act_adasyn import ActADASYN
|
|
6
|
+
from .act_smoteenn import ActSMOTEENN
|
|
7
|
+
from .act_smote_tomek import ActSMOTETomek
|
|
8
|
+
from .act_random_under_sampler import ActRandomUnderSampler
|
|
9
|
+
from .act_tomek_links import ActTomekLinks
|
|
10
|
+
from .act_near_miss import ActNearMiss
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""[STEP] ADASYN"""
|
|
2
|
+
import textwrap
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from imblearn.over_sampling import ADASYN
|
|
8
|
+
|
|
9
|
+
from ...actionable import Actionable
|
|
10
|
+
from ...candidate import Candidate
|
|
11
|
+
from ...data_type import DataType
|
|
12
|
+
from ...dataset import Dataset
|
|
13
|
+
from ...decorators.all import is_step
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@is_step('imbalance')
|
|
17
|
+
class ActADASYN(Actionable):
|
|
18
|
+
"""[STEP] ADASYN"""
|
|
19
|
+
|
|
20
|
+
name: str = "ADASYN"
|
|
21
|
+
_description: str = textwrap.dedent('''\
|
|
22
|
+
ADASYN creates synthetic samples in hard to learn regions
|
|
23
|
+
to adaptively balance classes.''')
|
|
24
|
+
_description_long: str = textwrap.dedent('''\
|
|
25
|
+
ADASYN (Adaptive Synthetic Sampling) focuses on minority samples that are
|
|
26
|
+
difficult to learn. It generates more synthetic data where class overlap is
|
|
27
|
+
higher, improving decision boundaries while keeping runtime small.''')
|
|
28
|
+
_usage: str = "Use when numeric imbalance needs adaptive synthetic focus rather than ActSMOTE or ActRandomOverSampling. Applicable to binary or multiclass numeric features with sufficient minority samples. Avoid when categorical/text/date features exist or minority class is extremely small."
|
|
29
|
+
refs: list[dict[str, Any]] = [
|
|
30
|
+
{
|
|
31
|
+
'year': 2008,
|
|
32
|
+
'name': 'ADASYN: Adaptive Synthetic Sampling Approach for Imbalanced Learning',
|
|
33
|
+
'authors': [
|
|
34
|
+
'Haibo He',
|
|
35
|
+
'Yang Bai',
|
|
36
|
+
'Edwardo A. Garcia',
|
|
37
|
+
'Sheng Ma'
|
|
38
|
+
],
|
|
39
|
+
'doi': 'https://doi.org/10.1109/IJCNN.2008.4633969',
|
|
40
|
+
'publisher': 'IEEE International Joint Conference on Neural Networks'
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self.configuration = {
|
|
46
|
+
'sampling_strategy': {
|
|
47
|
+
'description': 'Sampling strategy to balance classes.',
|
|
48
|
+
'default': 'auto',
|
|
49
|
+
'categorical': ['minority', 'auto']
|
|
50
|
+
},
|
|
51
|
+
'n_neighbors': {
|
|
52
|
+
'description': 'Number of nearest neighbors used to create synthetic samples.',
|
|
53
|
+
'default': 5,
|
|
54
|
+
'range': [1, 20]
|
|
55
|
+
},
|
|
56
|
+
'random_state': {
|
|
57
|
+
'description': 'Random seed used for reproducibility.',
|
|
58
|
+
'default': 42
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
self.resampler: ADASYN | None = None
|
|
62
|
+
self.categorical_columns: list[str] = []
|
|
63
|
+
self.numeric_columns: list[str] = []
|
|
64
|
+
self._effective_n_neighbors: int | None = None
|
|
65
|
+
|
|
66
|
+
def fit(self, dataset: Dataset) -> Actionable:
|
|
67
|
+
self.resampler = None
|
|
68
|
+
self.categorical_columns = dataset.get_columns_names_by_type(DataType.CATEGORICAL)
|
|
69
|
+
self.numeric_columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
70
|
+
self._effective_n_neighbors = None
|
|
71
|
+
|
|
72
|
+
if dataset.X.empty or dataset.type_of_target not in ['binary', 'multiclass']:
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
if dataset.y is None or len(dataset.y) == 0:
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
unsupported = dataset.get_columns_names_by_type(
|
|
79
|
+
[DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
|
|
80
|
+
)
|
|
81
|
+
if unsupported:
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
if self.categorical_columns:
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
if not self.numeric_columns:
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
_, counts = np.unique(dataset.y, return_counts=True)
|
|
91
|
+
if len(counts) < 2:
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
min_count = int(counts.min())
|
|
95
|
+
if min_count <= 1:
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
max_neighbors = min_count - 1
|
|
99
|
+
n_neighbors = min(int(self.get_config('n_neighbors')), max_neighbors)
|
|
100
|
+
n_neighbors = max(1, n_neighbors)
|
|
101
|
+
self._effective_n_neighbors = n_neighbors
|
|
102
|
+
|
|
103
|
+
params = self.passthrough_parameters()
|
|
104
|
+
params['n_neighbors'] = n_neighbors
|
|
105
|
+
|
|
106
|
+
self.resampler = ADASYN(**params)
|
|
107
|
+
self.resampler.fit(dataset.X, dataset.y)
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def resample(self, X: pd.DataFrame, y: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
111
|
+
"""Apply ADASYN.
|
|
112
|
+
|
|
113
|
+
:param pd.DataFrame X: Features to resample
|
|
114
|
+
:param pd.DataFrame y: Labels to resample
|
|
115
|
+
:return: Resampled X and y
|
|
116
|
+
"""
|
|
117
|
+
if self.resampler is None:
|
|
118
|
+
return X, y
|
|
119
|
+
return self.resampler.fit_resample(X, y)
|
|
120
|
+
|
|
121
|
+
def priorize(self, candidate: Candidate = None) -> float:
|
|
122
|
+
if candidate is None or candidate.dataset.y is None:
|
|
123
|
+
return 0.0
|
|
124
|
+
y = candidate.dataset.y
|
|
125
|
+
if len(y) == 0:
|
|
126
|
+
return 0.0
|
|
127
|
+
_, counts = np.unique(y, return_counts=True)
|
|
128
|
+
if len(counts) < 2:
|
|
129
|
+
return 0.0
|
|
130
|
+
imbalance = 1.0 - (counts.min() / counts.max())
|
|
131
|
+
return float(min(1.0, max(0.0, imbalance)))
|
|
132
|
+
|
|
133
|
+
def suitable(self, dataset: Dataset) -> bool:
|
|
134
|
+
if dataset.type_of_target not in ['binary', 'multiclass']:
|
|
135
|
+
return False
|
|
136
|
+
if dataset.X.empty or dataset.y is None or len(dataset.y) == 0:
|
|
137
|
+
return False
|
|
138
|
+
if dataset.get_columns_names_by_type(DataType.CATEGORICAL):
|
|
139
|
+
return False
|
|
140
|
+
if not dataset.get_columns_names_by_type(DataType.NUMERIC):
|
|
141
|
+
return False
|
|
142
|
+
unsupported = dataset.get_columns_names_by_type(
|
|
143
|
+
[DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
|
|
144
|
+
)
|
|
145
|
+
if unsupported:
|
|
146
|
+
return False
|
|
147
|
+
_, counts = np.unique(dataset.y, return_counts=True)
|
|
148
|
+
if len(counts) < 2:
|
|
149
|
+
return False
|
|
150
|
+
return counts.min() > 1
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""[STEP] Borderline SMOTE"""
|
|
2
|
+
import inspect
|
|
3
|
+
import textwrap
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from imblearn.over_sampling import BorderlineSMOTE
|
|
9
|
+
|
|
10
|
+
from ...actionable import Actionable
|
|
11
|
+
from ...candidate import Candidate
|
|
12
|
+
from ...data_type import DataType
|
|
13
|
+
from ...dataset import Dataset
|
|
14
|
+
from ...decorators.all import is_step
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@is_step('imbalance')
|
|
18
|
+
class ActBorderlineSMOTE(Actionable):
|
|
19
|
+
"""[STEP] Borderline SMOTE"""
|
|
20
|
+
|
|
21
|
+
name: str = "Borderline SMOTE"
|
|
22
|
+
_description: str = textwrap.dedent('''\
|
|
23
|
+
BorderlineSMOTE generates synthetic samples for minority points that
|
|
24
|
+
lie close to the decision boundary.''')
|
|
25
|
+
_description_long: str = textwrap.dedent('''\
|
|
26
|
+
BorderlineSMOTE is a SMOTE variant that targets minority samples in
|
|
27
|
+
the danger zone near the majority class. By focusing on boundary
|
|
28
|
+
samples, it can reduce noise introduced by oversampling safe regions.''')
|
|
29
|
+
_usage: str = "Use when minority points lie near the boundary and you want targeted oversampling vs ActSMOTE or ActRandomOverSampling. Applicable to numeric-only binary or multiclass datasets. Avoid when categorical/text/date features exist or the minority class is too small for neighbors."
|
|
30
|
+
refs: list[dict[str, Any]] = [
|
|
31
|
+
{
|
|
32
|
+
'year': 2005,
|
|
33
|
+
'name': 'Borderline-SMOTE: A New Over-Sampling Method in Imbalanced Data Sets Learning',
|
|
34
|
+
'authors': [
|
|
35
|
+
'Hui Han',
|
|
36
|
+
'Wen-Yuan Wang',
|
|
37
|
+
'Bing-Huan Mao'
|
|
38
|
+
],
|
|
39
|
+
'publisher': 'ICIC 2005, Lecture Notes in Computer Science Vol.3644 page 878--887'
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def __init__(self):
|
|
44
|
+
self.configuration = {
|
|
45
|
+
'sampling_strategy': {
|
|
46
|
+
'description': 'Sampling strategy to balance classes.',
|
|
47
|
+
'default': 'minority',
|
|
48
|
+
'categorical': ['minority', 'auto']
|
|
49
|
+
},
|
|
50
|
+
'k_neighbors': {
|
|
51
|
+
'description': 'Number of nearest neighbors used to create synthetic samples.',
|
|
52
|
+
'default': 5,
|
|
53
|
+
'range': [1, 20]
|
|
54
|
+
},
|
|
55
|
+
'm_neighbors': {
|
|
56
|
+
'description': 'Number of neighbors used to detect borderline samples.',
|
|
57
|
+
'default': 10,
|
|
58
|
+
'range': [1, 20]
|
|
59
|
+
},
|
|
60
|
+
'kind': {
|
|
61
|
+
'description': 'Borderline variant to use when supported by imbalanced-learn.',
|
|
62
|
+
'default': 'borderline-1',
|
|
63
|
+
'categorical': ['borderline-1', 'borderline-2'],
|
|
64
|
+
'passthrough': False
|
|
65
|
+
},
|
|
66
|
+
'random_state': {
|
|
67
|
+
'description': 'Random seed used for reproducibility.',
|
|
68
|
+
'default': 42
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
self.resampler: BorderlineSMOTE | None = None
|
|
72
|
+
self.categorical_columns: list[str] = []
|
|
73
|
+
self.numeric_columns: list[str] = []
|
|
74
|
+
self._effective_k_neighbors: int | None = None
|
|
75
|
+
self._effective_m_neighbors: int | None = None
|
|
76
|
+
|
|
77
|
+
def fit(self, dataset: Dataset) -> Actionable:
|
|
78
|
+
self.resampler = None
|
|
79
|
+
self.categorical_columns = dataset.get_columns_names_by_type(DataType.CATEGORICAL)
|
|
80
|
+
self.numeric_columns = dataset.get_columns_names_by_type(DataType.NUMERIC)
|
|
81
|
+
self._effective_k_neighbors = None
|
|
82
|
+
self._effective_m_neighbors = None
|
|
83
|
+
|
|
84
|
+
if dataset.X.empty or dataset.type_of_target not in ['binary', 'multiclass']:
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
if dataset.y is None or len(dataset.y) == 0:
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
unsupported = dataset.get_columns_names_by_type(
|
|
91
|
+
[DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
|
|
92
|
+
)
|
|
93
|
+
if unsupported:
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
if self.categorical_columns:
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
if not self.numeric_columns:
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
_, counts = np.unique(dataset.y, return_counts=True)
|
|
103
|
+
if len(counts) < 2:
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
min_count = int(counts.min())
|
|
107
|
+
if min_count <= 1:
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
max_k = min_count - 1
|
|
111
|
+
k_neighbors = min(int(self.get_config('k_neighbors')), max_k)
|
|
112
|
+
k_neighbors = max(1, k_neighbors)
|
|
113
|
+
self._effective_k_neighbors = k_neighbors
|
|
114
|
+
|
|
115
|
+
total_count = int(len(dataset.y))
|
|
116
|
+
max_m = max(1, total_count - 1)
|
|
117
|
+
m_neighbors = min(int(self.get_config('m_neighbors')), max_m)
|
|
118
|
+
m_neighbors = max(1, m_neighbors)
|
|
119
|
+
self._effective_m_neighbors = m_neighbors
|
|
120
|
+
|
|
121
|
+
params = self.passthrough_parameters()
|
|
122
|
+
params['k_neighbors'] = k_neighbors
|
|
123
|
+
params['m_neighbors'] = m_neighbors
|
|
124
|
+
if 'kind' in inspect.signature(BorderlineSMOTE).parameters:
|
|
125
|
+
params['kind'] = self.get_config('kind')
|
|
126
|
+
|
|
127
|
+
self.resampler = BorderlineSMOTE(**params)
|
|
128
|
+
self.resampler.fit(dataset.X, dataset.y)
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def resample(self, X: pd.DataFrame, y: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
132
|
+
"""Apply Borderline SMOTE.
|
|
133
|
+
|
|
134
|
+
:param pd.DataFrame X: Features to resample
|
|
135
|
+
:param pd.DataFrame y: Labels to resample
|
|
136
|
+
:return: Resampled X and y
|
|
137
|
+
"""
|
|
138
|
+
if self.resampler is None:
|
|
139
|
+
return X, y
|
|
140
|
+
return self.resampler.fit_resample(X, y)
|
|
141
|
+
|
|
142
|
+
def priorize(self, candidate: Candidate = None) -> float:
|
|
143
|
+
if candidate is None or candidate.dataset.y is None:
|
|
144
|
+
return 0.0
|
|
145
|
+
y = candidate.dataset.y
|
|
146
|
+
if len(y) == 0:
|
|
147
|
+
return 0.0
|
|
148
|
+
_, counts = np.unique(y, return_counts=True)
|
|
149
|
+
if len(counts) < 2:
|
|
150
|
+
return 0.0
|
|
151
|
+
imbalance = 1.0 - (counts.min() / counts.max())
|
|
152
|
+
return float(min(1.0, max(0.0, imbalance)))
|
|
153
|
+
|
|
154
|
+
def suitable(self, dataset: Dataset) -> bool:
|
|
155
|
+
if dataset.type_of_target not in ['binary', 'multiclass']:
|
|
156
|
+
return False
|
|
157
|
+
if dataset.X.empty or dataset.y is None or len(dataset.y) == 0:
|
|
158
|
+
return False
|
|
159
|
+
if dataset.get_columns_names_by_type(DataType.CATEGORICAL):
|
|
160
|
+
return False
|
|
161
|
+
if not dataset.get_columns_names_by_type(DataType.NUMERIC):
|
|
162
|
+
return False
|
|
163
|
+
unsupported = dataset.get_columns_names_by_type(
|
|
164
|
+
[DataType.TEXT, DataType.SHORT_TEXT, DataType.DATE]
|
|
165
|
+
)
|
|
166
|
+
if unsupported:
|
|
167
|
+
return False
|
|
168
|
+
_, counts = np.unique(dataset.y, return_counts=True)
|
|
169
|
+
if len(counts) < 2:
|
|
170
|
+
return False
|
|
171
|
+
return counts.min() > 1
|