PyIAML 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. iaml/__init__.py +56 -0
  2. iaml/actionable.py +11 -0
  3. iaml/actionables/__init__.py +21 -0
  4. iaml/actionables/boosting/__init__.py +4 -0
  5. iaml/actionables/boosting/act_adaboost.py +59 -0
  6. iaml/actionables/cleaning/__init__.py +26 -0
  7. iaml/actionables/cleaning/act_categorical_imputer.py +124 -0
  8. iaml/actionables/cleaning/act_count_vectorizer.py +204 -0
  9. iaml/actionables/cleaning/act_drop_categorical_column.py +51 -0
  10. iaml/actionables/cleaning/act_drop_date_column.py +48 -0
  11. iaml/actionables/cleaning/act_drop_high_cardinality_categorical.py +337 -0
  12. iaml/actionables/cleaning/act_drop_numerical_column.py +75 -0
  13. iaml/actionables/cleaning/act_drop_textual_column.py +51 -0
  14. iaml/actionables/cleaning/act_encode_target_column.py +56 -0
  15. iaml/actionables/cleaning/act_frequency_encoder.py +127 -0
  16. iaml/actionables/cleaning/act_hashing_vectorizer.py +186 -0
  17. iaml/actionables/cleaning/act_knn_imputer.py +152 -0
  18. iaml/actionables/cleaning/act_mean_column.py +79 -0
  19. iaml/actionables/cleaning/act_mice.py +464 -0
  20. iaml/actionables/cleaning/act_missing_count_feature.py +109 -0
  21. iaml/actionables/cleaning/act_missing_indicator.py +124 -0
  22. iaml/actionables/cleaning/act_onehot.py +65 -0
  23. iaml/actionables/cleaning/act_ordinal_encoder.py +177 -0
  24. iaml/actionables/cleaning/act_rare_category_grouper.py +173 -0
  25. iaml/actionables/cleaning/act_simple_imputer.py +109 -0
  26. iaml/actionables/cleaning/act_split_date.py +68 -0
  27. iaml/actionables/cleaning/act_target_encoder.py +274 -0
  28. iaml/actionables/cleaning/act_text_normalizer.py +241 -0
  29. iaml/actionables/cleaning/act_tf_idf.py +80 -0
  30. iaml/actionables/cleaning/act_word2vec.py +150 -0
  31. iaml/actionables/features_precleaning/__init__.py +12 -0
  32. iaml/actionables/features_precleaning/act_coerce_numeric_strings.py +194 -0
  33. iaml/actionables/features_precleaning/act_date_converter.py +99 -0
  34. iaml/actionables/features_precleaning/act_drop_bad_quality_rows.py +77 -0
  35. iaml/actionables/features_precleaning/act_drop_duplicate_rows.py +131 -0
  36. iaml/actionables/features_precleaning/act_drop_high_missing_columns.py +94 -0
  37. iaml/actionables/features_precleaning/act_drop_id_like_columns.py +294 -0
  38. iaml/actionables/features_precleaning/act_normalize_column_names.py +157 -0
  39. iaml/actionables/features_precleaning/act_sentinel_to_na_n.py +270 -0
  40. iaml/actionables/features_precleaning/act_trim_space.py +79 -0
  41. iaml/actionables/features_preprocessing/__init__.py +18 -0
  42. iaml/actionables/features_preprocessing/act_cyclical_date_encoding.py +212 -0
  43. iaml/actionables/features_preprocessing/act_fast_ica.py +161 -0
  44. iaml/actionables/features_preprocessing/act_feature_agglomeration.py +90 -0
  45. iaml/actionables/features_preprocessing/act_k_bins_discretizer.py +207 -0
  46. iaml/actionables/features_preprocessing/act_k_means_features.py +296 -0
  47. iaml/actionables/features_preprocessing/act_kernel_pca.py +143 -0
  48. iaml/actionables/features_preprocessing/act_log_transformer.py +122 -0
  49. iaml/actionables/features_preprocessing/act_nystroem.py +100 -0
  50. iaml/actionables/features_preprocessing/act_pca.py +77 -0
  51. iaml/actionables/features_preprocessing/act_polynomial_features.py +86 -0
  52. iaml/actionables/features_preprocessing/act_power_transformer.py +106 -0
  53. iaml/actionables/features_preprocessing/act_quantile_transformer.py +114 -0
  54. iaml/actionables/features_preprocessing/act_rbf_sampler.py +88 -0
  55. iaml/actionables/features_preprocessing/act_select_percentile.py +112 -0
  56. iaml/actionables/features_preprocessing/act_sparse_random_projection.py +157 -0
  57. iaml/actionables/features_preprocessing/act_truncated_svd.py +137 -0
  58. iaml/actionables/features_selection/__init__.py +8 -0
  59. iaml/actionables/features_selection/act_permutation_importance_selector.py +421 -0
  60. iaml/actionables/features_selection/act_remove_high_correlated_column.py +70 -0
  61. iaml/actionables/features_selection/act_remove_low_variance_column.py +74 -0
  62. iaml/actionables/features_selection/act_rfe.py +214 -0
  63. iaml/actionables/features_selection/act_select_from_model.py +325 -0
  64. iaml/actionables/features_selection/act_select_k_best.py +181 -0
  65. iaml/actionables/features_selection/act_vif_selector.py +130 -0
  66. iaml/actionables/imbalance/__init__.py +10 -0
  67. iaml/actionables/imbalance/act_adasyn.py +150 -0
  68. iaml/actionables/imbalance/act_borderline_smote.py +171 -0
  69. iaml/actionables/imbalance/act_near_miss.py +158 -0
  70. iaml/actionables/imbalance/act_random_over_sampling.py +60 -0
  71. iaml/actionables/imbalance/act_random_under_sampler.py +135 -0
  72. iaml/actionables/imbalance/act_smote.py +162 -0
  73. iaml/actionables/imbalance/act_smote_tomek.py +182 -0
  74. iaml/actionables/imbalance/act_smoteenn.py +193 -0
  75. iaml/actionables/imbalance/act_tomek_links.py +138 -0
  76. iaml/actionables/normalize/__init__.py +6 -0
  77. iaml/actionables/normalize/act_max_abs_scaler.py +78 -0
  78. iaml/actionables/normalize/act_minmax_scaler.py +56 -0
  79. iaml/actionables/normalize/act_normalizer.py +95 -0
  80. iaml/actionables/normalize/act_robust_scaler.py +111 -0
  81. iaml/actionables/normalize/act_standard_scaler.py +55 -0
  82. iaml/actionables/predictors/__init__.py +6 -0
  83. iaml/actionables/predictors/_xgboost.py +16 -0
  84. iaml/actionables/predictors/classifier/__init__.py +26 -0
  85. iaml/actionables/predictors/classifier/act_bagging_classifier.py +113 -0
  86. iaml/actionables/predictors/classifier/act_bernoulli_nb.py +89 -0
  87. iaml/actionables/predictors/classifier/act_catboost_classifier.py +135 -0
  88. iaml/actionables/predictors/classifier/act_complement_nb.py +106 -0
  89. iaml/actionables/predictors/classifier/act_decision_tree_classifier.py +117 -0
  90. iaml/actionables/predictors/classifier/act_extra_trees_classifier.py +115 -0
  91. iaml/actionables/predictors/classifier/act_gaussian_nb.py +53 -0
  92. iaml/actionables/predictors/classifier/act_hist_gradient_boosting_classifier.py +144 -0
  93. iaml/actionables/predictors/classifier/act_knn.py +86 -0
  94. iaml/actionables/predictors/classifier/act_light_gbm_classifier.py +211 -0
  95. iaml/actionables/predictors/classifier/act_linear_discriminant_analysis.py +63 -0
  96. iaml/actionables/predictors/classifier/act_linear_svc.py +134 -0
  97. iaml/actionables/predictors/classifier/act_logistic_regression.py +92 -0
  98. iaml/actionables/predictors/classifier/act_mlp_classifier.py +107 -0
  99. iaml/actionables/predictors/classifier/act_multinomial_nb.py +76 -0
  100. iaml/actionables/predictors/classifier/act_passive_aggressive_classifier.py +141 -0
  101. iaml/actionables/predictors/classifier/act_quadratic_discriminant_analysis.py +72 -0
  102. iaml/actionables/predictors/classifier/act_randomforest.py +113 -0
  103. iaml/actionables/predictors/classifier/act_ridge_classifier.py +116 -0
  104. iaml/actionables/predictors/classifier/act_sgd_classifier.py +149 -0
  105. iaml/actionables/predictors/classifier/act_svm_svc.py +88 -0
  106. iaml/actionables/predictors/classifier/act_xgboost.py +111 -0
  107. iaml/actionables/predictors/regressor/__init__.py +27 -0
  108. iaml/actionables/predictors/regressor/act_ada_boost_regressor.py +75 -0
  109. iaml/actionables/predictors/regressor/act_ard_regression.py +95 -0
  110. iaml/actionables/predictors/regressor/act_catboost_regressor.py +134 -0
  111. iaml/actionables/predictors/regressor/act_decision_tree_regressor.py +111 -0
  112. iaml/actionables/predictors/regressor/act_elastic_net_regressor.py +109 -0
  113. iaml/actionables/predictors/regressor/act_extra_trees_regressor.py +113 -0
  114. iaml/actionables/predictors/regressor/act_gaussian_process_regressor.py +55 -0
  115. iaml/actionables/predictors/regressor/act_gboost_regressor.py +95 -0
  116. iaml/actionables/predictors/regressor/act_hist_gradient_boosting_regressor.py +105 -0
  117. iaml/actionables/predictors/regressor/act_huber_regressor.py +101 -0
  118. iaml/actionables/predictors/regressor/act_knn_regressor.py +86 -0
  119. iaml/actionables/predictors/regressor/act_lasso_regressor.py +103 -0
  120. iaml/actionables/predictors/regressor/act_light_gbm_regressor.py +201 -0
  121. iaml/actionables/predictors/regressor/act_linear_regression.py +43 -0
  122. iaml/actionables/predictors/regressor/act_mlp_regressor.py +104 -0
  123. iaml/actionables/predictors/regressor/act_poisson_regressor.py +111 -0
  124. iaml/actionables/predictors/regressor/act_quantile_regressor.py +87 -0
  125. iaml/actionables/predictors/regressor/act_randomforest_regressor.py +116 -0
  126. iaml/actionables/predictors/regressor/act_ransac_regressor.py +106 -0
  127. iaml/actionables/predictors/regressor/act_ridge_regressor.py +107 -0
  128. iaml/actionables/predictors/regressor/act_sgd_regressor.py +106 -0
  129. iaml/actionables/predictors/regressor/act_svm_svr.py +81 -0
  130. iaml/actionables/predictors/regressor/act_xgboost_regressor.py +97 -0
  131. iaml/actionables/predictors/survival/__init__.py +12 -0
  132. iaml/actionables/predictors/survival/act_aalen_additive_model.py +83 -0
  133. iaml/actionables/predictors/survival/act_cox.py +110 -0
  134. iaml/actionables/predictors/survival/act_coxnet_survival_analysis.py +134 -0
  135. iaml/actionables/predictors/survival/act_extra_survival_trees.py +101 -0
  136. iaml/actionables/predictors/survival/act_fast_survival_svm.py +102 -0
  137. iaml/actionables/predictors/survival/act_gradient_boosting_survival_analysis.py +93 -0
  138. iaml/actionables/predictors/survival/act_random_survival_forest.py +91 -0
  139. iaml/actionables/predictors/survival/act_survival_component_wise_gboost.py +80 -0
  140. iaml/actionables/predictors/survival/act_survival_tree.py +120 -0
  141. iaml/actionables/predictors/survival/act_survival_xgboost.py +9 -0
  142. iaml/actionables/predictors/survival/act_weibull_aft.py +230 -0
  143. iaml/cache.py +61 -0
  144. iaml/cache_keys.py +57 -0
  145. iaml/candidate.py +736 -0
  146. iaml/core_dispatcher.py +125 -0
  147. iaml/data_type.py +11 -0
  148. iaml/dataset.py +506 -0
  149. iaml/decorators/__init__.py +3 -0
  150. iaml/decorators/all.py +4 -0
  151. iaml/decorators/is_step.py +45 -0
  152. iaml/decorators/runner.py +100 -0
  153. iaml/explanation.py +112 -0
  154. iaml/iaml.py +1072 -0
  155. iaml/iaml_pipeline.py +600 -0
  156. iaml/logger.py +138 -0
  157. iaml/meta_explorer_step.py +62 -0
  158. iaml/meta_ordered_step.py +28 -0
  159. iaml/meta_partial_explorer_step.py +34 -0
  160. iaml/meta_singleton.py +24 -0
  161. iaml/metastep.py +211 -0
  162. iaml/metric.py +111 -0
  163. iaml/metric_plot.py +82 -0
  164. iaml/metrics/__init__.py +21 -0
  165. iaml/metrics/_classification.py +28 -0
  166. iaml/metrics/_survival_times.py +22 -0
  167. iaml/metrics/accuracy_metric.py +59 -0
  168. iaml/metrics/balanced_accuracy_metric.py +67 -0
  169. iaml/metrics/brier_score.py +90 -0
  170. iaml/metrics/classification_error_metric.py +66 -0
  171. iaml/metrics/concordance_index_ipcw.py +84 -0
  172. iaml/metrics/concordance_index_metric.py +67 -0
  173. iaml/metrics/cumulative_dynamic_auc.py +119 -0
  174. iaml/metrics/f1_score_metric.py +71 -0
  175. iaml/metrics/integrated_brier_score.py +98 -0
  176. iaml/metrics/integrated_brier_score_loss.py +41 -0
  177. iaml/metrics/mean_absolute_error_metric.py +46 -0
  178. iaml/metrics/mean_squared_error_metric.py +46 -0
  179. iaml/metrics/mean_squared_log_error_metric.py +49 -0
  180. iaml/metrics/median_absolute_error_metric.py +48 -0
  181. iaml/metrics/precision_metric.py +63 -0
  182. iaml/metrics/r2_score_metric.py +45 -0
  183. iaml/metrics/recall_metric.py +65 -0
  184. iaml/metrics/roc_auc_metric.py +50 -0
  185. iaml/metrics/specificity_metric.py +44 -0
  186. iaml/metrics/specificity_multiclass_metric.py +55 -0
  187. iaml/metrics/specificity_multilabel_metric.py +60 -0
  188. iaml/optimizers/__init__.py +5 -0
  189. iaml/optimizers/bayesian_optimizer.py +193 -0
  190. iaml/optimizers/genetic_optimizer.py +284 -0
  191. iaml/optimizers/optimizer.py +31 -0
  192. iaml/optimizers/random_optimizer.py +101 -0
  193. iaml/plot.py +138 -0
  194. iaml/plots/__init__.py +32 -0
  195. iaml/plots/bar_plot.py +141 -0
  196. iaml/plots/box_plot.py +166 -0
  197. iaml/plots/class_prediction_error_plot.py +37 -0
  198. iaml/plots/classification_report_plot.py +35 -0
  199. iaml/plots/confusion_matrix_plot.py +34 -0
  200. iaml/plots/correlation_heatmap_plot.py +201 -0
  201. iaml/plots/cumulative_hazard_plot.py +72 -0
  202. iaml/plots/density_plot.py +210 -0
  203. iaml/plots/histogram_plot.py +179 -0
  204. iaml/plots/kaplan_meier_comparison_plot.py +89 -0
  205. iaml/plots/line_plot.py +70 -0
  206. iaml/plots/missingness_heatmap_plot.py +203 -0
  207. iaml/plots/outlier_plot.py +217 -0
  208. iaml/plots/pair_plot.py +228 -0
  209. iaml/plots/precision_recall_curve_plot.py +86 -0
  210. iaml/plots/prediction_error_plot.py +34 -0
  211. iaml/plots/qq_plot.py +220 -0
  212. iaml/plots/residual_plot.py +38 -0
  213. iaml/plots/roc_dynamique_curve_plot.py +79 -0
  214. iaml/plots/rocauc_plot.py +96 -0
  215. iaml/plots/shap_plot.py +187 -0
  216. iaml/plots/target_distribution_plot.py +241 -0
  217. iaml/plots/violin_plot.py +206 -0
  218. iaml/predictor.py +139 -0
  219. iaml/reference.py +65 -0
  220. iaml/shared_cache.py +90 -0
  221. iaml/sklearn_preprocessor.py +74 -0
  222. iaml/splitters/__init__.py +3 -0
  223. iaml/splitters/kfold_splitter.py +32 -0
  224. iaml/splitters/random_splitter.py +26 -0
  225. iaml/stack.py +39 -0
  226. iaml/statistic.py +66 -0
  227. iaml/statistics/__init__.py +77 -0
  228. iaml/statistics/anova_statistic.py +80 -0
  229. iaml/statistics/cardinality_ratio_statistic.py +63 -0
  230. iaml/statistics/category_cooccurrence_statistic.py +79 -0
  231. iaml/statistics/chi_square_statistic.py +81 -0
  232. iaml/statistics/coef_variation_statistic.py +72 -0
  233. iaml/statistics/correlation_with_target.py +105 -0
  234. iaml/statistics/count.py +72 -0
  235. iaml/statistics/data_type_summary_statistic.py +74 -0
  236. iaml/statistics/duplicate_row_statistic.py +56 -0
  237. iaml/statistics/effect_size_statistic.py +129 -0
  238. iaml/statistics/entropy_statistic.py +69 -0
  239. iaml/statistics/event_rate_statistic.py +52 -0
  240. iaml/statistics/grouped_mean_statistic.py +60 -0
  241. iaml/statistics/iqr_statistic.py +66 -0
  242. iaml/statistics/kurtosis.py +50 -0
  243. iaml/statistics/mad_statistic.py +66 -0
  244. iaml/statistics/mean.py +61 -0
  245. iaml/statistics/median_statistic.py +61 -0
  246. iaml/statistics/minmax.py +60 -0
  247. iaml/statistics/missing_rate_statistic.py +62 -0
  248. iaml/statistics/mode.py +47 -0
  249. iaml/statistics/most_frequent_ratio.py +81 -0
  250. iaml/statistics/outlier_count_iqr_statistic.py +76 -0
  251. iaml/statistics/quantile.py +59 -0
  252. iaml/statistics/range.py +53 -0
  253. iaml/statistics/rare_category_rate.py +92 -0
  254. iaml/statistics/skewness.py +53 -0
  255. iaml/statistics/stdev.py +50 -0
  256. iaml/statistics/summary_table_statistic.py +60 -0
  257. iaml/statistics/time_by_group_statistic.py +83 -0
  258. iaml/statistics/time_summary_statistic.py +56 -0
  259. iaml/statistics/top_k_value_counts.py +68 -0
  260. iaml/statistics/unique_count_statistic.py +57 -0
  261. iaml/statistics/value_counts.py +63 -0
  262. iaml/statistics/variance.py +51 -0
  263. iaml/statistics/violin.py +63 -0
  264. iaml/step.py +600 -0
  265. iaml/step_cache.py +87 -0
  266. iaml/step_wrapper.py +79 -0
  267. iaml/timed_pool_executor.py +492 -0
  268. iaml/type_of_target.py +68 -0
  269. iaml/void_step.py +101 -0
  270. iaml/worker_manager.py +169 -0
  271. iaml/wrapper/__init__.py +4 -0
  272. iaml/wrapper/wrap_basic_gridsearch.py +68 -0
  273. iaml/wrapper/wrap_genetic_gridsearch.py +293 -0
  274. iaml/wrapper/wrap_iterative_gridsearch.py +399 -0
  275. pyiaml-1.0.0.dist-info/METADATA +802 -0
  276. pyiaml-1.0.0.dist-info/RECORD +279 -0
  277. pyiaml-1.0.0.dist-info/WHEEL +5 -0
  278. pyiaml-1.0.0.dist-info/licenses/LICENSE +674 -0
  279. pyiaml-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,274 @@
1
+ """[STEP] Target mean encoding with internal cross-validation."""
2
+ import textwrap
3
+ from typing import Any
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from sklearn.model_selection import KFold, StratifiedKFold
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('cleaning')
17
+ class ActTargetEncoder(Actionable):
18
+ """[STEP] Encode categorical features by target mean with internal CV."""
19
+
20
+ name: str = 'Target mean encoding'
21
+ _description: str = textwrap.dedent('''\
22
+ Encode categorical columns with the mean of the target using internal CV.''')
23
+ _description_long: str = textwrap.dedent('''\
24
+ Replace each category with the mean target value computed on training folds only.
25
+ This yields out-of-fold encodings for the training data to reduce leakage. For new
26
+ data, mappings learned on the full training set are used and unseen categories are
27
+ mapped to the global target mean. Non-numeric targets are factorized first.''')
28
+ _usage: str = "Use when categorical features have target signal and you want numeric encoding vs ActCountVectorizer. Applicable to supervised data with categorical columns and enough rows. Avoid when leakage risk is high or categories are too sparse; prefer ActDropHighCardinalityCategorical."
29
+
30
+ def __init__(self) -> None:
31
+ self.columns: list[str] = []
32
+ self.encodings: dict[str, dict[Any, float]] = {}
33
+ self.fallback_values: dict[str, float] = {}
34
+ self.global_mean: float = 0.0
35
+ self._train_encoded: pd.DataFrame | None = None
36
+ self._train_X: pd.DataFrame | None = None
37
+
38
+ self.configuration = {
39
+ 'n_splits': {
40
+ 'description': 'Number of CV folds used to compute out-of-fold encodings.',
41
+ 'default': 5
42
+ },
43
+ 'shuffle': {
44
+ 'description': 'Shuffle rows before splitting into folds.',
45
+ 'default': True
46
+ },
47
+ 'random_state': {
48
+ 'description': 'Random seed used when shuffling.',
49
+ 'default': 42
50
+ },
51
+ 'smoothing': {
52
+ 'description': 'Smoothing strength towards the global mean.',
53
+ 'default': 1.0
54
+ }
55
+ }
56
+
57
+ def fit(self, dataset: Dataset) -> Actionable:
58
+ self.columns = self._select_columns(dataset)
59
+ self.encodings = {}
60
+ self.fallback_values = {}
61
+ self.global_mean = 0.0
62
+ self._train_encoded = None
63
+ self._train_X = None
64
+ self.explanations = []
65
+
66
+ if not self.columns or dataset.X.empty or dataset.y is None:
67
+ return self
68
+
69
+ y_series = pd.Series(dataset.y, index=dataset.X.index)
70
+ y_numeric = self._coerce_target(y_series)
71
+ if y_numeric.empty:
72
+ return self
73
+
74
+ prior = float(y_numeric.mean())
75
+ if np.isnan(prior):
76
+ prior = 0.0
77
+ self.global_mean = prior
78
+
79
+ n_rows = len(dataset.X)
80
+ n_splits = self._coerce_int(self.get_config('n_splits'), 5)
81
+ n_splits = min(max(n_splits, 2), n_rows) if n_rows > 1 else 1
82
+ shuffle = bool(self.get_config('shuffle'))
83
+ random_state = self._coerce_int(self.get_config('random_state'), 42)
84
+ smoothing = self._coerce_float(self.get_config('smoothing'), 1.0)
85
+
86
+ use_cv = n_rows >= 2 and n_splits >= 2
87
+ splits = []
88
+ if use_cv:
89
+ splits = self._build_splits(
90
+ dataset.X,
91
+ y_series,
92
+ dataset.type_of_target,
93
+ n_splits,
94
+ shuffle,
95
+ random_state
96
+ )
97
+
98
+ if use_cv and not splits:
99
+ use_cv = False
100
+
101
+ encoded_train = pd.DataFrame(index=dataset.X.index, columns=self.columns, dtype=float)
102
+
103
+ for column in self.columns:
104
+ series = dataset.X[column]
105
+ mapping_full = self._fit_mapping(series, y_numeric, prior, smoothing)
106
+ self.encodings[column] = mapping_full
107
+ self.fallback_values[column] = prior
108
+
109
+ if use_cv:
110
+ oof = pd.Series(index=dataset.X.index, dtype=float)
111
+ for train_idx, val_idx in splits:
112
+ mapping_fold = self._fit_mapping(
113
+ series.iloc[train_idx],
114
+ y_numeric.iloc[train_idx],
115
+ prior,
116
+ smoothing
117
+ )
118
+ encoded = series.iloc[val_idx].map(mapping_fold)
119
+ oof.iloc[val_idx] = encoded
120
+ oof = oof.fillna(prior).astype(float)
121
+ else:
122
+ oof = series.map(mapping_full).fillna(prior).astype(float)
123
+
124
+ encoded_train[column] = oof
125
+
126
+ self.explanations.append(
127
+ f"Target-encoded `{column}` using {len(mapping_full)} categories."
128
+ )
129
+
130
+ self._train_encoded = encoded_train
131
+ self._train_X = dataset.X
132
+
133
+ return self
134
+
135
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
136
+ if not self.columns or X.empty:
137
+ return X
138
+
139
+ if self._train_X is not None and X is self._train_X and self._train_encoded is not None:
140
+ for column in self.columns:
141
+ if column in X.columns and column in self._train_encoded.columns:
142
+ X[column] = self._train_encoded[column].reindex(X.index).astype(float)
143
+ return X
144
+
145
+ for column in self.columns:
146
+ if column not in X.columns:
147
+ continue
148
+ mapping = self.encodings.get(column, {})
149
+ fallback = self.fallback_values.get(column, self.global_mean)
150
+ fallback = self._coerce_float(fallback, self.global_mean)
151
+ X[column] = X[column].map(mapping).fillna(fallback).astype(float)
152
+
153
+ return X
154
+
155
+ def suitable(self, dataset: Dataset) -> bool:
156
+ if dataset.y is None or dataset.X.empty:
157
+ return False
158
+ if dataset.type_of_target == 'survival':
159
+ return False
160
+ return bool(self._select_columns(dataset))
161
+
162
+ def priorize(self, candidate: Candidate = None) -> float:
163
+ if candidate is None or candidate.dataset.X.empty or candidate.dataset.y is None:
164
+ return 0.0
165
+
166
+ columns = self._select_columns(candidate.dataset)
167
+ if not columns:
168
+ return 0.0
169
+
170
+ total_rows = len(candidate.dataset.X)
171
+ if total_rows <= 0:
172
+ return 0.0
173
+
174
+ unique_counts = candidate.dataset.X[columns].nunique(dropna=True)
175
+ avg_cardinality = float((unique_counts / total_rows).mean())
176
+
177
+ total_columns = candidate.dataset.X.shape[1] or 1
178
+ cat_ratio = len(columns) / total_columns
179
+
180
+ return min(1.0, max(cat_ratio, avg_cardinality))
181
+
182
+ def _select_columns(self, dataset: Dataset) -> list[str]:
183
+ columns = dataset.get_columns_names_by_type(DataType.CATEGORICAL)
184
+ category_columns = list(dataset.X.select_dtypes(include=['category']).columns)
185
+ seen = set()
186
+ ordered = []
187
+ for column in columns + category_columns:
188
+ if column in dataset.X.columns and column not in seen:
189
+ ordered.append(column)
190
+ seen.add(column)
191
+ return ordered
192
+
193
+ @staticmethod
194
+ def _coerce_target(y: pd.Series) -> pd.Series:
195
+ if y.empty:
196
+ return y.astype(float)
197
+ if pd.api.types.is_bool_dtype(y):
198
+ return y.astype(float)
199
+ if pd.api.types.is_numeric_dtype(y):
200
+ return y.astype(float)
201
+ codes, _ = pd.factorize(y, sort=True)
202
+ numeric = pd.Series(codes, index=y.index, dtype=float)
203
+ numeric[codes < 0] = np.nan
204
+ return numeric
205
+
206
+ @staticmethod
207
+ def _fit_mapping(
208
+ series: pd.Series,
209
+ target: pd.Series,
210
+ prior: float,
211
+ smoothing: float
212
+ ) -> dict[Any, float]:
213
+ if series.empty:
214
+ return {}
215
+
216
+ series_values = series.astype(object)
217
+ grouped = target.groupby(series_values).agg(['mean', 'count'])
218
+ if grouped.empty:
219
+ return {}
220
+
221
+ smoothing = float(smoothing)
222
+ if smoothing <= 0:
223
+ smooth = grouped['mean']
224
+ else:
225
+ smooth = (grouped['mean'] * grouped['count'] + prior * smoothing) \
226
+ / (grouped['count'] + smoothing)
227
+
228
+ return smooth.to_dict()
229
+
230
+ @staticmethod
231
+ def _coerce_int(value: Any, default: int) -> int:
232
+ try:
233
+ return int(value)
234
+ except (TypeError, ValueError):
235
+ return default
236
+
237
+ @staticmethod
238
+ def _coerce_float(value: Any, default: float) -> float:
239
+ try:
240
+ return float(value)
241
+ except (TypeError, ValueError):
242
+ return default
243
+
244
+ @staticmethod
245
+ def _build_splits(
246
+ X: pd.DataFrame,
247
+ y: pd.Series,
248
+ target_type: str,
249
+ n_splits: int,
250
+ shuffle: bool,
251
+ random_state: int
252
+ ) -> list[tuple[np.ndarray, np.ndarray]]:
253
+ if n_splits < 2:
254
+ return []
255
+
256
+ is_classification = target_type not in (
257
+ None,
258
+ 'continuous',
259
+ 'continuous-multioutput',
260
+ 'survival'
261
+ )
262
+ if is_classification:
263
+ try:
264
+ splitter = StratifiedKFold(
265
+ n_splits=n_splits,
266
+ shuffle=shuffle,
267
+ random_state=random_state
268
+ )
269
+ return list(splitter.split(X, y))
270
+ except ValueError:
271
+ pass
272
+
273
+ splitter = KFold(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
274
+ return list(splitter.split(X))
@@ -0,0 +1,241 @@
1
+ """[STEP] Normalize text columns before vectorization."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ import string
6
+ import textwrap
7
+
8
+ import pandas as pd
9
+ from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS
10
+
11
+ from ...actionable import Actionable
12
+ from ...candidate import Candidate
13
+ from ...data_type import DataType
14
+ from ...dataset import Dataset
15
+ from ...decorators.all import is_step
16
+
17
+
18
+ _PUNCTUATION_RE = re.compile(rf"[{re.escape(string.punctuation)}]+")
19
+ _MULTISPACE_RE = re.compile(r"\s+")
20
+
21
+
22
+ @is_step('cleaning')
23
+ class ActTextNormalizer(Actionable):
24
+ """[STEP] Normalize text columns before vectorization."""
25
+
26
+ name: str = 'Text normalizer'
27
+ _usage: str = 'Use when normalizing TEXT/SHORT_TEXT before ActCountVectorizer. Applicable to free-form text columns needing consistent casing, punctuation, or stopword cleanup. Avoid when text must remain verbatim or ActCountVectorizer settings already cover normalization.'
28
+ _description: str = textwrap.dedent('''\
29
+ Normalize text columns with lowercasing, punctuation removal, and stopword filtering.''')
30
+ _description_long: str = textwrap.dedent('''\
31
+ Prepare text columns for downstream vectorizers by standardizing casing, removing
32
+ punctuation, dropping common stopwords, and cleaning up extra whitespace. This step
33
+ targets columns typed as TEXT or SHORT_TEXT and keeps the processing deterministic.''')
34
+
35
+ def __init__(self) -> None:
36
+ self.configuration = {
37
+ 'lowercase': {
38
+ 'description': 'Lowercase text before normalization.',
39
+ 'default': True
40
+ },
41
+ 'remove_punctuation': {
42
+ 'description': 'Replace punctuation characters with spaces.',
43
+ 'default': True
44
+ },
45
+ 'remove_stopwords': {
46
+ 'description': 'Remove stopwords from text.',
47
+ 'default': True
48
+ },
49
+ 'stopwords': {
50
+ 'description': 'Stopwords to remove ("english", "none", or a list).',
51
+ 'default': 'english'
52
+ },
53
+ 'collapse_whitespace': {
54
+ 'description': 'Collapse repeated whitespace and strip edges.',
55
+ 'default': True
56
+ }
57
+ }
58
+
59
+ self.columns: list[str] = []
60
+ self.stop_words: set[str] = set()
61
+ self._stopword_pattern: re.Pattern | None = None
62
+ self._lowercase: bool = True
63
+ self._remove_punctuation: bool = True
64
+ self._remove_stopwords: bool = True
65
+ self._collapse_whitespace: bool = True
66
+
67
+ def fit(self, dataset: Dataset) -> Actionable:
68
+ self.columns = self._select_columns(dataset)
69
+ self.explanations = []
70
+
71
+ self._lowercase = self._coerce_bool(self.get_config('lowercase'), True)
72
+ self._remove_punctuation = self._coerce_bool(self.get_config('remove_punctuation'), True)
73
+ self._remove_stopwords = self._coerce_bool(self.get_config('remove_stopwords'), True)
74
+ self._collapse_whitespace = self._coerce_bool(self.get_config('collapse_whitespace'), True)
75
+
76
+ self.stop_words = self._normalize_stopwords(self._resolve_stopwords())
77
+ self._stopword_pattern = self._build_stopword_pattern(self.stop_words)
78
+
79
+ if not self.columns or dataset.X.empty:
80
+ return self
81
+
82
+ if not self._has_enabled_operations():
83
+ self.columns = []
84
+ self.explanations.append('Text normalizer skipped: no enabled operations.')
85
+ return self
86
+
87
+ operations = self._operations_summary()
88
+ for column in self.columns:
89
+ self.explanations.append(
90
+ f"Normalized text column **`{column}`** with {', '.join(operations)}."
91
+ )
92
+
93
+ return self
94
+
95
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
96
+ if not self.columns or not self._has_enabled_operations():
97
+ return X
98
+
99
+ for column in self.columns:
100
+ if column not in X.columns:
101
+ continue
102
+
103
+ series = X[column].fillna('').astype(str)
104
+
105
+ if self._lowercase:
106
+ series = series.str.lower()
107
+ if self._remove_punctuation:
108
+ series = series.str.replace(_PUNCTUATION_RE, ' ', regex=True)
109
+ if self._stopword_pattern is not None:
110
+ series = series.str.replace(self._stopword_pattern, ' ', regex=True)
111
+ if self._collapse_whitespace:
112
+ series = series.str.replace(_MULTISPACE_RE, ' ', regex=True).str.strip()
113
+
114
+ X[column] = series
115
+
116
+ return X
117
+
118
+ def suitable(self, dataset: Dataset) -> bool:
119
+ if dataset.X.empty:
120
+ return False
121
+ if not self._select_columns(dataset):
122
+ return False
123
+ return self._has_enabled_operations(configured_only=True)
124
+
125
+ def priorize(self, candidate: Candidate = None) -> float:
126
+ if candidate is None or candidate.dataset.X.empty:
127
+ return 0.0
128
+
129
+ columns = self._select_columns(candidate.dataset)
130
+ if not columns or not self._has_enabled_operations(configured_only=True):
131
+ return 0.0
132
+
133
+ total_columns = candidate.dataset.X.shape[1] or 1
134
+ return min(1.0, len(columns) / total_columns)
135
+
136
+ def _select_columns(self, dataset: Dataset) -> list[str]:
137
+ columns = dataset.get_columns_names_by_type([DataType.TEXT, DataType.SHORT_TEXT])
138
+ return [column for column in columns if column in dataset.X.columns]
139
+
140
+ def _resolve_stopwords(self) -> set[str]:
141
+ if not self._coerce_bool(self.get_config('remove_stopwords'), True):
142
+ return set()
143
+
144
+ stopwords = self.get_config('stopwords')
145
+ if stopwords is None:
146
+ return set()
147
+
148
+ if isinstance(stopwords, str):
149
+ normalized = stopwords.strip().lower()
150
+ if normalized in {'', 'none', 'false', 'off', 'no'}:
151
+ return set()
152
+ if normalized in {'english', 'sklearn'}:
153
+ return set(ENGLISH_STOP_WORDS)
154
+ tokens = re.split(r'[,;\s]+', stopwords.strip())
155
+ return {token for token in tokens if token}
156
+
157
+ if isinstance(stopwords, (list, tuple, set, frozenset)):
158
+ return {
159
+ str(word).strip()
160
+ for word in stopwords
161
+ if word is not None and str(word).strip()
162
+ }
163
+
164
+ return set()
165
+
166
+ def _normalize_stopwords(self, stopwords: set[str]) -> set[str]:
167
+ if not stopwords:
168
+ return set()
169
+
170
+ normalized: set[str] = set()
171
+ for word in stopwords:
172
+ token = str(word).strip()
173
+ if not token:
174
+ continue
175
+ if self._lowercase:
176
+ token = token.lower()
177
+ if self._remove_punctuation:
178
+ token = _PUNCTUATION_RE.sub(' ', token)
179
+ token = _MULTISPACE_RE.sub(' ', token).strip()
180
+ if not token:
181
+ continue
182
+ normalized.update(token.split())
183
+
184
+ return normalized
185
+
186
+ def _build_stopword_pattern(self, stopwords: set[str]) -> re.Pattern | None:
187
+ if not stopwords:
188
+ return None
189
+
190
+ escaped = [re.escape(word) for word in stopwords if word]
191
+ if not escaped:
192
+ return None
193
+
194
+ escaped.sort(key=len, reverse=True)
195
+ flags = re.IGNORECASE if not self._lowercase else 0
196
+ pattern = r'\b(?:' + '|'.join(escaped) + r')\b'
197
+ return re.compile(pattern, flags=flags)
198
+
199
+ def _operations_summary(self) -> list[str]:
200
+ operations: list[str] = []
201
+ if self._lowercase:
202
+ operations.append('lowercasing')
203
+ if self._remove_punctuation:
204
+ operations.append('punctuation removal')
205
+ if self._stopword_pattern is not None:
206
+ operations.append('stopword filtering')
207
+ if self._collapse_whitespace:
208
+ operations.append('whitespace cleanup')
209
+ if not operations:
210
+ operations.append('normalization')
211
+ return operations
212
+
213
+ def _has_enabled_operations(self, configured_only: bool = False) -> bool:
214
+ if configured_only:
215
+ lowercase = self._coerce_bool(self.get_config('lowercase'), True)
216
+ remove_punctuation = self._coerce_bool(self.get_config('remove_punctuation'), True)
217
+ collapse_whitespace = self._coerce_bool(self.get_config('collapse_whitespace'), True)
218
+ remove_stopwords = self._coerce_bool(self.get_config('remove_stopwords'), True)
219
+ if lowercase or remove_punctuation or collapse_whitespace:
220
+ return True
221
+ if remove_stopwords:
222
+ return bool(self._resolve_stopwords())
223
+ return False
224
+
225
+ if self._lowercase or self._remove_punctuation or self._collapse_whitespace:
226
+ return True
227
+ return self._stopword_pattern is not None
228
+
229
+ @staticmethod
230
+ def _coerce_bool(value: object, default: bool) -> bool:
231
+ if isinstance(value, bool):
232
+ return value
233
+ if isinstance(value, str):
234
+ normalized = value.strip().lower()
235
+ if normalized in {'true', '1', 'yes', 'y'}:
236
+ return True
237
+ if normalized in {'false', '0', 'no', 'n'}:
238
+ return False
239
+ if value is None:
240
+ return default
241
+ return bool(value)
@@ -0,0 +1,80 @@
1
+ """[STEP] Vectorize textual columns with TF-IDF"""
2
+ import textwrap
3
+ from typing import Any
4
+ import pandas as pd
5
+ from sklearn.feature_extraction.text import TfidfVectorizer
6
+ from ...actionable import Actionable
7
+ from ...dataset import Dataset
8
+ from ...candidate import Candidate
9
+ from ...decorators.all import is_step
10
+ from ...data_type import DataType
11
+
12
+
13
+ @is_step('cleaning')
14
+ class ActTfIdf(Actionable):
15
+ """[STEP] Vectorize textual columns with TF-IDF"""
16
+
17
+ name: str = 'TF-IDF'
18
+ _description: str = textwrap.dedent('''\
19
+ Process "Term Frequency / Inversed Document Frequency"
20
+ over a list of textual columns''')
21
+ _description_long: str = textwrap.dedent('''\
22
+ This algorithm is used to evaluate the importance of a word inside
23
+ it\'s corpus. A word with a lot of repetitions will
24
+ have more importance than a word appearing once.''')
25
+ _usage: str = 'Use when short text needs weighted term features; prefer over ActCountVectorizer for damping frequent terms. Applicable to short text columns. Avoid when you need raw counts or want to drop the column (ActCountVectorizer, ActDropCategoricalColumn).'
26
+ refs: list[dict[str, Any]] = [
27
+ {
28
+ 'year': 1972,
29
+ 'name': 'A STATISTICAL INTERPRETATION OF TERM SPECIFICITY AND ITS APPLICATION \
30
+ IN RETRIEVAL',
31
+ 'authors': [
32
+ 'Karen Sparck Jones'
33
+ ],
34
+ 'doi': 'https://doi.org/10.1108/eb026526',
35
+ 'publisher': 'Journal of Documentation Vol.21, No.1, page 11--21'
36
+ }
37
+ ]
38
+
39
+ def __init__(self):
40
+ self.columns: list[tuple[str, TfidfVectorizer]] = None
41
+
42
+ def fit(self, dataset: Dataset) -> Actionable:
43
+ self.columns = []
44
+ for column in dataset.get_columns_names_by_type([DataType.SHORT_TEXT]):
45
+ values = dataset.X[column].fillna('')
46
+ vectorizer = TfidfVectorizer().fit(values)
47
+ self.columns.append((column, vectorizer))
48
+
49
+ feature_names = { c: v.get_feature_names_out() for c, v in self.columns }
50
+ self.explanations = [
51
+ f'Encoded text column **`{c}`** into **{len(v)}** new columns.'
52
+ for c, v in feature_names.items() if len(v) > 0
53
+ ]
54
+
55
+ return self
56
+
57
+ def suitable(self, dataset: Dataset) -> bool:
58
+ return bool(dataset.get_columns_names_by_type(DataType.SHORT_TEXT))
59
+
60
+ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
61
+ """Apply TF-IDF on textual columns.
62
+
63
+ :param pd.DataFrame x: DataFrame to transform.
64
+ :return: Transformed dataset.
65
+ """
66
+ # Without Reset index, the join with vector_df will create NAN (index mismatch)
67
+ X = X.reset_index(drop=True)
68
+
69
+ for name, vectorizer in self.columns:
70
+ transformed = vectorizer.transform(X[name].fillna(''))
71
+
72
+ new_names = list(map(lambda x: "_".join([name, x]), vectorizer.get_feature_names_out())) # pylint: disable=cell-var-from-loop
73
+ vector_df = pd.DataFrame(transformed.todense(), columns=new_names)
74
+
75
+ X = pd.concat([X, vector_df], axis=1).drop([name], axis=1)
76
+
77
+ return X
78
+
79
+ def priorize(self, candidate: Candidate = None) -> float:
80
+ return 0.4