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
iaml/logger.py ADDED
@@ -0,0 +1,138 @@
1
+ """Singleton used by IAML to generate nice logs"""
2
+ from enum import Enum
3
+ import warnings
4
+ import rich.console
5
+ import rich.progress
6
+ import multiprocess
7
+ from multiprocess.queues import Empty as MpEmpty
8
+
9
+ try:
10
+ from queue import Empty as ThreadEmpty
11
+ except ImportError: # pragma: no cover
12
+ ThreadEmpty = MpEmpty
13
+
14
+ from .meta_singleton import MetaSingleton
15
+
16
+
17
+ class LogType(Enum):
18
+ """Types of log messages"""
19
+ INFO = 1
20
+ WARNING = 2
21
+ ERROR = 3
22
+
23
+
24
+ class Logger(metaclass=MetaSingleton):
25
+ """Singleton used by IAML to generate nice logs
26
+
27
+ :param int, optional verbose: Logger verbosity. Default to 1.
28
+
29
+ * 0 -> No print
30
+ * 1 -> Progressbar only
31
+ * 2 -> progressbar + infos
32
+ * 3 -> progressbar + infos + warning
33
+ * 4 -> progressbar + infos + warning + error
34
+ * -1 -> error and progressbar
35
+ """
36
+ def __init__(self, verbose: int = 1) -> None:
37
+ self.console: rich.console = rich.console.Console(log_path=False)
38
+ """Console used by the logger"""
39
+
40
+ self.progress: rich.progress = rich.progress.Progress(console=self.console)
41
+ """Progress bar used by the logger"""
42
+
43
+ self.verbose: int = verbose
44
+ """Loagger verbosity"""
45
+
46
+ try:
47
+ self.log_queue = multiprocess.Queue()
48
+ self._queue_enabled = True
49
+ except (OSError, PermissionError) as exc:
50
+ warnings.warn(f"Logger running without multiprocess queue ({exc!r})")
51
+ self.log_queue = None
52
+ self._queue_enabled = False
53
+ """Queue used by the logger to handle log from various process"""
54
+
55
+ self.callback: callable = None
56
+ """Logger callback"""
57
+
58
+ @property
59
+ def verbose(self) -> int:
60
+ """Get logger verbosity
61
+
62
+ :return: Logger verbosity
63
+ """
64
+ return self.__verbose
65
+
66
+ @verbose.setter
67
+ def verbose(self, value: int) -> int:
68
+ """Set logger verbosity
69
+
70
+ :param int value: The new logger verbosity
71
+ :return: The new logger verbosity
72
+ """
73
+ self.__verbose = max(min(value, 4), -1)
74
+ self.console.quiet = self.__verbose == 0
75
+ return self.__verbose
76
+
77
+ def __log(self, log_type: LogType, *text: list[str]) -> None:
78
+ """Show text in console
79
+
80
+ :param LogType log_type: Logger type
81
+ :param list[str] \\*text: Text to log in console
82
+ """
83
+ proc_name = multiprocess.current_process().name
84
+ if proc_name == 'MainProcess' or not self._queue_enabled:
85
+ if proc_name != 'MainProcess':
86
+ self.__print(f"[{proc_name}]", *text)
87
+ else:
88
+ self.__print(*text)
89
+ else:
90
+ self.log_queue.put((log_type, f"[{proc_name}]", *text))
91
+
92
+
93
+ def __print(self, *text: list[str]) -> None:
94
+ """Call logger callback or show text in console
95
+
96
+ :param list[str] \\*text: Text to log in console
97
+ """
98
+ if self.callback is not None:
99
+ self.callback(*text)
100
+ else:
101
+ self.console.log(*text)
102
+
103
+
104
+ def info(self, *text: list[str]) -> None:
105
+ """Show info text in console
106
+
107
+ :param list[str] \\*text: Info Text to log in console
108
+ """
109
+ if self.verbose > 1:
110
+ self.__log(LogType.INFO, *text)
111
+
112
+ def warning(self, *text: list[str]) -> None:
113
+ """Show warning text in console
114
+
115
+ :param list[str] \\*text: Warning Text to log in console
116
+ """
117
+ if self.verbose > 2:
118
+ self.__log(LogType.WARNING, *text)
119
+
120
+ def error(self, *text: list[str]) -> None:
121
+ """Show info text in console
122
+
123
+ :param list[str] \\*text: Error Text to log in console
124
+ """
125
+ if self.verbose > 3 or self.verbose == -1:
126
+ self.__log(LogType.ERROR, *text)
127
+
128
+ def print_queue(self):
129
+ """Print all texts from subProcess"""
130
+ if not self._queue_enabled or self.log_queue is None:
131
+ return
132
+
133
+ while not self.log_queue.empty():
134
+ try:
135
+ messages = self.log_queue.get(block=False)
136
+ self.__print(*messages)
137
+ except (MpEmpty, ThreadEmpty):
138
+ break
@@ -0,0 +1,62 @@
1
+ """[METASTEP] Explore all sub steps in Thread and return one candidate by Sub Step"""
2
+ from .metastep import MetaStep
3
+ from .candidate import Candidate
4
+ from .step import Step
5
+ from .decorators.all import is_step, runner
6
+ from .worker_manager import WorkerFuture, WorkerManager
7
+
8
+ #
9
+ # Inherit from MetaStep but will execute all steps at the same time.
10
+ # The goal here is to explore many answer to a question. For example -> Try all Learning models
11
+ #
12
+ @is_step('meta')
13
+ class MetaExplorerStep(MetaStep):
14
+ """[METASTEP] Explore all sub steps in Thread and return one candidate by Sub Step"""
15
+
16
+ name: str = "MetaExplorerStep"
17
+ _description: str = 'Execute all steps and keep the best result'
18
+ _description_long: str = None
19
+ _usage: str = "Use when running all child steps in parallel to pick the best result, rather than MetaOrderedStep or MetaPartialExplorerStep. Applicable to interchangeable steps on the same candidate input. Avoid when you need deterministic ordering, a single path, or lower compute."
20
+
21
+ def __init__(self, *args, also_explore_without: bool = False, **kwargs): # pylint: disable=unused-argument
22
+ self.candidate = []
23
+ self.also_explore_without = also_explore_without
24
+
25
+ def json_pipeline(self) -> dict:
26
+ json = Step.json_pipeline(self)
27
+ json['children'] = list(map(lambda step: step.json_pipeline(), self.steps))
28
+ return json
29
+
30
+ def add_step(self, step: Step) -> None:
31
+ if Step in step.__class__.__mro__:
32
+ step.is_interchangeable = True
33
+
34
+ # Define enable only if False because True can generate strange behavior.
35
+ # True is default value anyway
36
+ if not self._enable:
37
+ step.enable = False
38
+
39
+ self.steps.append(self.configure_child(step))
40
+ else:
41
+ raise ValueError("step must be an occurrence of step (or inherited classes)")
42
+
43
+ # Explore all steps
44
+ @runner
45
+ def run(self, candidate: Candidate) -> Candidate:
46
+ output = []
47
+
48
+ # Try a candidate without any of explored steps
49
+ if self.also_explore_without:
50
+ output += [candidate.to_output()]
51
+
52
+ workers: list[WorkerFuture] = []
53
+
54
+ for step in self.steps:
55
+ future = WorkerManager().submit(step, step.run, candidate.to_input())
56
+ workers.append(future)
57
+
58
+ # Wait end of all threads
59
+ for worker in workers:
60
+ output += worker.result()
61
+
62
+ return output
@@ -0,0 +1,28 @@
1
+ """
2
+ Group several Step and run them in list order
3
+ """
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .decorators.all import is_step, runner
7
+ from .metastep import MetaStep
8
+
9
+ if TYPE_CHECKING:
10
+ from .candidate import Candidate
11
+
12
+ #
13
+ # Inherit from MetaStep but will execute all steps without priorize() method.
14
+ #
15
+ @is_step('meta')
16
+ class MetaOrderedStep(MetaStep):
17
+ """
18
+ Group several Step and run them in list order
19
+ """
20
+ _usage: str = "Use when you need strict list-ordered execution of child steps instead of MetaStep prioritization. Applicable to pipelines where each step must run on the same Candidate in order. Avoid when steps are interchangeable and best-of exploration is needed (MetaExplorerStep)."
21
+ # Run steps self ordered by "priorize" function
22
+ @runner
23
+ def run(self, candidate:'Candidate') -> 'Candidate':
24
+ current_candidate: Candidate = candidate
25
+ for step in self.steps:
26
+ current_candidate = step.run(current_candidate)
27
+
28
+ return current_candidate
@@ -0,0 +1,34 @@
1
+ """[METASTEP] Generate pipeline without any exploration but optimizer
2
+ will be able to mutate into other steps
3
+ """
4
+ from .meta_explorer_step import MetaExplorerStep
5
+ from .step import Step
6
+ from .void_step import VoidStep
7
+ from .decorators.all import find_steps_by_tag, is_step
8
+
9
+ @is_step('meta')
10
+ class MetaPartialExplorerStep(MetaExplorerStep):
11
+ """[METASTEP] Generate pipeline without any exploration but optimizer will
12
+ be able to mutate into other steps
13
+
14
+ :param Step, optional initial_step: Run this step initially instead of a no-op.
15
+ When a tag is supplied, the step must carry that tag.
16
+ """
17
+
18
+ name: str = "MetaPartialExplorerStep"
19
+ _usage: str = "Use when you want one initial choice that the optimizer can later replace. Starts with initial_step when supplied, otherwise a no-op matching the tag. Avoid when you need immediate exploration or an optimizer that cannot replace steps."
20
+
21
+ def __init__(self, *args, tag: str = None, initial_step: Step = None,
22
+ **kwargs): # pylint: disable=unused-argument
23
+ self.steps = []
24
+ # Partial exploration always emits a single branch, including for a no-op.
25
+ self.also_explore_without = False
26
+ if initial_step is not None:
27
+ if not isinstance(initial_step, Step):
28
+ raise TypeError("initial_step must be a Step")
29
+ if tag is not None and tag not in (initial_step.tags or set()):
30
+ raise ValueError("initial_step must match the exploration tag")
31
+ self.add_step(initial_step)
32
+ elif steps := find_steps_by_tag(tag):
33
+ representative = min(steps, key=lambda step: (step.__module__, step.__name__))
34
+ self.add_step(VoidStep(step_to_mimic=representative()))
iaml/meta_singleton.py ADDED
@@ -0,0 +1,24 @@
1
+ """Thread-safe implementation of Singleton"""
2
+ from threading import Lock
3
+ from typing import Any
4
+
5
+ # Inspired by : https://refactoring.guru/fr/design-patterns/singleton/python/example#example-1
6
+ class MetaSingleton(type):
7
+ """Thread-safe implementation of Singleton"""
8
+ _instances = {}
9
+ _lock: Lock = Lock() # Use to synchronize threads during first access
10
+
11
+ def __call__(cls, *args, **kwargs) -> Any:
12
+ with cls._lock:
13
+ if cls not in cls._instances:
14
+ instance = super().__call__(*args, **kwargs)
15
+ cls._instances[cls] = instance
16
+ return cls._instances[cls]
17
+
18
+ @classmethod
19
+ def reset(mcs: 'MetaSingleton') -> None:
20
+ """Delete saved instances -> Reset the singleton
21
+
22
+ :param MetaSingleton mcs: The singleton to reset
23
+ """
24
+ mcs._instances = {}
iaml/metastep.py ADDED
@@ -0,0 +1,211 @@
1
+ """MetaStep is a direct child of Step and will carry and execute several other Steps
2
+ -> MetaStep will execute Step one by one, using the candidate of a step as candidate of
3
+ the next one.
4
+
5
+ The order of Step is defined by the priorize() method .
6
+
7
+ There is children classes of MetaStep to execute Steps in a different way
8
+ """
9
+ from typing import Any
10
+ from .step import Step
11
+ from .candidate import Candidate
12
+ from .decorators.all import find_steps_by_tag, is_step, runner
13
+ from .step_wrapper import StepWrapper
14
+
15
+
16
+ @is_step('meta')
17
+ class MetaStep(Step):
18
+ """MetaStep is a direct child of Step and will carry and execute several other Steps
19
+ -> MetaStep will execute Step one by one, using the candidate of a step as candidate of
20
+ the next one.
21
+
22
+ The order of Step is defined by the priorize() method
23
+
24
+ There is children classes of MetaStep to execute Steps in a different way
25
+
26
+ :param tuple, optional \\*args: Additional parameters.
27
+ :param str, optional str: The tag of the meta step. Default to None.
28
+ :param StepWrapper, optional wrap: If exist, will wrap Steps with it. Default to None.
29
+ :param str, optional name: Name of the MetaStep. Default to None.
30
+ :param str, optional description: Description of the MetaStep. Default to None.
31
+ :param dict, optional \\**kwargs: Additional parameters.
32
+ """
33
+ name: str = "Steps group"
34
+ description: str = 'Execute steps one by one'
35
+ _usage: str = "Use when you need a meta step to run several child Steps and pass candidates between them. Applicable to pipelines with multiple child steps or tagged steps producing Candidate outputs. Avoid when you need fixed ordering or exploration; consider MetaOrderedStep or MetaExplorerStep."
36
+ description_long: str = None
37
+
38
+ def __init__(
39
+ self,
40
+ *args,
41
+ tag: str = None,
42
+ wrap: StepWrapper = None,
43
+ name: str = None,
44
+ description: str = None,
45
+ **kwargs) -> None:
46
+ self.tag: str | None = None
47
+ """Optional tag used to populate this meta step."""
48
+ self.steps: list[Step] = [] # Initialize steps to empty
49
+ """List of steps"""
50
+
51
+ # If there is a tag -> add all Steps with this tag
52
+ if tag:
53
+ self.tag = tag
54
+ self.add_step_by_tag(tag, wrap=wrap)
55
+ self.name = f"Steps from : {tag}"
56
+
57
+ if name:
58
+ self.name = name
59
+ if name:
60
+ self.description = description
61
+
62
+ @classmethod
63
+ def from_pipeline(cls, pipeline: dict[str, Any], *args, **kwargs) -> Step:
64
+ metastep = super().from_pipeline(pipeline)
65
+
66
+ if 'children' in pipeline:
67
+ for child in pipeline['children']:
68
+ metastep.add_step(Step.from_pipeline(child))
69
+
70
+ if 'tag' in pipeline:
71
+ metastep.add_step_by_tag(pipeline['tag'])
72
+
73
+ if not metastep.steps:
74
+ raise TypeError('invalid pipeline: MetaStep must have at least one child')
75
+
76
+ return metastep
77
+
78
+ def configure_parents(self, *parents: list[Step]) -> None:
79
+ for step in self.steps:
80
+ step.configure_parents(*parents)
81
+
82
+ super().configure_parents(*parents)
83
+
84
+ def add_step(self, step: Step) -> None:
85
+ """Add one step to the MetaStep.
86
+ step must be a Step inherited class
87
+
88
+ :param Step step: Step to add
89
+ :raise ValueError: step must be an occurrence of step (or inherited classes)
90
+ """
91
+ if Step in step.__class__.__mro__:
92
+ step = self.configure_child(step)
93
+
94
+ # Define enable only if False because True can generate strange behavior.
95
+ # True is default value anyway
96
+ if not self._enable:
97
+ step.enable = False
98
+
99
+ self.steps.append(step)
100
+ else:
101
+ raise ValueError("step must be an occurrence of step (or inherited classes)")
102
+
103
+ def add_steps(self, steps: list[Step]) -> None:
104
+ """Add a list of Steps
105
+
106
+ :param list[Step] steps: List of steps to add to this MetaStep
107
+ """
108
+ for step in steps:
109
+ self.add_step(step)
110
+
111
+ def add_step_by_tag(self, tag: str, wrap: StepWrapper = None) -> None:
112
+ """Add all Step with this tag to the MetaStep
113
+ Wrap -> If exist, will wrap Steps with it. Check WrapperStep to know more
114
+
115
+ :param str tag: tag to search Steps.
116
+ :param StepWrapper, optional wrap: Wrap Step in it. Default to None.
117
+ """
118
+ if self.tag is None:
119
+ self.tag = tag
120
+ steps_to_add = find_steps_by_tag(tag)
121
+
122
+ if wrap is not None:
123
+ steps_to_add = list(map(lambda step: wrap(step()), steps_to_add))
124
+ else:
125
+ steps_to_add = list(map(lambda step: step(), steps_to_add))
126
+
127
+ for step in steps_to_add:
128
+ self.add_step(step)
129
+
130
+ def all_configurations(self) -> list[dict[str, Any]]:
131
+ to_return = Step.all_configurations(self)
132
+
133
+ for step in self.steps:
134
+ to_return = to_return + step.all_configurations()
135
+
136
+ return to_return
137
+
138
+ def json_pipeline(self) -> dict[str, Any]:
139
+ payload = {
140
+ **Step.json_pipeline(self),
141
+ 'children': [ step.json_pipeline() for step in self.steps ]
142
+ }
143
+ if self.tag:
144
+ payload['tag'] = self.tag
145
+ return payload
146
+
147
+ def all_steps(self) -> list[Step]:
148
+ children = []
149
+ for step in self.steps:
150
+ children += step.all_steps()
151
+
152
+ return [self, *children]
153
+
154
+ @runner
155
+ def run(self, candidate: Candidate) -> Candidate:
156
+ steps_to_run = self.steps.copy()
157
+ return self.__recursive_run(steps_to_run, [candidate])
158
+
159
+
160
+ def __recursive_run(self,
161
+ remain_steps: list[Step],
162
+ candidates: list[Candidate]) -> list[Candidate]:
163
+ """Recursive_run to manage Step with several candidates
164
+
165
+ :param list[Step] remain_steps: Steps remaining.
166
+ :param list[Candidate] candidates: Candidate of previous Step.
167
+ :return: Results.
168
+ """
169
+ output_candidates = []
170
+ if remain_steps:
171
+ for current_candidate in candidates:
172
+ max_eval = remain_steps[0].priorize(current_candidate)
173
+ max_index = 0
174
+ for index, step in enumerate(remain_steps[1:]):
175
+ current_eval = step.priorize(current_candidate)
176
+ if current_eval > max_eval:
177
+ max_eval = current_eval
178
+ max_index = index+1
179
+
180
+ results = remain_steps[max_index].run(current_candidate)
181
+ futures_steps = remain_steps.copy()
182
+ futures_steps.pop(max_index)
183
+ output_candidates = output_candidates + \
184
+ self.__recursive_run(futures_steps, results)
185
+
186
+ return output_candidates
187
+ return candidates
188
+
189
+ def conf_to_rich_str_list(self) -> str:
190
+ conf = super().conf_to_rich_str_list()
191
+ conf.append(f'steps={",".join({ step.__class__.__name__ for step in self.steps })}')
192
+
193
+ return conf
194
+
195
+ def count_steps(self) -> int:
196
+ return 1 + sum(map(lambda child: child.count_steps(), self.steps))
197
+
198
+
199
+ @property
200
+ def enable(self) -> bool:
201
+ return any(step.enable for step in self.steps) if hasattr(self, 'steps') else False
202
+
203
+ @enable.setter
204
+ def enable(self, value: bool) -> bool:
205
+ if hasattr(self, 'steps'):
206
+ for step in self.steps:
207
+ step.enable = value
208
+
209
+ self._enable = value # Only used has default value for new children
210
+
211
+ return self.enable
iaml/metric.py ADDED
@@ -0,0 +1,111 @@
1
+ """[METRIC] Parent of all others Metrics, implement the default behavior"""
2
+ from typing import Any
3
+ import pandas as pd
4
+ from .reference import Reference
5
+
6
+
7
+ class Metric:
8
+ """[METRIC] Parent of all others Metrics, implement the default behavior"""
9
+
10
+ greater_is_better: bool = True
11
+ """Whether larger values are better. Set to False for error metrics."""
12
+
13
+ name: str = ""
14
+ """Name of the metric"""
15
+
16
+ _description: str = ""
17
+ """Short description of the metric"""
18
+
19
+ _description_long: str = ""
20
+ """Long description of the metric"""
21
+
22
+ refs: list[dict[str, Any]] = []
23
+ """List of references for this metric."""
24
+
25
+ @classmethod
26
+ def all_subclasses(cls) -> list['Metric']:
27
+ """Return all metrics subclasses
28
+
29
+ :return: List of all metrics.
30
+ """
31
+ subclasses = cls.__subclasses__()
32
+ for subclass in subclasses:
33
+ subclasses += subclass.all_subclasses()
34
+ return subclasses
35
+
36
+ @classmethod
37
+ def get_refs(cls) -> list[Reference]:
38
+ """Get bibliography references
39
+
40
+ :return: List of references for this metric.
41
+ """
42
+ if hasattr(cls, 'refs'):
43
+ return [Reference(ref, cls.__name__) for ref in cls.refs]
44
+ return []
45
+
46
+ @property
47
+ def description(self) -> str:
48
+ """Formats the description of a step with its configuration.
49
+
50
+ :return: Formatted description.
51
+ """
52
+ return self._description.replace('\n', '')
53
+
54
+ @property
55
+ def description_long(self) -> str:
56
+ """Formats the longer description of a step with its configuration.
57
+
58
+ :return: Formatted description.
59
+ """
60
+ return self._description_long.replace('\n', '')
61
+
62
+ def __str__(self) -> str:
63
+ """Metric name
64
+
65
+ :return: Metric name.
66
+ """
67
+ return 'base_metric'
68
+
69
+ def explain(self) -> str:
70
+ """Describe metric
71
+
72
+ :return: Metric description
73
+ """
74
+ return self.description_long
75
+
76
+ def compute(self, y: pd.DataFrame, y_pred: pd.DataFrame, **kwargs) -> float:
77
+ """Compute metric given y, y_pred.
78
+ Must be overridden by children classes
79
+
80
+ :param pd.DataFrame y: Ground truth to compute the metric.
81
+ :param pd.DataFrame y_pred: Prediction to compute the metric.
82
+ :param dict, optional \\**kwargs: Additional parameters
83
+ :return: Computed value
84
+ """
85
+ raise NotImplementedError('Subclass must implement abstract method')
86
+
87
+ def suitable(self, X: pd.DataFrame, y: pd.DataFrame, type_of_target: str) -> bool: # pylint: disable=unused-argument
88
+ """Is this metric usable given X and y ?
89
+
90
+ :param pd.DataFrame X: The dataset we try to compute metrics on.
91
+ :param pd.DataFrame y: The dataset target we try to compute metrics on
92
+ :param str type_of_target: The type of target we are trying to predict
93
+ :return: Suitable ?
94
+ """
95
+ return False
96
+
97
+ @property
98
+ def needed_prediction(self) -> str:
99
+ """Which kind of predict is needed by the metric
100
+
101
+ :return: Method name.
102
+ """
103
+ return "predict"
104
+
105
+ @property
106
+ def name(self) -> str:
107
+ """Return the metric formatted name
108
+
109
+ :return: Formatted name.
110
+ """
111
+ return ' '.join(x.title() for x in str(self).split('_'))
iaml/metric_plot.py ADDED
@@ -0,0 +1,82 @@
1
+ """[PLOT] Parent for all metrics plots."""
2
+ from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Type
4
+
5
+ import io
6
+ import pandas as pd
7
+
8
+ from yellowbrick.base import Visualizer
9
+
10
+ from .plot import capture, Plot
11
+
12
+ if TYPE_CHECKING:
13
+ from .iaml_pipeline import IAMLPipeline
14
+
15
+
16
+ class MetricPlot(Plot):
17
+ """To be used by performance Explainer"""
18
+
19
+ def compute(
20
+ self,
21
+ estimator: IAMLPipeline,
22
+ X: pd.DataFrame,
23
+ y: pd.Series,
24
+ X_train: pd.DataFrame = None,
25
+ y_train: pd.Series = None,
26
+ **kwargs) -> 'MetricPlot':
27
+ """Compute plot given X, y.
28
+ Must be overridden by children classes
29
+
30
+ :param IAMLPipeline estimator: The pipeline we compute the plot on.
31
+ :param pd.DataFrame X: The dataset we wanna compute plot on.
32
+ :param pd.Series y: The dataset target we wanna compute plot on.
33
+ :param pd.DataFrame, optional X_train: The dataframe used for training.
34
+ :param pd.Series, optional y_train: The dataframe target used for training.
35
+ :param optional \\**kwargs: Additional parameters for plotting.
36
+ :return: A MetricPlot object.
37
+ """
38
+ raise NotImplementedError('Subclass must implement abstract method')
39
+
40
+
41
+ def _estimator_is_fitted(estimator) -> bool:
42
+ checker = getattr(estimator, "__sklearn_is_fitted__", None)
43
+ if callable(checker):
44
+ try:
45
+ fitted = bool(checker())
46
+ except Exception: # noqa: BLE001
47
+ return False
48
+ if fitted and hasattr(estimator, "predictor"):
49
+ predictor = getattr(estimator, "predictor", None)
50
+ if predictor and isinstance(predictor, tuple):
51
+ model = getattr(predictor[1], "model", None)
52
+ if model is None:
53
+ return False
54
+ return fitted
55
+ return False
56
+
57
+
58
+ def yellowbrick_plot(yellowbrick_visualizer: Type[Visualizer]):
59
+ """Decorator for creating metric plots based on Yellowbrick visualizers.
60
+
61
+ :param Type[Visualizer] yellowbrick_visualizer: Base Yellowbrick visualizer.
62
+ """
63
+ def decorator(cls):
64
+ @capture
65
+ def compute(self, estimator, X, y, X_train = None, y_train = None, **kwargs): # pylint: disable=missing-function-docstring, unused-argument
66
+ self._binary_image = io.BytesIO() # pylint: disable=protected-access
67
+ already_fitted = _estimator_is_fitted(estimator)
68
+ visualizer = yellowbrick_visualizer(estimator, is_fitted=already_fitted)
69
+
70
+ if X_train is not None and y_train is not None and not already_fitted:
71
+ visualizer.fit(X_train, y_train)
72
+
73
+ visualizer.score(X, y)
74
+ visualizer.poof(self._binary_image) # pylint: disable=protected-access
75
+
76
+ return self
77
+
78
+ cls.compute = compute
79
+
80
+ return cls
81
+
82
+ return decorator