upgini 1.2.167a4220.dev1__tar.gz → 1.2.167a4220.dev3__tar.gz
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.
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/PKG-INFO +1 -1
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/pyproject.toml +3 -0
- upgini-1.2.167a4220.dev3/src/upgini/__about__.py +1 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/features_enricher.py +105 -68
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/metrics.py +40 -6
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/report/assemble.py +44 -4
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/report/data.py +22 -2
- upgini-1.2.167a4220.dev3/src/upgini/report/html.py +167 -0
- upgini-1.2.167a4220.dev3/src/upgini/report/template.html +732 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/strings.properties +2 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/display_utils.py +54 -37
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/track_info.py +4 -0
- upgini-1.2.167a4220.dev1/src/upgini/__about__.py +0 -1
- upgini-1.2.167a4220.dev1/src/upgini/report/html.py +0 -281
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/.gitignore +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/LICENSE +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/README.md +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/ads.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/ads_management/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/ads_management/ads_manager.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/all_operators.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/binary.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/date.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/feature.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/groupby.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/operand.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/operator.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/base.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/cross.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/delta.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/lag.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/numpy_kernels.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/roll.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/trend.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/volatility.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/unary.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/vector.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/data_source/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/data_source/data_source_publisher.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/dataset.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/errors.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/http.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/mdc/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/mdc/context.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/metadata.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/normalizer/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/normalizer/normalize_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/report/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/exceptions.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/strings_widget.properties +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/base.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/random_under_sampler.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/search_task.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/spinner.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/Roboto-Regular.ttf +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/__init__.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/base_search_key_detector.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/blocked_time_series.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/config.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/country_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/cpu_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/custom_loss_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/cv_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/datetime_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/deduplicate_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/email_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/fallback_progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/feature_info.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/features_validator.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/format.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/hash_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/ip_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/mstats.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/one_hot_encoder.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/phone_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/postal_code_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/psi.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/pyarrow_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/sample_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/search_key_derivations.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/sklearn_ext.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/sort.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/target_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/ts_utils.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/utils/warning_counter.py +0 -0
- {upgini-1.2.167a4220.dev1 → upgini-1.2.167a4220.dev3}/src/upgini/version_validator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: upgini
|
|
3
|
-
Version: 1.2.167a4220.
|
|
3
|
+
Version: 1.2.167a4220.dev3
|
|
4
4
|
Summary: Intelligent data search & enrichment for Machine Learning
|
|
5
5
|
Project-URL: Bug Reports, https://github.com/upgini/upgini/issues
|
|
6
6
|
Project-URL: Homepage, https://upgini.com/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.167.a4220.dev3"
|
|
@@ -68,7 +68,13 @@ from upgini.metadata import (
|
|
|
68
68
|
RuntimeParameters,
|
|
69
69
|
SearchKey,
|
|
70
70
|
)
|
|
71
|
-
from upgini.metrics import
|
|
71
|
+
from upgini.metrics import (
|
|
72
|
+
EstimatorWrapper,
|
|
73
|
+
define_scorer,
|
|
74
|
+
format_metrics_for_display,
|
|
75
|
+
put_cv_metric,
|
|
76
|
+
validate_scoring_argument,
|
|
77
|
+
)
|
|
72
78
|
from upgini.normalizer.normalize_utils import Normalizer
|
|
73
79
|
from upgini.resource_bundle import ResourceBundle, bundle, get_custom_bundle
|
|
74
80
|
from upgini.search_task import SearchTask
|
|
@@ -179,9 +185,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
179
185
|
|
|
180
186
|
shared_datasets: list of str, optional (default=None)
|
|
181
187
|
list of private shared dataset ids for custom search
|
|
182
|
-
|
|
183
|
-
reports_path: str, optional (default=None)
|
|
184
|
-
Directory for the HTML search report. Defaults to a `reports` folder in the current working directory.
|
|
185
188
|
"""
|
|
186
189
|
|
|
187
190
|
TARGET_NAME = "target"
|
|
@@ -259,7 +262,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
259
262
|
sample_config: SampleConfig | None = None,
|
|
260
263
|
print_trace_id: bool = False,
|
|
261
264
|
print_loaded_report: bool = True,
|
|
262
|
-
reports_path: str | None = None,
|
|
263
265
|
**kwargs,
|
|
264
266
|
):
|
|
265
267
|
self.bundle = get_custom_bundle(custom_bundle_config)
|
|
@@ -318,6 +320,7 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
318
320
|
self.relevant_data_sources: pd.DataFrame = self.EMPTY_DATA_SOURCES
|
|
319
321
|
self._relevant_data_sources_wo_links: pd.DataFrame = self.EMPTY_DATA_SOURCES
|
|
320
322
|
self.metrics: pd.DataFrame | None = None
|
|
323
|
+
self.metrics_raw: pd.DataFrame | None = None
|
|
321
324
|
self.metrics_metric_name: str | None = None
|
|
322
325
|
self.feature_names_ = []
|
|
323
326
|
self.external_source_feature_names = []
|
|
@@ -328,9 +331,8 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
328
331
|
self.disable_force_downsampling = disable_force_downsampling
|
|
329
332
|
self.print_trace_id = print_trace_id
|
|
330
333
|
self.add_info: AddInfo | None = None
|
|
331
|
-
self.reports_path = reports_path
|
|
332
334
|
self.search_duration_seconds: float | None = None
|
|
333
|
-
self.
|
|
335
|
+
self.report_html: str | None = None
|
|
334
336
|
|
|
335
337
|
if search_id:
|
|
336
338
|
search_task = SearchTask(search_id, rest_client=self.rest_client, logger=self.logger)
|
|
@@ -1108,8 +1110,8 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1108
1110
|
|
|
1109
1111
|
# 1 If client features are presented - fit and predict with KFold estimator
|
|
1110
1112
|
# on etalon features and calculate baseline metric
|
|
1111
|
-
baseline_metric = None
|
|
1112
1113
|
baseline_estimator = None
|
|
1114
|
+
baseline_cv_result = None
|
|
1113
1115
|
updating_shaps = None
|
|
1114
1116
|
custom_loss_add_params = get_additional_params_custom_loss(
|
|
1115
1117
|
self.loss, model_task_type, logger=self.logger
|
|
@@ -1133,22 +1135,23 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1133
1135
|
baseline_cv_result = baseline_estimator.cross_val_predict(
|
|
1134
1136
|
fitting_X, y_sorted, self.baseline_score_column
|
|
1135
1137
|
)
|
|
1136
|
-
|
|
1137
|
-
if baseline_metric is None:
|
|
1138
|
+
if baseline_cv_result.metric is None:
|
|
1138
1139
|
self.logger.info(
|
|
1139
1140
|
f"Baseline {metric} on train client features is None (maybe all features was removed)"
|
|
1140
1141
|
)
|
|
1141
1142
|
baseline_estimator = None
|
|
1142
1143
|
else:
|
|
1143
|
-
self.logger.info(
|
|
1144
|
+
self.logger.info(
|
|
1145
|
+
f"Baseline {metric} on train client features: {baseline_cv_result.get_display_metric()}"
|
|
1146
|
+
)
|
|
1144
1147
|
updating_shaps = baseline_cv_result.shap_values
|
|
1145
1148
|
|
|
1146
1149
|
# 2 Fit and predict with KFold estimator on enriched tds
|
|
1147
1150
|
# and calculate final metric (and uplift)
|
|
1148
|
-
enriched_metric = None
|
|
1149
1151
|
uplift = None
|
|
1150
1152
|
uplift_perc = None
|
|
1151
1153
|
enriched_estimator = None
|
|
1154
|
+
enriched_cv_result = None
|
|
1152
1155
|
ensemble_score_column = self._get_ensemble_score_column(fitting_X, fitting_enriched_X)
|
|
1153
1156
|
|
|
1154
1157
|
def evaluate_enriched(score_column, description: str):
|
|
@@ -1167,17 +1170,16 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1167
1170
|
cv_result = wrapper.cross_val_predict(
|
|
1168
1171
|
fitting_enriched_X, enriched_y_sorted, score_column
|
|
1169
1172
|
)
|
|
1170
|
-
|
|
1171
|
-
if display_metric is None:
|
|
1173
|
+
if cv_result.metric is None:
|
|
1172
1174
|
self.logger.warning(f"Enriched {metric} {description} is None")
|
|
1173
|
-
return None,
|
|
1174
|
-
self.logger.info(f"Enriched {metric} {description}: {
|
|
1175
|
+
return None, cv_result, None, None
|
|
1176
|
+
self.logger.info(f"Enriched {metric} {description}: {cv_result.get_display_metric()}")
|
|
1175
1177
|
sample_uplift = None
|
|
1176
1178
|
sample_uplift_perc = None
|
|
1177
|
-
if
|
|
1179
|
+
if baseline_cv_result is not None and baseline_cv_result.metric is not None:
|
|
1178
1180
|
sample_uplift = (cv_result.metric - baseline_cv_result.metric) * multiplier
|
|
1179
1181
|
sample_uplift_perc = sample_uplift / abs(baseline_cv_result.metric) * 100
|
|
1180
|
-
return wrapper,
|
|
1182
|
+
return wrapper, cv_result, sample_uplift, sample_uplift_perc
|
|
1181
1183
|
|
|
1182
1184
|
if ensemble_score_column is not None:
|
|
1183
1185
|
self.logger.info(
|
|
@@ -1186,14 +1188,14 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1186
1188
|
)
|
|
1187
1189
|
(
|
|
1188
1190
|
enriched_estimator,
|
|
1189
|
-
|
|
1190
|
-
updating_shaps,
|
|
1191
|
+
enriched_cv_result,
|
|
1191
1192
|
uplift,
|
|
1192
1193
|
uplift_perc,
|
|
1193
1194
|
) = evaluate_enriched(
|
|
1194
1195
|
ensemble_score_column,
|
|
1195
1196
|
f"on ensemble score column {ensemble_score_column}",
|
|
1196
1197
|
)
|
|
1198
|
+
updating_shaps = enriched_cv_result.shap_values
|
|
1197
1199
|
elif set(fitting_X.columns) != set(fitting_enriched_X.columns):
|
|
1198
1200
|
self.logger.info(
|
|
1199
1201
|
f"Calculate enriched {metric} on train combined "
|
|
@@ -1201,11 +1203,11 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1201
1203
|
)
|
|
1202
1204
|
(
|
|
1203
1205
|
enriched_estimator,
|
|
1204
|
-
|
|
1205
|
-
updating_shaps,
|
|
1206
|
+
enriched_cv_result,
|
|
1206
1207
|
uplift,
|
|
1207
1208
|
uplift_perc,
|
|
1208
1209
|
) = evaluate_enriched(None, "on train combined features")
|
|
1210
|
+
updating_shaps = enriched_cv_result.shap_values
|
|
1209
1211
|
|
|
1210
1212
|
train_metrics = {
|
|
1211
1213
|
self.bundle.get("quality_metrics_segment_header"): self.bundle.get(
|
|
@@ -1223,14 +1225,22 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1223
1225
|
np.mean(y_sorted),
|
|
1224
1226
|
4,
|
|
1225
1227
|
)
|
|
1226
|
-
|
|
1227
|
-
train_metrics
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1228
|
+
put_cv_metric(
|
|
1229
|
+
train_metrics,
|
|
1230
|
+
"quality_metrics_baseline_header",
|
|
1231
|
+
"quality_metrics_baseline_std_header",
|
|
1232
|
+
baseline_cv_result,
|
|
1233
|
+
metric,
|
|
1234
|
+
self.bundle,
|
|
1235
|
+
)
|
|
1236
|
+
put_cv_metric(
|
|
1237
|
+
train_metrics,
|
|
1238
|
+
"quality_metrics_enriched_header",
|
|
1239
|
+
"quality_metrics_enriched_std_header",
|
|
1240
|
+
enriched_cv_result,
|
|
1241
|
+
metric,
|
|
1242
|
+
self.bundle,
|
|
1243
|
+
)
|
|
1234
1244
|
if uplift is not None:
|
|
1235
1245
|
train_metrics[self.bundle.get("quality_metrics_uplift_header")] = round(uplift, 3)
|
|
1236
1246
|
train_metrics[self.bundle.get("quality_metrics_uplift_perc_header")] = (
|
|
@@ -1262,12 +1272,13 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1262
1272
|
etalon_eval_results = baseline_estimator.calculate_metric(
|
|
1263
1273
|
eval_X_sorted, eval_y_sorted, self.baseline_score_column
|
|
1264
1274
|
)
|
|
1265
|
-
etalon_eval_metric = etalon_eval_results.get_display_metric()
|
|
1266
1275
|
self.logger.info(
|
|
1267
|
-
|
|
1276
|
+
"Baseline {} on eval set {} client features: {}".format(
|
|
1277
|
+
metric, idx + 1, etalon_eval_results.get_display_metric()
|
|
1278
|
+
)
|
|
1268
1279
|
)
|
|
1269
1280
|
else:
|
|
1270
|
-
|
|
1281
|
+
etalon_eval_results = None
|
|
1271
1282
|
|
|
1272
1283
|
if enriched_estimator is not None:
|
|
1273
1284
|
self.logger.info(
|
|
@@ -1277,14 +1288,20 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1277
1288
|
enriched_eval_results = enriched_estimator.calculate_metric(
|
|
1278
1289
|
enriched_eval_X_sorted, enriched_eval_y_sorted, ensemble_score_column
|
|
1279
1290
|
)
|
|
1280
|
-
enriched_eval_metric = enriched_eval_results.get_display_metric()
|
|
1281
1291
|
self.logger.info(
|
|
1282
|
-
|
|
1292
|
+
"Enriched {} on eval set {} combined features: {}".format(
|
|
1293
|
+
metric, idx + 1, enriched_eval_results.get_display_metric()
|
|
1294
|
+
)
|
|
1283
1295
|
)
|
|
1284
1296
|
else:
|
|
1285
|
-
|
|
1297
|
+
enriched_eval_results = None
|
|
1286
1298
|
|
|
1287
|
-
if
|
|
1299
|
+
if (
|
|
1300
|
+
etalon_eval_results is not None
|
|
1301
|
+
and etalon_eval_results.metric is not None
|
|
1302
|
+
and enriched_eval_results is not None
|
|
1303
|
+
and enriched_eval_results.metric is not None
|
|
1304
|
+
):
|
|
1288
1305
|
eval_uplift = (enriched_eval_results.metric - etalon_eval_results.metric) * multiplier
|
|
1289
1306
|
eval_uplift_perc = eval_uplift / abs(etalon_eval_results.metric) * 100
|
|
1290
1307
|
else:
|
|
@@ -1308,14 +1325,22 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1308
1325
|
np.mean(eval_y_sorted),
|
|
1309
1326
|
4,
|
|
1310
1327
|
)
|
|
1311
|
-
|
|
1312
|
-
eval_metrics
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1328
|
+
put_cv_metric(
|
|
1329
|
+
eval_metrics,
|
|
1330
|
+
"quality_metrics_baseline_header",
|
|
1331
|
+
"quality_metrics_baseline_std_header",
|
|
1332
|
+
etalon_eval_results,
|
|
1333
|
+
metric,
|
|
1334
|
+
self.bundle,
|
|
1335
|
+
)
|
|
1336
|
+
put_cv_metric(
|
|
1337
|
+
eval_metrics,
|
|
1338
|
+
"quality_metrics_enriched_header",
|
|
1339
|
+
"quality_metrics_enriched_std_header",
|
|
1340
|
+
enriched_eval_results,
|
|
1341
|
+
metric,
|
|
1342
|
+
self.bundle,
|
|
1343
|
+
)
|
|
1319
1344
|
if eval_uplift is not None:
|
|
1320
1345
|
eval_metrics[self.bundle.get("quality_metrics_uplift_header")] = round(eval_uplift, 3)
|
|
1321
1346
|
eval_metrics[self.bundle.get("quality_metrics_uplift_perc_header")] = (
|
|
@@ -1352,8 +1377,9 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
1352
1377
|
elif uplift_col in metrics_df.columns and (metrics_df[uplift_col] < 0).any():
|
|
1353
1378
|
self.logger.warning("Uplift is negative")
|
|
1354
1379
|
|
|
1380
|
+
self.metrics_raw = metrics_df
|
|
1355
1381
|
self.metrics_metric_name = metric
|
|
1356
|
-
return metrics_df
|
|
1382
|
+
return format_metrics_for_display(metrics_df, metric, self.bundle)
|
|
1357
1383
|
except Exception as e:
|
|
1358
1384
|
error_message = "Failed to calculate metrics" + (
|
|
1359
1385
|
" with validation error" if isinstance(e, ValidationError) else ""
|
|
@@ -2274,9 +2300,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2274
2300
|
return None
|
|
2275
2301
|
return ensemble[0]
|
|
2276
2302
|
|
|
2277
|
-
def _reports_dir(self) -> Path:
|
|
2278
|
-
return Path(self.reports_path) if self.reports_path else Path.cwd() / "reports"
|
|
2279
|
-
|
|
2280
2303
|
def _reference_rows(self) -> int | None:
|
|
2281
2304
|
if self.X is None:
|
|
2282
2305
|
return None
|
|
@@ -2299,23 +2322,19 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2299
2322
|
search_duration_seconds=self.search_duration_seconds,
|
|
2300
2323
|
reference_rows=self._reference_rows(),
|
|
2301
2324
|
samples=self._report_sample_names(),
|
|
2302
|
-
metrics_df=self.metrics,
|
|
2325
|
+
metrics_df=self.metrics_raw if self.metrics_raw is not None else self.metrics,
|
|
2303
2326
|
metric_name=self.metrics_metric_name,
|
|
2327
|
+
model_features=self._ensemble_model_feature_count(),
|
|
2328
|
+
is_binary=self.model_task_type == ModelTaskType.BINARY,
|
|
2304
2329
|
bundle=self.bundle,
|
|
2305
2330
|
)
|
|
2306
2331
|
|
|
2307
|
-
def
|
|
2332
|
+
def _score_report_html(self) -> str | None:
|
|
2308
2333
|
if self._search_task is None or not self._has_single_ensemble_score():
|
|
2309
2334
|
return None
|
|
2310
2335
|
report_data = self._assemble_report_data()
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
2314
|
-
path = reports_dir / f"upgini-report-{report_data.metadata.search_id}.html"
|
|
2315
|
-
path.write_text(html, encoding="utf-8")
|
|
2316
|
-
self.report_file_path = str(path)
|
|
2317
|
-
self.logger.info(f"Score report saved to {path}")
|
|
2318
|
-
return self.report_file_path
|
|
2336
|
+
self.report_html = generate_html_report(report_data)
|
|
2337
|
+
return self.report_html
|
|
2319
2338
|
|
|
2320
2339
|
def _is_ensemble_feature(self, column_name: str) -> bool:
|
|
2321
2340
|
renaming = self.fit_columns_renaming or {}
|
|
@@ -2325,15 +2344,20 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2325
2344
|
return False
|
|
2326
2345
|
return bool(aliases & self._column_name_aliases(list(ensemble_names), renaming))
|
|
2327
2346
|
|
|
2328
|
-
def
|
|
2347
|
+
def _ensemble_generated_metadata(self):
|
|
2329
2348
|
autofe_meta = self._search_task.get_autofe_metadata() if self._search_task else None
|
|
2330
2349
|
if not autofe_meta:
|
|
2331
|
-
return
|
|
2332
|
-
|
|
2350
|
+
return []
|
|
2351
|
+
matched = []
|
|
2333
2352
|
for meta in autofe_meta:
|
|
2334
2353
|
op = EnsembleModel.from_formula(meta.formula.split("(")[0])
|
|
2335
|
-
if op is None:
|
|
2336
|
-
|
|
2354
|
+
if op is not None:
|
|
2355
|
+
matched.append((meta, op))
|
|
2356
|
+
return matched
|
|
2357
|
+
|
|
2358
|
+
def _ensemble_feature_names_from_metadata(self) -> set[str]:
|
|
2359
|
+
names: set[str] = set()
|
|
2360
|
+
for meta, op in self._ensemble_generated_metadata():
|
|
2337
2361
|
feature = (
|
|
2338
2362
|
Feature(op, [])
|
|
2339
2363
|
.set_display_index(meta.display_index)
|
|
@@ -2345,6 +2369,14 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2345
2369
|
names.add(meta.alias)
|
|
2346
2370
|
return names
|
|
2347
2371
|
|
|
2372
|
+
def _ensemble_model_feature_count(self) -> int | None:
|
|
2373
|
+
names = {
|
|
2374
|
+
column.original_name
|
|
2375
|
+
for meta, _ in self._ensemble_generated_metadata()
|
|
2376
|
+
for column in meta.base_columns
|
|
2377
|
+
}
|
|
2378
|
+
return len(names) or None
|
|
2379
|
+
|
|
2348
2380
|
def _psi_exceeds_threshold(self, psi: float | None, threshold: float) -> bool:
|
|
2349
2381
|
return psi is not None and not pd.isna(psi) and psi > threshold
|
|
2350
2382
|
|
|
@@ -4219,7 +4251,9 @@ if response.status_code == 200:
|
|
|
4219
4251
|
self._fit_match_search_keys = None
|
|
4220
4252
|
self.__cached_sampled_datasets = dict()
|
|
4221
4253
|
self.metrics = None
|
|
4254
|
+
self.metrics_raw = None
|
|
4222
4255
|
self.metrics_metric_name = None
|
|
4256
|
+
self.report_html = None
|
|
4223
4257
|
self.fit_columns_renaming = None
|
|
4224
4258
|
self.fit_dropped_features = set()
|
|
4225
4259
|
self.fit_generated_features = []
|
|
@@ -6037,13 +6071,16 @@ if response.status_code == 200:
|
|
|
6037
6071
|
|
|
6038
6072
|
def __show_report_button(self, display_id: str | None = None, display_handle=None):
|
|
6039
6073
|
try:
|
|
6040
|
-
|
|
6041
|
-
if
|
|
6074
|
+
report_html = self._score_report_html() or self.report_html
|
|
6075
|
+
if report_html:
|
|
6042
6076
|
if not ipython_available():
|
|
6043
|
-
print(f"Full report saved to {report_path}")
|
|
6044
6077
|
return
|
|
6078
|
+
search_id = self._search_task.search_task_id if self._search_task is not None else (self.search_id or "")
|
|
6045
6079
|
return show_button_open_report(
|
|
6046
|
-
|
|
6080
|
+
report_html,
|
|
6081
|
+
download_name=f"upgini-report-{search_id}.html",
|
|
6082
|
+
display_id=display_id,
|
|
6083
|
+
display_handle=display_handle,
|
|
6047
6084
|
)
|
|
6048
6085
|
|
|
6049
6086
|
eval_sets_drift_df = self._get_eval_sets_drift_summary()
|
|
@@ -253,12 +253,46 @@ class _CrossValResults:
|
|
|
253
253
|
shap_values: Optional[Dict[str, float]]
|
|
254
254
|
|
|
255
255
|
def get_display_metric(self) -> Optional[str]:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
256
|
+
return format_display_metric(self.metric, self.metric_std)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def format_display_metric(metric: Optional[float], metric_std: Optional[float] = None) -> Optional[str]:
|
|
260
|
+
if metric is None or pd.isna(metric):
|
|
261
|
+
return None
|
|
262
|
+
if metric_std is None or pd.isna(metric_std):
|
|
263
|
+
return f"{float(metric):.3f}"
|
|
264
|
+
return f"{float(metric):.3f} ± {float(metric_std):.3f}"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def format_metrics_for_display(metrics_df: pd.DataFrame, metric_name: str, bundle) -> pd.DataFrame:
|
|
268
|
+
display = metrics_df.copy()
|
|
269
|
+
drop: list[str] = []
|
|
270
|
+
for value_key, std_key in (
|
|
271
|
+
("quality_metrics_baseline_header", "quality_metrics_baseline_std_header"),
|
|
272
|
+
("quality_metrics_enriched_header", "quality_metrics_enriched_std_header"),
|
|
273
|
+
):
|
|
274
|
+
col = bundle.get(value_key).format(metric_name)
|
|
275
|
+
std_col = bundle.get(std_key).format(metric_name)
|
|
276
|
+
if col in display.columns:
|
|
277
|
+
stds = display[std_col] if std_col in display.columns else [None] * len(display)
|
|
278
|
+
display[col] = [format_display_metric(value, std) for value, std in zip(display[col], stds)]
|
|
279
|
+
if std_col in display.columns:
|
|
280
|
+
drop.append(std_col)
|
|
281
|
+
return display.drop(columns=drop, errors="ignore")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def put_cv_metric(
|
|
285
|
+
row: dict,
|
|
286
|
+
value_key: str,
|
|
287
|
+
std_key: str,
|
|
288
|
+
result: Optional[_CrossValResults],
|
|
289
|
+
metric_name: str,
|
|
290
|
+
bundle,
|
|
291
|
+
) -> None:
|
|
292
|
+
if result is None or result.metric is None:
|
|
293
|
+
return
|
|
294
|
+
row[bundle.get(value_key).format(metric_name)] = result.metric
|
|
295
|
+
row[bundle.get(std_key).format(metric_name)] = result.metric_std
|
|
262
296
|
|
|
263
297
|
|
|
264
298
|
def is_numeric_object(x: pd.Series) -> bool:
|
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
8
|
from upgini.__about__ import __version__
|
|
9
|
-
from upgini.report.data import QualitySample, ReportData, ReportMetadata
|
|
9
|
+
from upgini.report.data import QualitySample, ReportData, ReportMetadata, SampleStats, SearchResultsSummary
|
|
10
10
|
from upgini.resource_bundle import ResourceBundle
|
|
11
11
|
|
|
12
12
|
|
|
@@ -34,9 +34,13 @@ def assemble_report_data(
|
|
|
34
34
|
samples: Optional[list[str]] = None,
|
|
35
35
|
metrics_df: Optional[pd.DataFrame] = None,
|
|
36
36
|
metric_name: Optional[str] = None,
|
|
37
|
+
model_features: Optional[int] = None,
|
|
38
|
+
is_binary: bool = False,
|
|
39
|
+
logo_url: Optional[str] = None,
|
|
37
40
|
bundle: ResourceBundle,
|
|
38
41
|
) -> ReportData:
|
|
39
42
|
generated_at = generated_at or datetime.now(timezone.utc)
|
|
43
|
+
sample_names = list(samples or [])
|
|
40
44
|
return ReportData(
|
|
41
45
|
metadata=ReportMetadata(
|
|
42
46
|
search_id=search_id,
|
|
@@ -45,9 +49,13 @@ def assemble_report_data(
|
|
|
45
49
|
search_keys=list(search_keys or []),
|
|
46
50
|
search_duration=format_search_duration(search_duration_seconds),
|
|
47
51
|
reference_rows=reference_rows,
|
|
48
|
-
samples=
|
|
52
|
+
samples=sample_names,
|
|
53
|
+
logo_url=logo_url or None,
|
|
49
54
|
),
|
|
50
55
|
quality_by_sample=_quality_samples(metrics_df, metric_name, bundle),
|
|
56
|
+
summary=SearchResultsSummary(model_features=model_features),
|
|
57
|
+
sample_stats=_sample_stats(sample_names, metrics_df, bundle),
|
|
58
|
+
is_binary=is_binary,
|
|
51
59
|
)
|
|
52
60
|
|
|
53
61
|
|
|
@@ -61,6 +69,8 @@ def _quality_samples(
|
|
|
61
69
|
uplift_pct_col = bundle.get("quality_metrics_uplift_perc_header")
|
|
62
70
|
baseline_col = bundle.get("quality_metrics_baseline_header").format(metric_name) if metric_name else None
|
|
63
71
|
enriched_col = bundle.get("quality_metrics_enriched_header").format(metric_name) if metric_name else None
|
|
72
|
+
baseline_std_col = bundle.get("quality_metrics_baseline_std_header").format(metric_name) if metric_name else None
|
|
73
|
+
enriched_std_col = bundle.get("quality_metrics_enriched_std_header").format(metric_name) if metric_name else None
|
|
64
74
|
|
|
65
75
|
samples: list[QualitySample] = []
|
|
66
76
|
for _, row in metrics_df.iterrows():
|
|
@@ -68,8 +78,10 @@ def _quality_samples(
|
|
|
68
78
|
QualitySample(
|
|
69
79
|
evaluation_scope=(_as_str(row[segment_col]) or "") if segment_col in metrics_df.columns else "",
|
|
70
80
|
metric=metric_name or "",
|
|
71
|
-
baseline=
|
|
72
|
-
|
|
81
|
+
baseline=_as_float(row[baseline_col]) if baseline_col in metrics_df.columns else None,
|
|
82
|
+
baseline_std=_as_float(row[baseline_std_col]) if baseline_std_col in metrics_df.columns else None,
|
|
83
|
+
enriched=_as_float(row[enriched_col]) if enriched_col in metrics_df.columns else None,
|
|
84
|
+
enriched_std=_as_float(row[enriched_std_col]) if enriched_std_col in metrics_df.columns else None,
|
|
73
85
|
uplift=_as_float(row[uplift_col]) if uplift_col in metrics_df.columns else None,
|
|
74
86
|
relative_uplift=_as_str(row[uplift_pct_col]) if uplift_pct_col in metrics_df.columns else None,
|
|
75
87
|
)
|
|
@@ -77,9 +89,37 @@ def _quality_samples(
|
|
|
77
89
|
return samples
|
|
78
90
|
|
|
79
91
|
|
|
92
|
+
def _sample_stats(
|
|
93
|
+
sample_names: list[str], metrics_df: Optional[pd.DataFrame], bundle: ResourceBundle
|
|
94
|
+
) -> list[SampleStats]:
|
|
95
|
+
segment_col = bundle.get("quality_metrics_segment_header")
|
|
96
|
+
rows_col = bundle.get("quality_metrics_rows_header")
|
|
97
|
+
mean_col = bundle.get("quality_metrics_mean_target_header")
|
|
98
|
+
by_name: dict[str, SampleStats] = {}
|
|
99
|
+
if metrics_df is not None and not metrics_df.empty and segment_col in metrics_df.columns:
|
|
100
|
+
for _, row in metrics_df.iterrows():
|
|
101
|
+
name = _as_str(row[segment_col])
|
|
102
|
+
if not name:
|
|
103
|
+
continue
|
|
104
|
+
by_name[name] = SampleStats(
|
|
105
|
+
sample=name,
|
|
106
|
+
rows=_as_int(row[rows_col]) if rows_col in metrics_df.columns else None,
|
|
107
|
+
mean_target=_as_float(row[mean_col]) if mean_col in metrics_df.columns else None,
|
|
108
|
+
)
|
|
109
|
+
names = sample_names or list(by_name)
|
|
110
|
+
stats = [by_name.get(name) or SampleStats(sample=name) for name in names]
|
|
111
|
+
seen = set(names)
|
|
112
|
+
stats.extend(sample for name, sample in by_name.items() if name not in seen)
|
|
113
|
+
return stats
|
|
114
|
+
|
|
115
|
+
|
|
80
116
|
def _as_str(value) -> Optional[str]:
|
|
81
117
|
return None if pd.isna(value) else str(value)
|
|
82
118
|
|
|
83
119
|
|
|
84
120
|
def _as_float(value) -> Optional[float]:
|
|
85
121
|
return None if pd.isna(value) else float(value)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _as_int(value) -> Optional[int]:
|
|
125
|
+
return None if pd.isna(value) else int(value)
|
|
@@ -13,17 +13,35 @@ class ReportMetadata:
|
|
|
13
13
|
search_duration: Optional[str] = None
|
|
14
14
|
reference_rows: Optional[int] = None
|
|
15
15
|
samples: list[str] = field(default_factory=list)
|
|
16
|
+
logo_url: Optional[str] = None
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@dataclass
|
|
19
20
|
class QualitySample:
|
|
20
21
|
evaluation_scope: str
|
|
21
22
|
metric: str
|
|
22
|
-
baseline: Optional[
|
|
23
|
-
|
|
23
|
+
baseline: Optional[float] = None
|
|
24
|
+
baseline_std: Optional[float] = None
|
|
25
|
+
enriched: Optional[float] = None
|
|
26
|
+
enriched_std: Optional[float] = None
|
|
24
27
|
uplift: Optional[float] = None
|
|
25
28
|
relative_uplift: Optional[str] = None
|
|
26
29
|
|
|
30
|
+
@property
|
|
31
|
+
def std(self) -> Optional[float]:
|
|
32
|
+
return self.enriched_std if self.enriched_std is not None else self.baseline_std
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class SampleStats:
|
|
37
|
+
sample: str
|
|
38
|
+
rows: Optional[int] = None
|
|
39
|
+
mean_target: Optional[float] = None
|
|
40
|
+
date_range: Optional[str] = None
|
|
41
|
+
labeled: Optional[int] = None
|
|
42
|
+
unlabeled: Optional[int] = None
|
|
43
|
+
positive: Optional[int] = None
|
|
44
|
+
|
|
27
45
|
|
|
28
46
|
@dataclass
|
|
29
47
|
class FeatureRow:
|
|
@@ -66,6 +84,8 @@ class ReportData:
|
|
|
66
84
|
metadata: ReportMetadata
|
|
67
85
|
quality_by_sample: list[QualitySample] = field(default_factory=list)
|
|
68
86
|
summary: SearchResultsSummary = field(default_factory=SearchResultsSummary)
|
|
87
|
+
sample_stats: list[SampleStats] = field(default_factory=list)
|
|
88
|
+
is_binary: bool = False
|
|
69
89
|
features: list[FeatureRow] = field(default_factory=list)
|
|
70
90
|
sources: list[SourceRow] = field(default_factory=list)
|
|
71
91
|
autofe: list[dict[str, str]] = field(default_factory=list)
|