PyIAML 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iaml/__init__.py +56 -0
- iaml/actionable.py +11 -0
- iaml/actionables/__init__.py +21 -0
- iaml/actionables/boosting/__init__.py +4 -0
- iaml/actionables/boosting/act_adaboost.py +59 -0
- iaml/actionables/cleaning/__init__.py +26 -0
- iaml/actionables/cleaning/act_categorical_imputer.py +124 -0
- iaml/actionables/cleaning/act_count_vectorizer.py +204 -0
- iaml/actionables/cleaning/act_drop_categorical_column.py +51 -0
- iaml/actionables/cleaning/act_drop_date_column.py +48 -0
- iaml/actionables/cleaning/act_drop_high_cardinality_categorical.py +337 -0
- iaml/actionables/cleaning/act_drop_numerical_column.py +75 -0
- iaml/actionables/cleaning/act_drop_textual_column.py +51 -0
- iaml/actionables/cleaning/act_encode_target_column.py +56 -0
- iaml/actionables/cleaning/act_frequency_encoder.py +127 -0
- iaml/actionables/cleaning/act_hashing_vectorizer.py +186 -0
- iaml/actionables/cleaning/act_knn_imputer.py +152 -0
- iaml/actionables/cleaning/act_mean_column.py +79 -0
- iaml/actionables/cleaning/act_mice.py +464 -0
- iaml/actionables/cleaning/act_missing_count_feature.py +109 -0
- iaml/actionables/cleaning/act_missing_indicator.py +124 -0
- iaml/actionables/cleaning/act_onehot.py +65 -0
- iaml/actionables/cleaning/act_ordinal_encoder.py +177 -0
- iaml/actionables/cleaning/act_rare_category_grouper.py +173 -0
- iaml/actionables/cleaning/act_simple_imputer.py +109 -0
- iaml/actionables/cleaning/act_split_date.py +68 -0
- iaml/actionables/cleaning/act_target_encoder.py +274 -0
- iaml/actionables/cleaning/act_text_normalizer.py +241 -0
- iaml/actionables/cleaning/act_tf_idf.py +80 -0
- iaml/actionables/cleaning/act_word2vec.py +150 -0
- iaml/actionables/features_precleaning/__init__.py +12 -0
- iaml/actionables/features_precleaning/act_coerce_numeric_strings.py +194 -0
- iaml/actionables/features_precleaning/act_date_converter.py +99 -0
- iaml/actionables/features_precleaning/act_drop_bad_quality_rows.py +77 -0
- iaml/actionables/features_precleaning/act_drop_duplicate_rows.py +131 -0
- iaml/actionables/features_precleaning/act_drop_high_missing_columns.py +94 -0
- iaml/actionables/features_precleaning/act_drop_id_like_columns.py +294 -0
- iaml/actionables/features_precleaning/act_normalize_column_names.py +157 -0
- iaml/actionables/features_precleaning/act_sentinel_to_na_n.py +270 -0
- iaml/actionables/features_precleaning/act_trim_space.py +79 -0
- iaml/actionables/features_preprocessing/__init__.py +18 -0
- iaml/actionables/features_preprocessing/act_cyclical_date_encoding.py +212 -0
- iaml/actionables/features_preprocessing/act_fast_ica.py +161 -0
- iaml/actionables/features_preprocessing/act_feature_agglomeration.py +90 -0
- iaml/actionables/features_preprocessing/act_k_bins_discretizer.py +207 -0
- iaml/actionables/features_preprocessing/act_k_means_features.py +296 -0
- iaml/actionables/features_preprocessing/act_kernel_pca.py +143 -0
- iaml/actionables/features_preprocessing/act_log_transformer.py +122 -0
- iaml/actionables/features_preprocessing/act_nystroem.py +100 -0
- iaml/actionables/features_preprocessing/act_pca.py +77 -0
- iaml/actionables/features_preprocessing/act_polynomial_features.py +86 -0
- iaml/actionables/features_preprocessing/act_power_transformer.py +106 -0
- iaml/actionables/features_preprocessing/act_quantile_transformer.py +114 -0
- iaml/actionables/features_preprocessing/act_rbf_sampler.py +88 -0
- iaml/actionables/features_preprocessing/act_select_percentile.py +112 -0
- iaml/actionables/features_preprocessing/act_sparse_random_projection.py +157 -0
- iaml/actionables/features_preprocessing/act_truncated_svd.py +137 -0
- iaml/actionables/features_selection/__init__.py +8 -0
- iaml/actionables/features_selection/act_permutation_importance_selector.py +421 -0
- iaml/actionables/features_selection/act_remove_high_correlated_column.py +70 -0
- iaml/actionables/features_selection/act_remove_low_variance_column.py +74 -0
- iaml/actionables/features_selection/act_rfe.py +214 -0
- iaml/actionables/features_selection/act_select_from_model.py +325 -0
- iaml/actionables/features_selection/act_select_k_best.py +181 -0
- iaml/actionables/features_selection/act_vif_selector.py +130 -0
- iaml/actionables/imbalance/__init__.py +10 -0
- iaml/actionables/imbalance/act_adasyn.py +150 -0
- iaml/actionables/imbalance/act_borderline_smote.py +171 -0
- iaml/actionables/imbalance/act_near_miss.py +158 -0
- iaml/actionables/imbalance/act_random_over_sampling.py +60 -0
- iaml/actionables/imbalance/act_random_under_sampler.py +135 -0
- iaml/actionables/imbalance/act_smote.py +162 -0
- iaml/actionables/imbalance/act_smote_tomek.py +182 -0
- iaml/actionables/imbalance/act_smoteenn.py +193 -0
- iaml/actionables/imbalance/act_tomek_links.py +138 -0
- iaml/actionables/normalize/__init__.py +6 -0
- iaml/actionables/normalize/act_max_abs_scaler.py +78 -0
- iaml/actionables/normalize/act_minmax_scaler.py +56 -0
- iaml/actionables/normalize/act_normalizer.py +95 -0
- iaml/actionables/normalize/act_robust_scaler.py +111 -0
- iaml/actionables/normalize/act_standard_scaler.py +55 -0
- iaml/actionables/predictors/__init__.py +6 -0
- iaml/actionables/predictors/_xgboost.py +16 -0
- iaml/actionables/predictors/classifier/__init__.py +26 -0
- iaml/actionables/predictors/classifier/act_bagging_classifier.py +113 -0
- iaml/actionables/predictors/classifier/act_bernoulli_nb.py +89 -0
- iaml/actionables/predictors/classifier/act_catboost_classifier.py +135 -0
- iaml/actionables/predictors/classifier/act_complement_nb.py +106 -0
- iaml/actionables/predictors/classifier/act_decision_tree_classifier.py +117 -0
- iaml/actionables/predictors/classifier/act_extra_trees_classifier.py +115 -0
- iaml/actionables/predictors/classifier/act_gaussian_nb.py +53 -0
- iaml/actionables/predictors/classifier/act_hist_gradient_boosting_classifier.py +144 -0
- iaml/actionables/predictors/classifier/act_knn.py +86 -0
- iaml/actionables/predictors/classifier/act_light_gbm_classifier.py +211 -0
- iaml/actionables/predictors/classifier/act_linear_discriminant_analysis.py +63 -0
- iaml/actionables/predictors/classifier/act_linear_svc.py +134 -0
- iaml/actionables/predictors/classifier/act_logistic_regression.py +92 -0
- iaml/actionables/predictors/classifier/act_mlp_classifier.py +107 -0
- iaml/actionables/predictors/classifier/act_multinomial_nb.py +76 -0
- iaml/actionables/predictors/classifier/act_passive_aggressive_classifier.py +141 -0
- iaml/actionables/predictors/classifier/act_quadratic_discriminant_analysis.py +72 -0
- iaml/actionables/predictors/classifier/act_randomforest.py +113 -0
- iaml/actionables/predictors/classifier/act_ridge_classifier.py +116 -0
- iaml/actionables/predictors/classifier/act_sgd_classifier.py +149 -0
- iaml/actionables/predictors/classifier/act_svm_svc.py +88 -0
- iaml/actionables/predictors/classifier/act_xgboost.py +111 -0
- iaml/actionables/predictors/regressor/__init__.py +27 -0
- iaml/actionables/predictors/regressor/act_ada_boost_regressor.py +75 -0
- iaml/actionables/predictors/regressor/act_ard_regression.py +95 -0
- iaml/actionables/predictors/regressor/act_catboost_regressor.py +134 -0
- iaml/actionables/predictors/regressor/act_decision_tree_regressor.py +111 -0
- iaml/actionables/predictors/regressor/act_elastic_net_regressor.py +109 -0
- iaml/actionables/predictors/regressor/act_extra_trees_regressor.py +113 -0
- iaml/actionables/predictors/regressor/act_gaussian_process_regressor.py +55 -0
- iaml/actionables/predictors/regressor/act_gboost_regressor.py +95 -0
- iaml/actionables/predictors/regressor/act_hist_gradient_boosting_regressor.py +105 -0
- iaml/actionables/predictors/regressor/act_huber_regressor.py +101 -0
- iaml/actionables/predictors/regressor/act_knn_regressor.py +86 -0
- iaml/actionables/predictors/regressor/act_lasso_regressor.py +103 -0
- iaml/actionables/predictors/regressor/act_light_gbm_regressor.py +201 -0
- iaml/actionables/predictors/regressor/act_linear_regression.py +43 -0
- iaml/actionables/predictors/regressor/act_mlp_regressor.py +104 -0
- iaml/actionables/predictors/regressor/act_poisson_regressor.py +111 -0
- iaml/actionables/predictors/regressor/act_quantile_regressor.py +87 -0
- iaml/actionables/predictors/regressor/act_randomforest_regressor.py +116 -0
- iaml/actionables/predictors/regressor/act_ransac_regressor.py +106 -0
- iaml/actionables/predictors/regressor/act_ridge_regressor.py +107 -0
- iaml/actionables/predictors/regressor/act_sgd_regressor.py +106 -0
- iaml/actionables/predictors/regressor/act_svm_svr.py +81 -0
- iaml/actionables/predictors/regressor/act_xgboost_regressor.py +97 -0
- iaml/actionables/predictors/survival/__init__.py +12 -0
- iaml/actionables/predictors/survival/act_aalen_additive_model.py +83 -0
- iaml/actionables/predictors/survival/act_cox.py +110 -0
- iaml/actionables/predictors/survival/act_coxnet_survival_analysis.py +134 -0
- iaml/actionables/predictors/survival/act_extra_survival_trees.py +101 -0
- iaml/actionables/predictors/survival/act_fast_survival_svm.py +102 -0
- iaml/actionables/predictors/survival/act_gradient_boosting_survival_analysis.py +93 -0
- iaml/actionables/predictors/survival/act_random_survival_forest.py +91 -0
- iaml/actionables/predictors/survival/act_survival_component_wise_gboost.py +80 -0
- iaml/actionables/predictors/survival/act_survival_tree.py +120 -0
- iaml/actionables/predictors/survival/act_survival_xgboost.py +9 -0
- iaml/actionables/predictors/survival/act_weibull_aft.py +230 -0
- iaml/cache.py +61 -0
- iaml/cache_keys.py +57 -0
- iaml/candidate.py +736 -0
- iaml/core_dispatcher.py +125 -0
- iaml/data_type.py +11 -0
- iaml/dataset.py +506 -0
- iaml/decorators/__init__.py +3 -0
- iaml/decorators/all.py +4 -0
- iaml/decorators/is_step.py +45 -0
- iaml/decorators/runner.py +100 -0
- iaml/explanation.py +112 -0
- iaml/iaml.py +1072 -0
- iaml/iaml_pipeline.py +600 -0
- iaml/logger.py +138 -0
- iaml/meta_explorer_step.py +62 -0
- iaml/meta_ordered_step.py +28 -0
- iaml/meta_partial_explorer_step.py +34 -0
- iaml/meta_singleton.py +24 -0
- iaml/metastep.py +211 -0
- iaml/metric.py +111 -0
- iaml/metric_plot.py +82 -0
- iaml/metrics/__init__.py +21 -0
- iaml/metrics/_classification.py +28 -0
- iaml/metrics/_survival_times.py +22 -0
- iaml/metrics/accuracy_metric.py +59 -0
- iaml/metrics/balanced_accuracy_metric.py +67 -0
- iaml/metrics/brier_score.py +90 -0
- iaml/metrics/classification_error_metric.py +66 -0
- iaml/metrics/concordance_index_ipcw.py +84 -0
- iaml/metrics/concordance_index_metric.py +67 -0
- iaml/metrics/cumulative_dynamic_auc.py +119 -0
- iaml/metrics/f1_score_metric.py +71 -0
- iaml/metrics/integrated_brier_score.py +98 -0
- iaml/metrics/integrated_brier_score_loss.py +41 -0
- iaml/metrics/mean_absolute_error_metric.py +46 -0
- iaml/metrics/mean_squared_error_metric.py +46 -0
- iaml/metrics/mean_squared_log_error_metric.py +49 -0
- iaml/metrics/median_absolute_error_metric.py +48 -0
- iaml/metrics/precision_metric.py +63 -0
- iaml/metrics/r2_score_metric.py +45 -0
- iaml/metrics/recall_metric.py +65 -0
- iaml/metrics/roc_auc_metric.py +50 -0
- iaml/metrics/specificity_metric.py +44 -0
- iaml/metrics/specificity_multiclass_metric.py +55 -0
- iaml/metrics/specificity_multilabel_metric.py +60 -0
- iaml/optimizers/__init__.py +5 -0
- iaml/optimizers/bayesian_optimizer.py +193 -0
- iaml/optimizers/genetic_optimizer.py +284 -0
- iaml/optimizers/optimizer.py +31 -0
- iaml/optimizers/random_optimizer.py +101 -0
- iaml/plot.py +138 -0
- iaml/plots/__init__.py +32 -0
- iaml/plots/bar_plot.py +141 -0
- iaml/plots/box_plot.py +166 -0
- iaml/plots/class_prediction_error_plot.py +37 -0
- iaml/plots/classification_report_plot.py +35 -0
- iaml/plots/confusion_matrix_plot.py +34 -0
- iaml/plots/correlation_heatmap_plot.py +201 -0
- iaml/plots/cumulative_hazard_plot.py +72 -0
- iaml/plots/density_plot.py +210 -0
- iaml/plots/histogram_plot.py +179 -0
- iaml/plots/kaplan_meier_comparison_plot.py +89 -0
- iaml/plots/line_plot.py +70 -0
- iaml/plots/missingness_heatmap_plot.py +203 -0
- iaml/plots/outlier_plot.py +217 -0
- iaml/plots/pair_plot.py +228 -0
- iaml/plots/precision_recall_curve_plot.py +86 -0
- iaml/plots/prediction_error_plot.py +34 -0
- iaml/plots/qq_plot.py +220 -0
- iaml/plots/residual_plot.py +38 -0
- iaml/plots/roc_dynamique_curve_plot.py +79 -0
- iaml/plots/rocauc_plot.py +96 -0
- iaml/plots/shap_plot.py +187 -0
- iaml/plots/target_distribution_plot.py +241 -0
- iaml/plots/violin_plot.py +206 -0
- iaml/predictor.py +139 -0
- iaml/reference.py +65 -0
- iaml/shared_cache.py +90 -0
- iaml/sklearn_preprocessor.py +74 -0
- iaml/splitters/__init__.py +3 -0
- iaml/splitters/kfold_splitter.py +32 -0
- iaml/splitters/random_splitter.py +26 -0
- iaml/stack.py +39 -0
- iaml/statistic.py +66 -0
- iaml/statistics/__init__.py +77 -0
- iaml/statistics/anova_statistic.py +80 -0
- iaml/statistics/cardinality_ratio_statistic.py +63 -0
- iaml/statistics/category_cooccurrence_statistic.py +79 -0
- iaml/statistics/chi_square_statistic.py +81 -0
- iaml/statistics/coef_variation_statistic.py +72 -0
- iaml/statistics/correlation_with_target.py +105 -0
- iaml/statistics/count.py +72 -0
- iaml/statistics/data_type_summary_statistic.py +74 -0
- iaml/statistics/duplicate_row_statistic.py +56 -0
- iaml/statistics/effect_size_statistic.py +129 -0
- iaml/statistics/entropy_statistic.py +69 -0
- iaml/statistics/event_rate_statistic.py +52 -0
- iaml/statistics/grouped_mean_statistic.py +60 -0
- iaml/statistics/iqr_statistic.py +66 -0
- iaml/statistics/kurtosis.py +50 -0
- iaml/statistics/mad_statistic.py +66 -0
- iaml/statistics/mean.py +61 -0
- iaml/statistics/median_statistic.py +61 -0
- iaml/statistics/minmax.py +60 -0
- iaml/statistics/missing_rate_statistic.py +62 -0
- iaml/statistics/mode.py +47 -0
- iaml/statistics/most_frequent_ratio.py +81 -0
- iaml/statistics/outlier_count_iqr_statistic.py +76 -0
- iaml/statistics/quantile.py +59 -0
- iaml/statistics/range.py +53 -0
- iaml/statistics/rare_category_rate.py +92 -0
- iaml/statistics/skewness.py +53 -0
- iaml/statistics/stdev.py +50 -0
- iaml/statistics/summary_table_statistic.py +60 -0
- iaml/statistics/time_by_group_statistic.py +83 -0
- iaml/statistics/time_summary_statistic.py +56 -0
- iaml/statistics/top_k_value_counts.py +68 -0
- iaml/statistics/unique_count_statistic.py +57 -0
- iaml/statistics/value_counts.py +63 -0
- iaml/statistics/variance.py +51 -0
- iaml/statistics/violin.py +63 -0
- iaml/step.py +600 -0
- iaml/step_cache.py +87 -0
- iaml/step_wrapper.py +79 -0
- iaml/timed_pool_executor.py +492 -0
- iaml/type_of_target.py +68 -0
- iaml/void_step.py +101 -0
- iaml/worker_manager.py +169 -0
- iaml/wrapper/__init__.py +4 -0
- iaml/wrapper/wrap_basic_gridsearch.py +68 -0
- iaml/wrapper/wrap_genetic_gridsearch.py +293 -0
- iaml/wrapper/wrap_iterative_gridsearch.py +399 -0
- pyiaml-1.0.0.dist-info/METADATA +802 -0
- pyiaml-1.0.0.dist-info/RECORD +279 -0
- pyiaml-1.0.0.dist-info/WHEEL +5 -0
- pyiaml-1.0.0.dist-info/licenses/LICENSE +674 -0
- pyiaml-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""[STATISTIC] Violin."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import textwrap
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from scipy import stats
|
|
8
|
+
|
|
9
|
+
from ..dataset import Dataset
|
|
10
|
+
from ..data_type import DataType
|
|
11
|
+
from ..statistic import Statistic
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ViolinStatistic(Statistic):
|
|
15
|
+
"""[STATISTIC] Violin."""
|
|
16
|
+
|
|
17
|
+
name: str = "Violin"
|
|
18
|
+
_description: str = textwrap.dedent("""\
|
|
19
|
+
Violin statistics summarize target distributions per categorical value.
|
|
20
|
+
""")
|
|
21
|
+
_description_long: str = textwrap.dedent("""\
|
|
22
|
+
Violin statistics summarize target distributions per categorical value
|
|
23
|
+
to support violin-style plots.
|
|
24
|
+
""")
|
|
25
|
+
refs: list[dict] = []
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
return 'violin'
|
|
29
|
+
|
|
30
|
+
def compute(self, dataset: Dataset, **kwargs) -> pd.DataFrame:
|
|
31
|
+
"""Compute violin statistics for categorical features."""
|
|
32
|
+
if dataset.type_of_target != 'continuous':
|
|
33
|
+
return pd.DataFrame()
|
|
34
|
+
|
|
35
|
+
data = []
|
|
36
|
+
columns = []
|
|
37
|
+
for col in dataset.X.columns:
|
|
38
|
+
if dataset.columns_types[col][1] not in (DataType.TEXT, DataType.SHORT_TEXT):
|
|
39
|
+
columns.append(col)
|
|
40
|
+
if dataset.columns_types[col][1] == DataType.CATEGORICAL:
|
|
41
|
+
categorical_values = dataset.X[col].unique()
|
|
42
|
+
stats_dict = {}
|
|
43
|
+
for cat in categorical_values:
|
|
44
|
+
cond = (dataset.X[col] == cat).to_numpy()
|
|
45
|
+
tmp = dataset.y[cond]
|
|
46
|
+
if len(tmp) > 1:
|
|
47
|
+
kernel = stats.gaussian_kde(tmp)
|
|
48
|
+
support = np.linspace(min(tmp), max(tmp), 100)
|
|
49
|
+
density = kernel(support)
|
|
50
|
+
quartiles = np.percentile(tmp, [25, 50, 75])
|
|
51
|
+
stats_dict[cat] = {
|
|
52
|
+
'density': density,
|
|
53
|
+
'support': support,
|
|
54
|
+
'quartiles': quartiles
|
|
55
|
+
}
|
|
56
|
+
data.append(stats_dict)
|
|
57
|
+
else:
|
|
58
|
+
data.append(None)
|
|
59
|
+
return pd.DataFrame([data], index=[str(self)], columns=columns)
|
|
60
|
+
|
|
61
|
+
def suitable(self, dataset: Dataset) -> bool: # pylint: disable=unused-argument
|
|
62
|
+
"""Does this statistic apply to the dataset?"""
|
|
63
|
+
return dataset.type_of_target == 'continuous'
|
iaml/step.py
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Step class is a brick used to create pipelines.
|
|
3
|
+
This class is not really use in Pipeline, run function doesn't do anything.
|
|
4
|
+
Step class is use to create new kinds of steps by inheritance and give all
|
|
5
|
+
needed attributes and methods to children classes.
|
|
6
|
+
|
|
7
|
+
There are also decorators needed to create a Step. See it under Step class.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import json
|
|
14
|
+
import textwrap
|
|
15
|
+
import uuid
|
|
16
|
+
from hashlib import md5
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
from typing import Any
|
|
19
|
+
from copy import deepcopy
|
|
20
|
+
from multipledispatch import dispatch
|
|
21
|
+
from .dataset import Dataset
|
|
22
|
+
from .decorators.runner import runner
|
|
23
|
+
from .reference import Reference
|
|
24
|
+
from .step_cache import StepCache
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .candidate import Candidate
|
|
28
|
+
from ..iaml.reference import Reference
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Step: # pylint: disable=too-many-public-methods, too-many-instance-attributes
|
|
32
|
+
"""This class is the base for all IAML steps.
|
|
33
|
+
|
|
34
|
+
:param bool, optional use_cache: Whether to enable caching for this step.
|
|
35
|
+
"""
|
|
36
|
+
available_steps: dict[Step, tuple[str]] = {}
|
|
37
|
+
"""Static list of all registered steps within IAML."""
|
|
38
|
+
|
|
39
|
+
name: str = 'Step'
|
|
40
|
+
"""Step's name."""
|
|
41
|
+
|
|
42
|
+
refs: list[dict[str, Any]] = None
|
|
43
|
+
"""List of references for this step."""
|
|
44
|
+
|
|
45
|
+
_description: str = ''
|
|
46
|
+
"""Short description of the step."""
|
|
47
|
+
|
|
48
|
+
_description_long: str = ''
|
|
49
|
+
"""Longer description of the step."""
|
|
50
|
+
|
|
51
|
+
_usage: str = ''
|
|
52
|
+
"""Concise guidance on when to use (and avoid) this step."""
|
|
53
|
+
|
|
54
|
+
can_be_disabled: bool = True
|
|
55
|
+
"""Whether this step can be disabled."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, *args, use_cache: bool = True, **kwargs): # pylint: disable=unused-argument
|
|
58
|
+
self.tags: set = None
|
|
59
|
+
"""Step's tags. Tags are equivalent to categories of steps."""
|
|
60
|
+
|
|
61
|
+
self.__use_cache: bool = use_cache
|
|
62
|
+
"""Whether this step should benefit from the cache."""
|
|
63
|
+
|
|
64
|
+
self._cache_id: str = uuid.uuid4().hex
|
|
65
|
+
"""Shared cache namespace id for this step across deep copies."""
|
|
66
|
+
|
|
67
|
+
self._config_version: int = 0
|
|
68
|
+
"""Incremented when configuration changes to invalidate fingerprints."""
|
|
69
|
+
|
|
70
|
+
self.explanations: list[str] = []
|
|
71
|
+
"""List of explanations that were computed during the step's execution."""
|
|
72
|
+
|
|
73
|
+
self.is_interchangeable: bool = False
|
|
74
|
+
"""Whether this step can be mutated into another step with the same tags."""
|
|
75
|
+
|
|
76
|
+
self.enable: bool = True
|
|
77
|
+
"""Whether this step is enabled. If it is disabled, the step will simply return the input
|
|
78
|
+
candidate."""
|
|
79
|
+
|
|
80
|
+
self.optimizable: bool = False
|
|
81
|
+
"""Whether the configuration's parameters should be considered optimizable."""
|
|
82
|
+
|
|
83
|
+
self.configuration: dict = {}
|
|
84
|
+
"""Configuration of this step (equivalent to hyperparameters for models)."""
|
|
85
|
+
|
|
86
|
+
self.parents_steps: list[Step] = []
|
|
87
|
+
"""If this step is a child of another one, lists all the parents of this step."""
|
|
88
|
+
|
|
89
|
+
self.references: list[Reference] = []
|
|
90
|
+
"""References for this step."""
|
|
91
|
+
|
|
92
|
+
# Build a list of References from Step's references list
|
|
93
|
+
if self.refs is not None:
|
|
94
|
+
self.references = [ Reference(ref, type(self).__name__) for ref in self.refs ]
|
|
95
|
+
|
|
96
|
+
self.default_configuration() # Loads the default configuration
|
|
97
|
+
|
|
98
|
+
def __deepcopy__(self, memo: dict) -> 'Step':
|
|
99
|
+
"""Custom deepcopy to avoid copying per-step runtime caches."""
|
|
100
|
+
cls = self.__class__
|
|
101
|
+
result = cls.__new__(cls)
|
|
102
|
+
memo[id(self)] = result
|
|
103
|
+
for key, value in self.__dict__.items():
|
|
104
|
+
if key == 'candidate':
|
|
105
|
+
setattr(result, key, None)
|
|
106
|
+
continue
|
|
107
|
+
if key == 'caches':
|
|
108
|
+
setattr(result, key, [])
|
|
109
|
+
continue
|
|
110
|
+
setattr(result, key, deepcopy(value, memo))
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_pipeline(cls, pipeline: dict[str, Any], *args, **kwargs) -> 'Step':
|
|
115
|
+
"""Loads any kind of Step (Step, MetaStep, Wrapper, etc.) from an imported pipeline.
|
|
116
|
+
|
|
117
|
+
:param dict pipeline: Pipeline to import.
|
|
118
|
+
:param optional \\*args: Args to pass to the Step's constructor.
|
|
119
|
+
:param optional \\**kwargs: Kwargs to pass to the Step's constructor.
|
|
120
|
+
:raise TypeError: invalid pipeline: missing step attribute
|
|
121
|
+
:raise TypeError: invalid pipeline: step does not exist
|
|
122
|
+
:return: A step
|
|
123
|
+
"""
|
|
124
|
+
step = None
|
|
125
|
+
if 'step' not in pipeline:
|
|
126
|
+
raise TypeError("invalid pipeline: missing step attribute")
|
|
127
|
+
|
|
128
|
+
step_class = getattr(sys.modules['iaml'], pipeline['step']) # Get class from string
|
|
129
|
+
if Step in step_class.__mro__:
|
|
130
|
+
if step_class == cls:
|
|
131
|
+
step = cls(*args, **kwargs)
|
|
132
|
+
|
|
133
|
+
if 'enable' in pipeline:
|
|
134
|
+
step.enable = pipeline['enable']
|
|
135
|
+
|
|
136
|
+
if 'configuration' in pipeline:
|
|
137
|
+
for name, value in pipeline['configuration'].items():
|
|
138
|
+
step.configure(name, value['value']) # pylint: disable=too-many-function-args
|
|
139
|
+
else:
|
|
140
|
+
step = step_class.from_pipeline(pipeline)
|
|
141
|
+
else:
|
|
142
|
+
raise TypeError('invalid pipeline: step does not exist')
|
|
143
|
+
|
|
144
|
+
return step
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def enable(self) -> bool:
|
|
148
|
+
"""Tells whether the step is enabled.
|
|
149
|
+
|
|
150
|
+
:return: Whether the step is enabled.
|
|
151
|
+
"""
|
|
152
|
+
return self.__enable
|
|
153
|
+
|
|
154
|
+
@enable.setter
|
|
155
|
+
def enable(self, value: bool) -> None:
|
|
156
|
+
"""Sets the state of the step.
|
|
157
|
+
|
|
158
|
+
:param value: State of the step (True if enabled, False if disabled).
|
|
159
|
+
"""
|
|
160
|
+
# If can_be_disabled is False, value will always be True
|
|
161
|
+
self.__enable = value or not self.can_be_disabled
|
|
162
|
+
|
|
163
|
+
def __str__(self) -> str:
|
|
164
|
+
"""Returns the name of this step.
|
|
165
|
+
|
|
166
|
+
:return: Name of this step.
|
|
167
|
+
"""
|
|
168
|
+
return self.name
|
|
169
|
+
|
|
170
|
+
def suitable(self, dataset: Dataset) -> bool: # pylint: disable=unused-argument
|
|
171
|
+
"""Evaluates whether the step is suitable to run with the given dataset.
|
|
172
|
+
|
|
173
|
+
:param Dataset dataset: Dataset to evaluate on.
|
|
174
|
+
:return: Whether the step is suitable to run.
|
|
175
|
+
"""
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
def configure_child(self, child: 'Step') -> 'Step':
|
|
179
|
+
"""When working with steps that can have children, setups the child with the parent.
|
|
180
|
+
|
|
181
|
+
:param Step child: Step to configure with the parent.
|
|
182
|
+
:return: Configured step.
|
|
183
|
+
"""
|
|
184
|
+
child.configure_parents(self)
|
|
185
|
+
|
|
186
|
+
return child
|
|
187
|
+
|
|
188
|
+
def configure_parents(self, *parents: list['Step']) -> None:
|
|
189
|
+
"""Backpropagates the parents to the children.
|
|
190
|
+
|
|
191
|
+
:param list[Step] parents: Parent steps to add to the children.
|
|
192
|
+
"""
|
|
193
|
+
self.parents_steps.extend(map(id, parents))
|
|
194
|
+
|
|
195
|
+
def step_with_same_tags(self) -> list['Step']:
|
|
196
|
+
"""Returns the steps that have the same tags as this step.
|
|
197
|
+
|
|
198
|
+
:return: List of steps.
|
|
199
|
+
"""
|
|
200
|
+
return [ key for key, tags in Step.available_steps.items() if self.tags == set(tags) ]
|
|
201
|
+
|
|
202
|
+
################
|
|
203
|
+
# Configurable #
|
|
204
|
+
################
|
|
205
|
+
#
|
|
206
|
+
# A actionable step can be configured by the user.
|
|
207
|
+
# For example, we can configure learning rate of a machine learning step.
|
|
208
|
+
#
|
|
209
|
+
# Each parameters have a name, a description and a default value.
|
|
210
|
+
# Default value can be fixed or computed based on dataset
|
|
211
|
+
|
|
212
|
+
@dispatch(str, object)
|
|
213
|
+
def configure(self, key: str, value: Any) -> None:
|
|
214
|
+
"""Configures a parameter.
|
|
215
|
+
|
|
216
|
+
:param str key: Name (or key) of the parameter to set.
|
|
217
|
+
:param Any value: New value for the parameter.
|
|
218
|
+
:raise AttributeError: When the key is invalid.
|
|
219
|
+
"""
|
|
220
|
+
if key in self.configuration:
|
|
221
|
+
self.configuration[key]['value'] = value
|
|
222
|
+
self._config_version += 1
|
|
223
|
+
else:
|
|
224
|
+
raise AttributeError(f"Configurable Key '{key}' does not exist.")
|
|
225
|
+
|
|
226
|
+
@dispatch(dict)
|
|
227
|
+
def configure(self, config: dict[str, Any]) -> None: # pylint: disable=function-redefined
|
|
228
|
+
"""Configures several parameters at once.
|
|
229
|
+
|
|
230
|
+
:param dict[str, Any] config: Dictionary of parameters' names and values.
|
|
231
|
+
"""
|
|
232
|
+
for key, value in config.items():
|
|
233
|
+
self.configure(key, value) # pylint: disable=too-many-function-args
|
|
234
|
+
|
|
235
|
+
def passthrough_parameters(self, default: bool = True) -> dict[str, Any]:
|
|
236
|
+
"""Constructs a dictionary of parameters' names and values.
|
|
237
|
+
|
|
238
|
+
:param bool default: Default behavior when trying to passthrough configurations from
|
|
239
|
+
one step which have no "passthrough" key. When "passthrough" is undefined and "default"
|
|
240
|
+
is set to False, the configuration will not be returned; otherwise, the default value
|
|
241
|
+
for that configuration will be returned.
|
|
242
|
+
:return: A dictionary view of the configuration with parameters' names and values.
|
|
243
|
+
"""
|
|
244
|
+
parameters = {}
|
|
245
|
+
for key, value in self.configuration.items():
|
|
246
|
+
if "passthrough" in value:
|
|
247
|
+
if value['passthrough']:
|
|
248
|
+
parameters[key] = value['value']
|
|
249
|
+
elif default:
|
|
250
|
+
parameters[key] = value['value']
|
|
251
|
+
|
|
252
|
+
return parameters
|
|
253
|
+
|
|
254
|
+
def all_configurations(self) -> list[dict[str, Any]]:
|
|
255
|
+
"""Retrieves the signature (id and configuration) of the step. This should be overridden in
|
|
256
|
+
steps which have children to also return the children's configuration.
|
|
257
|
+
|
|
258
|
+
:return: List of all configurations as objects.
|
|
259
|
+
"""
|
|
260
|
+
return [{
|
|
261
|
+
'step_id': id(self),
|
|
262
|
+
'configuration': self.configuration
|
|
263
|
+
}]
|
|
264
|
+
|
|
265
|
+
def resume_configuration(self) -> dict:
|
|
266
|
+
"""Summarizes the configuration of this step as keys and values.
|
|
267
|
+
|
|
268
|
+
:return: Configuration summary.
|
|
269
|
+
"""
|
|
270
|
+
return Step.__resume_a_configuration(self.configuration)
|
|
271
|
+
|
|
272
|
+
def serializable_resume_configuration(self) -> dict:
|
|
273
|
+
"""Generates a serializable configuration of this step.
|
|
274
|
+
|
|
275
|
+
:return: Serializable dictionary.
|
|
276
|
+
"""
|
|
277
|
+
return {key: value.__name__ if callable(value) else value \
|
|
278
|
+
for key, value in self.resume_configuration().items()}
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def __resume_a_configuration(cls, config: dict[str, Any]) -> dict:
|
|
282
|
+
"""Summarizes a configuration, and uses the default value for each configuration which have
|
|
283
|
+
no value yet.
|
|
284
|
+
|
|
285
|
+
:param dict[str, Any] config: Step's configuration.
|
|
286
|
+
:return: The configuration to make a "summary" for.
|
|
287
|
+
"""
|
|
288
|
+
return { k: Step.__get_a_value(v) for k, v in config.items() }
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def __get_a_value(cls, elem: dict[str, Any]) -> Any:
|
|
292
|
+
"""Retrieves a value from the configuration, and uses the default value for that
|
|
293
|
+
configuration if it has no value.
|
|
294
|
+
|
|
295
|
+
:param dict[str, Any] elem: Step's configuration item.
|
|
296
|
+
:return: Value or default value.
|
|
297
|
+
"""
|
|
298
|
+
return elem['value'] if 'value' in elem.keys() else elem['default']
|
|
299
|
+
|
|
300
|
+
def get_config(self, key: str) -> Any:
|
|
301
|
+
"""Retrieves a value from the configuration, and uses the default value for that
|
|
302
|
+
configuration if it has no value.
|
|
303
|
+
|
|
304
|
+
:param str key: Step's configuration item.
|
|
305
|
+
:return: Value or default value.
|
|
306
|
+
"""
|
|
307
|
+
param: dict = self.configuration[key]
|
|
308
|
+
|
|
309
|
+
return param['value'] if 'value' in param else param['default']
|
|
310
|
+
|
|
311
|
+
def default_configuration(self) -> None:
|
|
312
|
+
"""Gives the default value to all configuration items."""
|
|
313
|
+
for param in self.configuration.values():
|
|
314
|
+
param['value'] = param['default']
|
|
315
|
+
|
|
316
|
+
def check_configuration(self, fix: bool = True) -> bool:
|
|
317
|
+
"""Checks whether the configuration's format is valid.
|
|
318
|
+
|
|
319
|
+
:param bool fix: Whether to try to fix the configuration if something is wrong.
|
|
320
|
+
:return: Whether the configuration is wrong. Fixed issues return True.
|
|
321
|
+
"""
|
|
322
|
+
for key, config in self.configuration.items():
|
|
323
|
+
if 'categorical' in config:
|
|
324
|
+
if self.get_config(key) not in config['categorical']:
|
|
325
|
+
if fix:
|
|
326
|
+
self.configure(key, config['categorical'][0]) # pylint: disable=too-many-function-args
|
|
327
|
+
else:
|
|
328
|
+
return False
|
|
329
|
+
elif 'range' in config:
|
|
330
|
+
if self.get_config(key) < config['range'][0] \
|
|
331
|
+
or self.get_config(key) > config['range'][1]:
|
|
332
|
+
if fix:
|
|
333
|
+
self.configure(key, (config['range'][0] + config['range'][1]) / 2) # pylint: disable=too-many-function-args
|
|
334
|
+
else:
|
|
335
|
+
return False
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
def all_steps(self) -> list[Step]:
|
|
339
|
+
"""Retrieves all steps of a pipeline. This should be overridden in steps which may run
|
|
340
|
+
other steps, such as children steps.
|
|
341
|
+
|
|
342
|
+
:return: List of steps.
|
|
343
|
+
"""
|
|
344
|
+
return [self]
|
|
345
|
+
#####################
|
|
346
|
+
## CACHING RESULTS ##
|
|
347
|
+
#####################
|
|
348
|
+
# Results of run() can be stored in cache to avoid recomputing the same candidate.
|
|
349
|
+
|
|
350
|
+
def from_cache(self, candidate: Candidate) -> Candidate:
|
|
351
|
+
"""Checks whether this step was already run with the same input candidate and the same
|
|
352
|
+
configuration, and return the cached result if that is the case. Otherwise, return None.
|
|
353
|
+
|
|
354
|
+
:param Candidate candidate: Candidate to look for in cache.
|
|
355
|
+
:return: Cached candidate or None.
|
|
356
|
+
"""
|
|
357
|
+
if not self.use_cache:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
cache_key = self._cache_key(candidate)
|
|
361
|
+
cached = StepCache().get(cache_key, candidate)
|
|
362
|
+
if cached is None:
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
return self._clone_output(cached)
|
|
366
|
+
|
|
367
|
+
def add_cache(self, input_candidate: Candidate, output_candidate: Candidate) -> bool:
|
|
368
|
+
"""Adds a candidate in the cache of this step.
|
|
369
|
+
|
|
370
|
+
:param Candidate input_candidate: Input candidate.
|
|
371
|
+
:param Candidate output_candidate: Output candidate to cache.
|
|
372
|
+
:return: Whether the candidate was cached.
|
|
373
|
+
"""
|
|
374
|
+
if not self.use_cache:
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
cache_key = self._cache_key(input_candidate)
|
|
378
|
+
frozen_output = self._clone_output(output_candidate)
|
|
379
|
+
StepCache().put(cache_key, frozen_output, self._cache_id, input_candidate)
|
|
380
|
+
|
|
381
|
+
return True
|
|
382
|
+
|
|
383
|
+
def reset_cache(self) -> None:
|
|
384
|
+
"""Removes all cached candidates from the cache."""
|
|
385
|
+
StepCache().clear(self._cache_id)
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def caches(self) -> list:
|
|
389
|
+
"""Deprecated view of cached results backed by StepCache."""
|
|
390
|
+
return StepCache().values_for_step(self._cache_id)
|
|
391
|
+
|
|
392
|
+
@caches.setter
|
|
393
|
+
def caches(self, value: list | None) -> None:
|
|
394
|
+
if not value:
|
|
395
|
+
StepCache().clear(self._cache_id)
|
|
396
|
+
|
|
397
|
+
def _cache_key(self, candidate: Candidate) -> tuple:
|
|
398
|
+
"""Index by address; StepCache also verifies the input through a weak reference."""
|
|
399
|
+
candidate_id = id(candidate) if candidate is not None else None
|
|
400
|
+
return (self._cache_id, self.fingerprint(), candidate_id)
|
|
401
|
+
|
|
402
|
+
def _clone_output(self, output: Any) -> Any:
|
|
403
|
+
"""Return cached output without cloning to preserve identity semantics."""
|
|
404
|
+
return output
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def use_cache(self) -> bool:
|
|
408
|
+
"""Tells whether the step is using the cache.
|
|
409
|
+
|
|
410
|
+
:return: True if caching is enabled, False otherwise.
|
|
411
|
+
"""
|
|
412
|
+
return self.__use_cache
|
|
413
|
+
|
|
414
|
+
@use_cache.setter
|
|
415
|
+
def use_cache(self, value: bool) -> bool:
|
|
416
|
+
"""Enables or disables caching.
|
|
417
|
+
|
|
418
|
+
:param bool value: If True, enables caching; if False, disables caching.
|
|
419
|
+
:raise ValueError: When `value` is not a boolean.
|
|
420
|
+
"""
|
|
421
|
+
if not isinstance(value, bool):
|
|
422
|
+
raise ValueError('Value must be a boolean')
|
|
423
|
+
self.__use_cache = value
|
|
424
|
+
|
|
425
|
+
def json_pipeline(self) -> dict[str, Any]:
|
|
426
|
+
"""Exports a representation of this step as a dictionary.
|
|
427
|
+
|
|
428
|
+
:return: Dictionary view of this step's signature.
|
|
429
|
+
"""
|
|
430
|
+
return {
|
|
431
|
+
'step': self.__class__.__name__,
|
|
432
|
+
'name': self.name,
|
|
433
|
+
'description': self.description,
|
|
434
|
+
'enable': self.enable,
|
|
435
|
+
'can_be_disable': self.can_be_disabled,
|
|
436
|
+
'configuration': {
|
|
437
|
+
k: { **v, 'description': v['description'].replace('\n', ' ') }
|
|
438
|
+
for k, v in self.configuration.items() },
|
|
439
|
+
'children': []
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
def conf_to_rich_str_list(self) -> list[str]:
|
|
443
|
+
"""Renders the configuration of this step as a list of rich-formatted strings.
|
|
444
|
+
|
|
445
|
+
:return: List of rich-formatted strings.
|
|
446
|
+
"""
|
|
447
|
+
conf = [ f'{name}={conf["value"]}' for name, conf in self.configuration.items() ]
|
|
448
|
+
|
|
449
|
+
return conf
|
|
450
|
+
|
|
451
|
+
def to_rich_str(self) -> str:
|
|
452
|
+
"""Renders the signature of this step as a rich-formatted string.
|
|
453
|
+
|
|
454
|
+
:return: Rich-formatted string.
|
|
455
|
+
"""
|
|
456
|
+
conf = self.conf_to_rich_str_list()
|
|
457
|
+
|
|
458
|
+
if len(conf) > 0:
|
|
459
|
+
name = f'[b]{self.__class__.__name__}[/] ({", ".join(conf)})'
|
|
460
|
+
else:
|
|
461
|
+
name = f'[b]{self.__class__.__name__}[/]'
|
|
462
|
+
|
|
463
|
+
return name
|
|
464
|
+
|
|
465
|
+
def count_steps(self) -> int:
|
|
466
|
+
"""Returns a rough estimation of the total count of steps for a given pipeline.
|
|
467
|
+
|
|
468
|
+
:return: Number of steps.
|
|
469
|
+
"""
|
|
470
|
+
return 1
|
|
471
|
+
|
|
472
|
+
############
|
|
473
|
+
# Priorize #
|
|
474
|
+
############
|
|
475
|
+
def priorize(self, candidate: Candidate = None) -> float: # pylint: disable=unused-argument
|
|
476
|
+
"""Evaluates the priority of the step within a pipeline. 0 means the execution of the step
|
|
477
|
+
should not be prioritized, and 1 means it should be executed early in the pipeline.
|
|
478
|
+
|
|
479
|
+
:param Candidate, optional candidate: Candidate on which the priority should be evaluated.
|
|
480
|
+
:return: Value between 0 and 1.
|
|
481
|
+
"""
|
|
482
|
+
return 0.0
|
|
483
|
+
|
|
484
|
+
#######
|
|
485
|
+
# RUN #
|
|
486
|
+
#######
|
|
487
|
+
def fit(self, dataset: Dataset) -> 'Step': # pylint: disable=unused-argument
|
|
488
|
+
"""Fits the step on the given dataset.
|
|
489
|
+
|
|
490
|
+
:param Dataset dataset: Features and labels.
|
|
491
|
+
:return: Fitted step.
|
|
492
|
+
"""
|
|
493
|
+
return self
|
|
494
|
+
|
|
495
|
+
@runner
|
|
496
|
+
def run(self, candidate: Candidate) -> Candidate:
|
|
497
|
+
"""Runs the step for a given candidate.
|
|
498
|
+
|
|
499
|
+
:param Candidate candidate: Candidate to run the step for.
|
|
500
|
+
:return: Run candidate.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
# Never fit predictor during generation of candidates
|
|
504
|
+
if self.tags and 'predictor' in self.tags:
|
|
505
|
+
return candidate.add_to_pipeline(self)
|
|
506
|
+
|
|
507
|
+
self.fit(candidate.dataset)
|
|
508
|
+
|
|
509
|
+
return candidate.add_to_pipeline(self)
|
|
510
|
+
|
|
511
|
+
def fingerprint(self) -> str:
|
|
512
|
+
"""Returns a MD5 hash that can be used to distinguish steps.
|
|
513
|
+
|
|
514
|
+
:return: MD5 string.
|
|
515
|
+
"""
|
|
516
|
+
to_hash = f"{str(self.__class__)} = \
|
|
517
|
+
{json.dumps(self.serializable_resume_configuration(), sort_keys=True)}"
|
|
518
|
+
|
|
519
|
+
return md5(to_hash.encode()).hexdigest()
|
|
520
|
+
|
|
521
|
+
####################
|
|
522
|
+
### Explanations ###
|
|
523
|
+
####################
|
|
524
|
+
def __format_description(self, description: str) -> str:
|
|
525
|
+
return description \
|
|
526
|
+
.replace('\n', ' ') \
|
|
527
|
+
.format(**{ k: v['value'] for k, v in self.configuration.items() })
|
|
528
|
+
|
|
529
|
+
@property
|
|
530
|
+
def description(self) -> str:
|
|
531
|
+
"""Formats the description of a step with its configuration.
|
|
532
|
+
|
|
533
|
+
:return: Formatted description.
|
|
534
|
+
"""
|
|
535
|
+
return self.__format_description(self._description)
|
|
536
|
+
|
|
537
|
+
@property
|
|
538
|
+
def description_long(self) -> str:
|
|
539
|
+
"""Formats the longer description of a step with its configuration.
|
|
540
|
+
|
|
541
|
+
:return: Formatted description.
|
|
542
|
+
"""
|
|
543
|
+
return self.__format_description(self._description_long)
|
|
544
|
+
|
|
545
|
+
def explain(self, processings_limit: int = 20) -> str:
|
|
546
|
+
"""Renders the step configurations as Markdown text.
|
|
547
|
+
|
|
548
|
+
:param int processings_limit: Maximum number of processings to render.
|
|
549
|
+
:return: Markdown document.
|
|
550
|
+
"""
|
|
551
|
+
confs = '\n '.join([
|
|
552
|
+
f'| **{k}** | {v["description"]} | {v["value"]} |'.replace('\n', '')
|
|
553
|
+
for k, v in self.configuration.items()
|
|
554
|
+
])
|
|
555
|
+
|
|
556
|
+
processings = [ f' - {p}' for p in self.explanations[:processings_limit] ]
|
|
557
|
+
explanations = '\n '.join(processings)
|
|
558
|
+
processings_left = len(self.explanations) - processings_limit
|
|
559
|
+
|
|
560
|
+
if len(explanations) == 0 and (hasattr(self, 'transform') or hasattr(self, 'resample')):
|
|
561
|
+
return None
|
|
562
|
+
|
|
563
|
+
markdown_conf = textwrap.dedent(f"""\
|
|
564
|
+
### Configuration
|
|
565
|
+
| Name | Description | Value |
|
|
566
|
+
| ---- | ----------- | ----- |
|
|
567
|
+
{confs}
|
|
568
|
+
""") if len(confs) > 0 else ""
|
|
569
|
+
|
|
570
|
+
markdown_processings = textwrap.dedent(f'''\
|
|
571
|
+
### Processings
|
|
572
|
+
{explanations}
|
|
573
|
+
{f" - *and **{processings_left}** more explanations...*" if processings_left > 0 else ""}
|
|
574
|
+
''') if len(explanations) > 0 else ""
|
|
575
|
+
|
|
576
|
+
return '\n'.join([
|
|
577
|
+
f'## {self.name}',
|
|
578
|
+
f'**{self.description}**\n',
|
|
579
|
+
markdown_conf,
|
|
580
|
+
markdown_processings,
|
|
581
|
+
])
|
|
582
|
+
|
|
583
|
+
def same_types(a: dict, b: dict) -> bool:
|
|
584
|
+
"""Recursively checks whether the two provided dictionaries are the exact same.
|
|
585
|
+
|
|
586
|
+
:param dict a: First dictionary.
|
|
587
|
+
:param dict b: Second dictionary.
|
|
588
|
+
:return: Whether the two dictionaries are the same.
|
|
589
|
+
"""
|
|
590
|
+
if len(a.keys()) != len(b.keys()):
|
|
591
|
+
return False
|
|
592
|
+
|
|
593
|
+
for key, value in a.items():
|
|
594
|
+
if isinstance(value, dict):
|
|
595
|
+
return same_types(value, b[key])
|
|
596
|
+
|
|
597
|
+
if key not in b or value != b[key]:
|
|
598
|
+
return False
|
|
599
|
+
|
|
600
|
+
return True
|