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