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/core_dispatcher.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Singleton used by IAML to dispatch cores to process"""
|
|
2
|
+
import threading
|
|
3
|
+
import warnings
|
|
4
|
+
import multiprocess
|
|
5
|
+
import multiprocess.managers
|
|
6
|
+
import psutil
|
|
7
|
+
from .meta_singleton import MetaSingleton
|
|
8
|
+
from .logger import Logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CoreDispatcher(metaclass=MetaSingleton):
|
|
12
|
+
"""Singleton used by IAML to dispatch cores to process
|
|
13
|
+
|
|
14
|
+
:param tuple, optional \\*args: Additional parameters.
|
|
15
|
+
:param dict, optional \\**kwargs: Additional parameters.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, *args, **kwargs) -> None: #pylint: disable=unused-argument
|
|
18
|
+
self.__all_cores: set[int] = set(range(psutil.cpu_count(logical=True)))
|
|
19
|
+
"""Set of all cores available"""
|
|
20
|
+
|
|
21
|
+
self.manager: multiprocess.Manager | None = None
|
|
22
|
+
"""Manager used to dispatch cores to process (None when not available)."""
|
|
23
|
+
|
|
24
|
+
self._lock: threading.RLock | multiprocess.managers.SyncManager.RLockProxy
|
|
25
|
+
"""Lock protecting access to booked cores."""
|
|
26
|
+
|
|
27
|
+
self.books: list | multiprocess.managers.ListProxy
|
|
28
|
+
"""Booked cores registry."""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
self.manager = multiprocess.Manager()
|
|
32
|
+
self._lock = self.manager.RLock()
|
|
33
|
+
self.books = self.manager.list([])
|
|
34
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
35
|
+
warnings.warn(f"CoreDispatcher fallback to local mode (manager start failed: {exc!r})")
|
|
36
|
+
self.manager = None
|
|
37
|
+
self._lock = threading.RLock()
|
|
38
|
+
self.books = []
|
|
39
|
+
|
|
40
|
+
def reset_books(self) -> None:
|
|
41
|
+
"""Set all cpu cores as available"""
|
|
42
|
+
with self._lock:
|
|
43
|
+
if hasattr(self.books, "clear"):
|
|
44
|
+
self.books.clear()
|
|
45
|
+
else:
|
|
46
|
+
self.books[:] = []
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def available_cores(self) -> set[int]:
|
|
50
|
+
"""All cores minus booked ones
|
|
51
|
+
|
|
52
|
+
:return: Set of available cpu cores
|
|
53
|
+
"""
|
|
54
|
+
avail = set(self.__all_cores)
|
|
55
|
+
with self._lock:
|
|
56
|
+
for book in list(self.books):
|
|
57
|
+
avail -= set(book['cores'])
|
|
58
|
+
|
|
59
|
+
return avail
|
|
60
|
+
|
|
61
|
+
def __book_cpu(self, pids: set[int], number: int) -> None:
|
|
62
|
+
"""Affiliate CPU cores to process
|
|
63
|
+
|
|
64
|
+
:param set[int] pids: pids to set affinity with.
|
|
65
|
+
:param int number: Number of cores to book.
|
|
66
|
+
:raise RuntimeError: Not enough CPU cores available.
|
|
67
|
+
"""
|
|
68
|
+
self.__free_cores()
|
|
69
|
+
with self._lock:
|
|
70
|
+
if len(self.available_cores) < number:
|
|
71
|
+
raise RuntimeError('Not enough CPU cores available')
|
|
72
|
+
|
|
73
|
+
to_book = list(self.available_cores)[:number]
|
|
74
|
+
entry = {
|
|
75
|
+
'pids': pids,
|
|
76
|
+
'cores': to_book
|
|
77
|
+
}
|
|
78
|
+
self.books.extend([entry])
|
|
79
|
+
|
|
80
|
+
for pid in pids:
|
|
81
|
+
process = psutil.Process(pid)
|
|
82
|
+
try:
|
|
83
|
+
process.cpu_affinity(to_book)
|
|
84
|
+
except (psutil.AccessDenied, AttributeError, NotImplementedError):
|
|
85
|
+
Logger().warning(
|
|
86
|
+
'Unable to set CPU affinity for process '
|
|
87
|
+
f'{pid}. Continuing without affinity control.'
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def affiliate(self, pids: list[int], core_number: int = 1) -> None:
|
|
91
|
+
"""Run process with CPU affinity
|
|
92
|
+
|
|
93
|
+
:param list[int] pids: Process to affiliate with CPU cores.
|
|
94
|
+
:param int, optional core_number: number of process to run
|
|
95
|
+
(each process run the same target). Defaults to 1.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
test_process = psutil.Process(pids[0])
|
|
99
|
+
except psutil.Error:
|
|
100
|
+
Logger().warning('Unable to inspect process for CPU affinity; skipping affinity control.')
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if hasattr(test_process, 'cpu_affinity'):
|
|
104
|
+
try:
|
|
105
|
+
self.__book_cpu(pids, core_number)
|
|
106
|
+
except RuntimeError as exc:
|
|
107
|
+
Logger().warning(str(exc))
|
|
108
|
+
else:
|
|
109
|
+
Logger().warning(
|
|
110
|
+
'Your OS doesn\'t support CPU affinity. We are not able to control CPU cores access'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def __free_cores(self) -> None:
|
|
114
|
+
"""Free cores that are not used anymore"""
|
|
115
|
+
with self._lock:
|
|
116
|
+
new_books = []
|
|
117
|
+
for book in self.books:
|
|
118
|
+
if any(psutil.pid_exists(pid) and psutil.Process(pid).is_running()
|
|
119
|
+
for pid in book['pids']):
|
|
120
|
+
new_books.append(book)
|
|
121
|
+
|
|
122
|
+
self.reset_books()
|
|
123
|
+
self.books.extend(new_books)
|
|
124
|
+
|
|
125
|
+
CoreDispatcher() # Run it a first time to init shared objects
|
iaml/data_type.py
ADDED
iaml/dataset.py
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"""Encapsulate X, y data to be used by Steps
|
|
2
|
+
Add features like data type detection and splitting
|
|
3
|
+
"""
|
|
4
|
+
import copy
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from typing import Any, Iterator, TYPE_CHECKING
|
|
7
|
+
from sklearn.model_selection import StratifiedShuffleSplit, ShuffleSplit
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from .cache_keys import hash_dataset
|
|
12
|
+
from .data_type import DataType
|
|
13
|
+
from .type_of_target import type_of_target
|
|
14
|
+
from .logger import Logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .iaml_pipeline import IAMLPipeline
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Dataset:
|
|
22
|
+
"""Encapsulate X, y data to be used by Steps
|
|
23
|
+
Add features like data type detection and splitting
|
|
24
|
+
|
|
25
|
+
:param pd.DataFrame X: The dataframe used in this Dataset.
|
|
26
|
+
:param list, optional y: The dataframe target. Default to None.
|
|
27
|
+
:param pd.DataFrame, optional groups: If set, will be used when splitting to avoid
|
|
28
|
+
having data rows for a similar ID in two different splits. Default to None.
|
|
29
|
+
:param list[str], optional groups_columns: Same as groups, but with column names.
|
|
30
|
+
Default to None.
|
|
31
|
+
:param dict, optional columns_types: Specify column types instead of detecting them
|
|
32
|
+
automaticaly. Default to None.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self,
|
|
37
|
+
X: pd.DataFrame,
|
|
38
|
+
y: list = None,
|
|
39
|
+
groups: pd.DataFrame = None,
|
|
40
|
+
groups_columns: list[str] = None,
|
|
41
|
+
columns_types: dict = None
|
|
42
|
+
):
|
|
43
|
+
type_of_target_override = None
|
|
44
|
+
if isinstance(y, str):
|
|
45
|
+
type_of_target_override = y
|
|
46
|
+
y = []
|
|
47
|
+
|
|
48
|
+
if columns_types is None and isinstance(groups, dict) and not groups_columns:
|
|
49
|
+
if self._looks_like_columns_types(groups):
|
|
50
|
+
columns_types = groups
|
|
51
|
+
groups = None
|
|
52
|
+
|
|
53
|
+
if groups is not None and groups_columns:
|
|
54
|
+
raise ValueError("groups and groups_columns are not None. Only one must be set")
|
|
55
|
+
|
|
56
|
+
if isinstance(groups, pd.DataFrame):
|
|
57
|
+
if set(groups.columns).intersection(X.columns):
|
|
58
|
+
raise ValueError("Group columns present in dataset!")
|
|
59
|
+
|
|
60
|
+
if groups_columns is None:
|
|
61
|
+
groups_columns = []
|
|
62
|
+
|
|
63
|
+
self.__X: pd.DataFrame = X.drop(columns=groups_columns)
|
|
64
|
+
"""The dataframe used in this Dataset object without groups columns if provided"""
|
|
65
|
+
|
|
66
|
+
# Make sure y is either None or single column
|
|
67
|
+
if isinstance(y, pd.DataFrame):
|
|
68
|
+
if len(y.columns) > 1:
|
|
69
|
+
raise ValueError("IAML only handle single column labels !")
|
|
70
|
+
y = y.values.ravel()
|
|
71
|
+
self.__y: np.array = np.array(y)
|
|
72
|
+
"""The dataframe target"""
|
|
73
|
+
|
|
74
|
+
if groups_columns:
|
|
75
|
+
self.groups = X[groups_columns]
|
|
76
|
+
else:
|
|
77
|
+
if isinstance(groups, dict):
|
|
78
|
+
self.groups = pd.DataFrame(groups)
|
|
79
|
+
elif isinstance(groups, (pd.Series, list, np.ndarray)):
|
|
80
|
+
self.groups = pd.DataFrame(groups)
|
|
81
|
+
else:
|
|
82
|
+
self.groups = groups
|
|
83
|
+
|
|
84
|
+
if self.groups is not None and self.groups.shape[1] > 1:
|
|
85
|
+
# Create a combined group label by concatenating all columns into tuples
|
|
86
|
+
Logger().warning("You are using multiple columns as groups. \
|
|
87
|
+
Be careful, as these columns will serve as a composite key.")
|
|
88
|
+
self.groups = pd.DataFrame(pd.Series(
|
|
89
|
+
list(zip(*[self.groups[col] for col in self.groups.columns]))),
|
|
90
|
+
columns=['groups']
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if self.groups is not None and len(self.groups) != len(self.__X):
|
|
94
|
+
raise ValueError("Groups must have one row per feature row")
|
|
95
|
+
|
|
96
|
+
self.columns_types: dict = self._normalize_columns_types(columns_types, self.__X)
|
|
97
|
+
"""Columns types to be applied to our dataframe columns"""
|
|
98
|
+
|
|
99
|
+
self.__detect_columns_types()
|
|
100
|
+
|
|
101
|
+
self.type_of_target: str = None
|
|
102
|
+
"""Type of target to predict"""
|
|
103
|
+
|
|
104
|
+
if type_of_target_override is not None:
|
|
105
|
+
self.type_of_target = type_of_target_override
|
|
106
|
+
elif y is not None:
|
|
107
|
+
self.type_of_target = type_of_target(self.__y)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _normalize_columns_types(columns_types: dict | None, X: pd.DataFrame) -> dict:
|
|
111
|
+
if not columns_types:
|
|
112
|
+
return {}
|
|
113
|
+
|
|
114
|
+
if all(isinstance(key, DataType) for key in columns_types.keys()):
|
|
115
|
+
normalized = {}
|
|
116
|
+
for data_type, columns in columns_types.items():
|
|
117
|
+
if columns is None:
|
|
118
|
+
continue
|
|
119
|
+
if isinstance(columns, (str, bytes)):
|
|
120
|
+
columns = [columns]
|
|
121
|
+
for column in columns:
|
|
122
|
+
if column in X.columns:
|
|
123
|
+
normalized[column] = (X[column].dtype, data_type)
|
|
124
|
+
return normalized
|
|
125
|
+
|
|
126
|
+
if all(isinstance(value, DataType) for value in columns_types.values()):
|
|
127
|
+
normalized = {}
|
|
128
|
+
for column, data_type in columns_types.items():
|
|
129
|
+
if column in X.columns:
|
|
130
|
+
normalized[column] = (X[column].dtype, data_type)
|
|
131
|
+
return normalized
|
|
132
|
+
|
|
133
|
+
return columns_types
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _looks_like_columns_types(columns_types: dict) -> bool:
|
|
137
|
+
if not columns_types:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
keys = columns_types.keys()
|
|
141
|
+
values = columns_types.values()
|
|
142
|
+
|
|
143
|
+
if all(isinstance(key, DataType) for key in keys):
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
if all(isinstance(value, DataType) for value in values):
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
if all(
|
|
150
|
+
isinstance(value, tuple)
|
|
151
|
+
and len(value) == 2
|
|
152
|
+
and isinstance(value[1], DataType)
|
|
153
|
+
for value in values
|
|
154
|
+
):
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def features(self) -> list[str]:
|
|
161
|
+
"""List columns names of X data
|
|
162
|
+
|
|
163
|
+
:return: columns names.
|
|
164
|
+
"""
|
|
165
|
+
return self.X.columns.to_list()
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def X(self) -> pd.DataFrame:
|
|
169
|
+
"""X data getter
|
|
170
|
+
|
|
171
|
+
:return: X data
|
|
172
|
+
"""
|
|
173
|
+
return self.__X
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def y(self) -> np.ndarray:
|
|
177
|
+
"""y data getter
|
|
178
|
+
|
|
179
|
+
:return: y data
|
|
180
|
+
"""
|
|
181
|
+
return self.__y
|
|
182
|
+
|
|
183
|
+
def copy(self, deep: bool = True) -> 'Dataset':
|
|
184
|
+
"""Copy Dataset into a new instance
|
|
185
|
+
|
|
186
|
+
:param bool, optional deep: Perform a deep copy. Defaults to True.
|
|
187
|
+
:return: Copied Dataset.
|
|
188
|
+
"""
|
|
189
|
+
if deep:
|
|
190
|
+
return copy.deepcopy(self)
|
|
191
|
+
return copy.copy(self)
|
|
192
|
+
|
|
193
|
+
def decline(self, X: pd.DataFrame, y: list, groups: pd.DataFrame = None) -> 'Dataset':
|
|
194
|
+
"""Create a new Dataset with columns_types based on self.
|
|
195
|
+
Avoid time consuming columns_types computing
|
|
196
|
+
|
|
197
|
+
:param pd.DataFrame X: The dataframe to use for our newly created Dataset.
|
|
198
|
+
:param list y: The target to use for our newly created Dataset.
|
|
199
|
+
:param pd.DataFrame, optional groups: The groups to use for our newly created Dataset.
|
|
200
|
+
:return: New Dataset.
|
|
201
|
+
"""
|
|
202
|
+
if groups is None:
|
|
203
|
+
groups = self.groups
|
|
204
|
+
return Dataset(X, y, groups=groups, columns_types=self.columns_types)
|
|
205
|
+
|
|
206
|
+
def sample(self, n: int) -> 'Dataset':
|
|
207
|
+
"""Return a dataset with a sample of data
|
|
208
|
+
|
|
209
|
+
:param int n: Number of line in the sample dataset
|
|
210
|
+
:return: dataset with a sample of data
|
|
211
|
+
"""
|
|
212
|
+
if isinstance(n, float):
|
|
213
|
+
n = int(self.X.shape[0]*n)
|
|
214
|
+
|
|
215
|
+
if n >= self.X.shape[0]:
|
|
216
|
+
return self.decline(self.X, self.y)
|
|
217
|
+
|
|
218
|
+
if self.type_of_target == 'continuous':
|
|
219
|
+
_, test_idx = next(
|
|
220
|
+
ShuffleSplit(n_splits=1, test_size=n, random_state=42
|
|
221
|
+
).split(self.X, self.y))
|
|
222
|
+
else:
|
|
223
|
+
try:
|
|
224
|
+
_, test_idx = next(
|
|
225
|
+
StratifiedShuffleSplit(n_splits=1, test_size=n, random_state=42
|
|
226
|
+
).split(self.X, self.y))
|
|
227
|
+
except ValueError:
|
|
228
|
+
_, test_idx = next(
|
|
229
|
+
ShuffleSplit(n_splits=1, test_size=n, random_state=42
|
|
230
|
+
).split(self.X, self.y))
|
|
231
|
+
|
|
232
|
+
groups = self.groups.iloc[test_idx].copy() if self.has_groups else None
|
|
233
|
+
return self.decline(self.X.iloc[test_idx], self.y[test_idx], groups=groups)
|
|
234
|
+
|
|
235
|
+
def transform(self, method: callable) -> None:
|
|
236
|
+
"""Apply transform method to X or y data based on the method signature
|
|
237
|
+
|
|
238
|
+
:param callable method: Callable to apply. Will be call with X or y as parameter.
|
|
239
|
+
:return: Transformed dataset.
|
|
240
|
+
"""
|
|
241
|
+
self.__X = method(self.__X)
|
|
242
|
+
self.__detect_columns_types()
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def has_groups(self) -> bool:
|
|
246
|
+
"""Groups exists ?
|
|
247
|
+
|
|
248
|
+
:return: Exists ?
|
|
249
|
+
"""
|
|
250
|
+
return self.groups is not None and not self.groups.empty
|
|
251
|
+
|
|
252
|
+
def resample(self, resampler: callable) -> 'Dataset':
|
|
253
|
+
"""Apply a resampler on X, y and groups data.
|
|
254
|
+
|
|
255
|
+
:param callable resampler: Resampler method.
|
|
256
|
+
:return: Resampled Dataset.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
if self.has_groups:
|
|
260
|
+
# Merge groups with X
|
|
261
|
+
X = self.X.reset_index(drop=True).join(self.groups.reset_index(drop=True))
|
|
262
|
+
|
|
263
|
+
# Resampler
|
|
264
|
+
X, y = resampler(X, self.y)
|
|
265
|
+
|
|
266
|
+
# Split groups and X
|
|
267
|
+
return self.decline(X.drop(columns=self.groups.columns),
|
|
268
|
+
y,
|
|
269
|
+
groups=X[self.groups.columns])
|
|
270
|
+
return self.decline(*resampler(self.X, self.y))
|
|
271
|
+
|
|
272
|
+
def fingerprint(self) -> str:
|
|
273
|
+
"""Hash the current features, targets, groups and interpretation metadata.
|
|
274
|
+
|
|
275
|
+
Recompute because X, y and groups are exposed as mutable objects.
|
|
276
|
+
"""
|
|
277
|
+
return hash_dataset(
|
|
278
|
+
self.__X, self.__y, self.groups, self.columns_types, self.type_of_target
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def split(self, splitter: callable, *args, **kwargs) -> Iterator[tuple['Dataset', 'Dataset']]:
|
|
282
|
+
"""Use splitter to split dataset into a list of tuple (train set, test set)
|
|
283
|
+
|
|
284
|
+
:param callable splitter: The splitter function to perform.
|
|
285
|
+
:param tuple, optional \\*args: Additional parameters.
|
|
286
|
+
:param dict, optional \\**kwargs: Additional parameters.
|
|
287
|
+
:return: Train set and Test set iterator.
|
|
288
|
+
"""
|
|
289
|
+
# Split the dataset as many times as the splitter requires it
|
|
290
|
+
y = self.y if self.type_of_target is not None else None
|
|
291
|
+
for i_train, i_test in splitter(self.X, y, *args, **kwargs):
|
|
292
|
+
X_train = self.X.iloc[i_train].copy()
|
|
293
|
+
X_test = self.X.iloc[i_test].copy()
|
|
294
|
+
|
|
295
|
+
if self.has_groups:
|
|
296
|
+
groups = self.groups.iloc[i_train].copy()
|
|
297
|
+
test_groups = self.groups.iloc[i_test].copy()
|
|
298
|
+
else:
|
|
299
|
+
groups = None
|
|
300
|
+
test_groups = None
|
|
301
|
+
|
|
302
|
+
if y is not None:
|
|
303
|
+
y_train = self.__y[i_train].copy()
|
|
304
|
+
y_test = self.__y[i_test].copy()
|
|
305
|
+
else:
|
|
306
|
+
y_train = None
|
|
307
|
+
y_test = None
|
|
308
|
+
|
|
309
|
+
yield (self.decline(X_train, y_train, groups=groups),
|
|
310
|
+
self.decline(X_test, y_test, groups=test_groups))
|
|
311
|
+
|
|
312
|
+
def x_with_groups(self) -> pd.DataFrame:
|
|
313
|
+
"""Return X dataframe with groups columns if not None. Return X otherwise.
|
|
314
|
+
|
|
315
|
+
:return: The X dataframe with or without the groups columns.
|
|
316
|
+
"""
|
|
317
|
+
if self.has_groups:
|
|
318
|
+
return self.X.reset_index(drop=True).join(self.groups.reset_index(drop=True))
|
|
319
|
+
return self.X
|
|
320
|
+
|
|
321
|
+
def get_columns_names_by_type(self, types: list[DataType]) -> list[str]:
|
|
322
|
+
"""Get names of all the columns with DataType in types
|
|
323
|
+
|
|
324
|
+
:param list[DataType] types: List of Datatype to search.
|
|
325
|
+
:return: columns names.
|
|
326
|
+
"""
|
|
327
|
+
if not isinstance(types, list):
|
|
328
|
+
types = [types]
|
|
329
|
+
|
|
330
|
+
return [
|
|
331
|
+
column
|
|
332
|
+
for column, (dtype, type) in self.columns_types.items()
|
|
333
|
+
if type in types
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
def __detect_data_type(self, column_name: str) -> DataType:
|
|
337
|
+
"""Detect data type of a column
|
|
338
|
+
|
|
339
|
+
:param str column_name: Name of the column to analyse.
|
|
340
|
+
:return Type of the columns.
|
|
341
|
+
"""
|
|
342
|
+
column_value = self.X[column_name]
|
|
343
|
+
dtype = column_value.dtype
|
|
344
|
+
detected: DataType = None
|
|
345
|
+
if isinstance(dtype, pd.CategoricalDtype) or pd.api.types.is_bool_dtype(dtype):
|
|
346
|
+
detected = DataType.CATEGORICAL
|
|
347
|
+
elif pd.api.types.is_object_dtype(dtype) or pd.api.types.is_string_dtype(dtype):
|
|
348
|
+
if len(column_value) == 0:
|
|
349
|
+
detected = DataType.CATEGORICAL
|
|
350
|
+
else:
|
|
351
|
+
unique_count = len(column_value.unique())
|
|
352
|
+
if (unique_count / len(column_value) < 0.05 \
|
|
353
|
+
or unique_count < 7):
|
|
354
|
+
detected = DataType.CATEGORICAL
|
|
355
|
+
elif column_value.astype(str).apply(len).max() <= 85:
|
|
356
|
+
detected = DataType.SHORT_TEXT
|
|
357
|
+
else:
|
|
358
|
+
detected = DataType.TEXT
|
|
359
|
+
elif pd.api.types.is_numeric_dtype(dtype) or pd.api.types.is_timedelta64_dtype(dtype):
|
|
360
|
+
detected = DataType.NUMERIC
|
|
361
|
+
elif pd.api.types.is_datetime64_any_dtype(dtype):
|
|
362
|
+
detected = DataType.DATE
|
|
363
|
+
|
|
364
|
+
return dtype, detected
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def needed_estimator(self) -> str:
|
|
368
|
+
"""Kind of estimator needed for this dataset
|
|
369
|
+
|
|
370
|
+
:return: Estimator type.
|
|
371
|
+
"""
|
|
372
|
+
if self.type_of_target == 'continuous':
|
|
373
|
+
return 'regressor'
|
|
374
|
+
|
|
375
|
+
if self.type_of_target == 'survival':
|
|
376
|
+
return 'survival'
|
|
377
|
+
|
|
378
|
+
return 'classifier'
|
|
379
|
+
|
|
380
|
+
def __detect_columns_types(self) -> None:
|
|
381
|
+
"""Detect column type of all features in X"""
|
|
382
|
+
new_types = {}
|
|
383
|
+
for column in self.features:
|
|
384
|
+
if column not in self.columns_types \
|
|
385
|
+
or self.columns_types[column][0] != self.X[column].dtype:
|
|
386
|
+
new_types[column] = self.__detect_data_type(column)
|
|
387
|
+
else:
|
|
388
|
+
new_types[column] = self.columns_types[column]
|
|
389
|
+
|
|
390
|
+
self.columns_types = new_types
|
|
391
|
+
|
|
392
|
+
def to_survival(self) -> None:
|
|
393
|
+
"""Turn dataframe to survival compatibility"""
|
|
394
|
+
return Dataset.fix_survival(self.X, self.y)
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def _normalize_survival_pair(value: Any) -> tuple[bool, float]:
|
|
398
|
+
"""Normalize a single survival sample to a (event, time) tuple."""
|
|
399
|
+
if isinstance(value, np.void):
|
|
400
|
+
if value.dtype.names and \
|
|
401
|
+
'event' in value.dtype.names and 'time' in value.dtype.names:
|
|
402
|
+
return bool(value['event']), float(value['time'])
|
|
403
|
+
value = value.tolist()
|
|
404
|
+
|
|
405
|
+
if isinstance(value, dict):
|
|
406
|
+
if 'event' not in value or 'time' not in value:
|
|
407
|
+
raise KeyError("Survival sample dictionary must include 'event' and 'time'.")
|
|
408
|
+
return bool(value['event']), float(value['time'])
|
|
409
|
+
|
|
410
|
+
if isinstance(value, np.ndarray):
|
|
411
|
+
if value.shape == ():
|
|
412
|
+
return Dataset._normalize_survival_pair(value.item())
|
|
413
|
+
if value.ndim >= 1 and value.shape[0] >= 2:
|
|
414
|
+
return bool(value[0]), float(value[1])
|
|
415
|
+
|
|
416
|
+
if isinstance(value, (tuple, list)):
|
|
417
|
+
if len(value) < 2:
|
|
418
|
+
raise ValueError("Survival sample must provide event indicator and time.")
|
|
419
|
+
return bool(value[0]), float(value[1])
|
|
420
|
+
|
|
421
|
+
raise TypeError(f"Unsupported survival sample format: {type(value)}")
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
def normalize_survival_target(cls, y: Any) -> list[tuple[bool, float]]:
|
|
425
|
+
"""Return survival targets as a list of (event, time) tuples."""
|
|
426
|
+
if y is None:
|
|
427
|
+
return []
|
|
428
|
+
|
|
429
|
+
if isinstance(y, pd.DataFrame):
|
|
430
|
+
if not len(y.columns):
|
|
431
|
+
return []
|
|
432
|
+
if {'event', 'time'}.issubset(y.columns):
|
|
433
|
+
iterator = zip(y['event'], y['time'])
|
|
434
|
+
elif len(y.columns) >= 2:
|
|
435
|
+
iterator = (row[:2] for row in y.itertuples(index=False, name=None))
|
|
436
|
+
else:
|
|
437
|
+
raise ValueError("Survival DataFrame must contain at least two columns.")
|
|
438
|
+
return [cls._normalize_survival_pair(sample) for sample in iterator]
|
|
439
|
+
|
|
440
|
+
if isinstance(y, pd.Series):
|
|
441
|
+
return cls.normalize_survival_target(y.to_frame())
|
|
442
|
+
|
|
443
|
+
if isinstance(y, np.ndarray):
|
|
444
|
+
if y.dtype.names and 'event' in y.dtype.names and 'time' in y.dtype.names:
|
|
445
|
+
return [cls._normalize_survival_pair((row['event'], row['time'])) for row in y]
|
|
446
|
+
if y.ndim == 0:
|
|
447
|
+
return [cls._normalize_survival_pair(y.item())]
|
|
448
|
+
if y.ndim == 1:
|
|
449
|
+
return [cls._normalize_survival_pair(sample) for sample in y.tolist()]
|
|
450
|
+
if y.ndim >= 2 and y.shape[1] >= 2:
|
|
451
|
+
return [cls._normalize_survival_pair(sample[:2]) for sample in y]
|
|
452
|
+
|
|
453
|
+
if isinstance(y, (list, tuple)):
|
|
454
|
+
return [cls._normalize_survival_pair(sample) for sample in y]
|
|
455
|
+
|
|
456
|
+
if hasattr(y, '__iter__'):
|
|
457
|
+
return cls.normalize_survival_target(list(y))
|
|
458
|
+
|
|
459
|
+
raise TypeError(f"Unsupported survival target format: {type(y)}")
|
|
460
|
+
|
|
461
|
+
@classmethod
|
|
462
|
+
def fix_survival(cls, X: pd.DataFrame, y: Any) -> tuple[pd.DataFrame, np.ndarray]:
|
|
463
|
+
"""Turn dataframe to survival compatibility
|
|
464
|
+
|
|
465
|
+
:param pd.DataFrame X: The dataframe to fix.
|
|
466
|
+
:param Any y: The dataframe target to fix.
|
|
467
|
+
:return: Fixed dataframe
|
|
468
|
+
"""
|
|
469
|
+
from sksurv.util import Surv
|
|
470
|
+
|
|
471
|
+
samples = cls.normalize_survival_target(y)
|
|
472
|
+
if samples:
|
|
473
|
+
events, times = zip(*samples)
|
|
474
|
+
y_surv = Surv.from_arrays(
|
|
475
|
+
event=np.asarray(events, dtype=bool),
|
|
476
|
+
time=np.asarray(times, dtype=float)
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
y_surv = np.array([], dtype=[('event', 'bool'), ('time', 'float')])
|
|
480
|
+
|
|
481
|
+
X = deepcopy(X)
|
|
482
|
+
X[X.select_dtypes(include=['float64']).columns] = \
|
|
483
|
+
X.select_dtypes(include=['float64']).astype('float32')
|
|
484
|
+
|
|
485
|
+
return X, y_surv
|
|
486
|
+
|
|
487
|
+
@classmethod
|
|
488
|
+
def fix_y_survival(cls, y: Any, y_train: Any) -> list[tuple[bool, float]]:
|
|
489
|
+
"""Adjust survival targets to avoid censoring beyond the training horizon."""
|
|
490
|
+
y_samples = cls.normalize_survival_target(y)
|
|
491
|
+
y_train_samples = cls.normalize_survival_target(y_train)
|
|
492
|
+
|
|
493
|
+
if not y_train_samples:
|
|
494
|
+
return y_samples
|
|
495
|
+
|
|
496
|
+
_, times = zip(*y_train_samples)
|
|
497
|
+
censure_time = max(times)
|
|
498
|
+
|
|
499
|
+
new_y: list[tuple[bool, float]] = []
|
|
500
|
+
for event, time in y_samples:
|
|
501
|
+
if time >= censure_time:
|
|
502
|
+
time = censure_time
|
|
503
|
+
event = False
|
|
504
|
+
new_y.append((event, time))
|
|
505
|
+
|
|
506
|
+
return new_y
|
iaml/decorators/all.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Class decorator -> Must decorate each Step inheritance"""
|
|
2
|
+
from ..step import Step
|
|
3
|
+
|
|
4
|
+
def is_step(*tags) -> callable:
|
|
5
|
+
"""is_step is needed to declare new Step.
|
|
6
|
+
With the Step inheritance, it will setup everything to make it work smoothly
|
|
7
|
+
|
|
8
|
+
:param tuple, optional tags: Your Step will be attached to these tags.
|
|
9
|
+
tags are use to easily include Step into Pipeline
|
|
10
|
+
:return: Step decorator
|
|
11
|
+
"""
|
|
12
|
+
def step_wrapper(cls) -> Step:
|
|
13
|
+
"""Register the step with IAML.
|
|
14
|
+
Add call to Step.__init__() so the Sub Step developer have one to care about this
|
|
15
|
+
|
|
16
|
+
:return: Edited step class
|
|
17
|
+
"""
|
|
18
|
+
Step.available_steps[cls] = tags # Declare your Step to IAML
|
|
19
|
+
|
|
20
|
+
# Help Python to find parent class
|
|
21
|
+
__class__ = cls # pylint: disable=unused-variable
|
|
22
|
+
|
|
23
|
+
initial_init = cls.__init__ # Keep the __init__ you have created
|
|
24
|
+
def __init__(self, *args, **kw):
|
|
25
|
+
if cls != Step:
|
|
26
|
+
super().__init__(*args, **kw) # All parent constructor
|
|
27
|
+
|
|
28
|
+
self.tags = set(tags)
|
|
29
|
+
initial_init(self, *args, **kw) # Run your __init__
|
|
30
|
+
self.default_configuration() # Setup default configuration
|
|
31
|
+
|
|
32
|
+
cls.__init__ = __init__ # Replace your init
|
|
33
|
+
|
|
34
|
+
return cls
|
|
35
|
+
|
|
36
|
+
return step_wrapper
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def find_steps_by_tag(tag: str) -> list[Step]:
|
|
40
|
+
"""Retrieves all registered steps with a specific tag.
|
|
41
|
+
|
|
42
|
+
:param str tag: Tag to search for
|
|
43
|
+
:return: Matching steps
|
|
44
|
+
"""
|
|
45
|
+
return set(filter(lambda key: tag in Step.available_steps[key], Step.available_steps.keys()))
|