upgini 1.2.167a4220.dev2__tar.gz → 1.2.167a4220.dev4__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.dev2 → upgini-1.2.167a4220.dev4}/PKG-INFO +1 -1
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/pyproject.toml +3 -0
- upgini-1.2.167a4220.dev4/src/upgini/__about__.py +1 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/features_enricher.py +135 -25
- upgini-1.2.167a4220.dev4/src/upgini/report/__init__.py +11 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/report/assemble.py +25 -14
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/report/data.py +71 -1
- upgini-1.2.167a4220.dev4/src/upgini/report/html.py +262 -0
- upgini-1.2.167a4220.dev4/src/upgini/report/stats.py +426 -0
- upgini-1.2.167a4220.dev4/src/upgini/report/template.html +737 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/display_utils.py +31 -20
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/psi.py +20 -1
- upgini-1.2.167a4220.dev2/src/upgini/__about__.py +0 -1
- upgini-1.2.167a4220.dev2/src/upgini/report/__init__.py +0 -5
- upgini-1.2.167a4220.dev2/src/upgini/report/html.py +0 -385
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/.gitignore +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/LICENSE +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/README.md +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/ads.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/ads_management/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/ads_management/ads_manager.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/all_operators.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/binary.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/date.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/feature.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/groupby.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/operand.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/operator.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/base.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/cross.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/delta.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/lag.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/numpy_kernels.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/roll.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/trend.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/timeseries/volatility.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/unary.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/autofe/vector.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/data_source/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/data_source/data_source_publisher.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/dataset.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/errors.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/http.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/mdc/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/mdc/context.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/metadata.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/metrics.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/normalizer/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/normalizer/normalize_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/resource_bundle/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/resource_bundle/exceptions.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/resource_bundle/strings.properties +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/resource_bundle/strings_widget.properties +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/sampler/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/sampler/base.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/sampler/random_under_sampler.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/sampler/utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/search_task.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/spinner.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/Roboto-Regular.ttf +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/base_search_key_detector.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/blocked_time_series.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/config.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/country_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/cpu_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/custom_loss_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/cv_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/datetime_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/deduplicate_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/email_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/fallback_progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/feature_info.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/features_validator.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/format.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/hash_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/ip_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/mstats.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/one_hot_encoder.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/phone_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/postal_code_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/pyarrow_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/sample_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/search_key_derivations.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/sklearn_ext.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/sort.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/target_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/track_info.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/ts_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/src/upgini/utils/warning_counter.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev4}/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.dev4
|
|
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.dev4"
|
|
@@ -100,6 +100,7 @@ from upgini.utils.deduplicate_utils import (
|
|
|
100
100
|
)
|
|
101
101
|
from upgini.report.assemble import assemble_report_data
|
|
102
102
|
from upgini.report.html import generate_html_report
|
|
103
|
+
from upgini.report.stats import make_report_frame
|
|
103
104
|
from upgini.utils.display_utils import (
|
|
104
105
|
display_html_dataframe,
|
|
105
106
|
do_without_pandas_limits,
|
|
@@ -185,9 +186,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
185
186
|
|
|
186
187
|
shared_datasets: list of str, optional (default=None)
|
|
187
188
|
list of private shared dataset ids for custom search
|
|
188
|
-
|
|
189
|
-
reports_path: str, optional (default=None)
|
|
190
|
-
Directory for the HTML search report. Defaults to a `reports` folder in the current working directory.
|
|
191
189
|
"""
|
|
192
190
|
|
|
193
191
|
TARGET_NAME = "target"
|
|
@@ -265,7 +263,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
265
263
|
sample_config: SampleConfig | None = None,
|
|
266
264
|
print_trace_id: bool = False,
|
|
267
265
|
print_loaded_report: bool = True,
|
|
268
|
-
reports_path: str | None = None,
|
|
269
266
|
**kwargs,
|
|
270
267
|
):
|
|
271
268
|
self.bundle = get_custom_bundle(custom_bundle_config)
|
|
@@ -335,9 +332,8 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
335
332
|
self.disable_force_downsampling = disable_force_downsampling
|
|
336
333
|
self.print_trace_id = print_trace_id
|
|
337
334
|
self.add_info: AddInfo | None = None
|
|
338
|
-
self.reports_path = reports_path
|
|
339
335
|
self.search_duration_seconds: float | None = None
|
|
340
|
-
self.
|
|
336
|
+
self.report_html: str | None = None
|
|
341
337
|
|
|
342
338
|
if search_id:
|
|
343
339
|
search_task = SearchTask(search_id, rest_client=self.rest_client, logger=self.logger)
|
|
@@ -2305,9 +2301,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2305
2301
|
return None
|
|
2306
2302
|
return ensemble[0]
|
|
2307
2303
|
|
|
2308
|
-
def _reports_dir(self) -> Path:
|
|
2309
|
-
return Path(self.reports_path) if self.reports_path else Path.cwd() / "reports"
|
|
2310
|
-
|
|
2311
2304
|
def _reference_rows(self) -> int | None:
|
|
2312
2305
|
if self.X is None:
|
|
2313
2306
|
return None
|
|
@@ -2316,17 +2309,135 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2316
2309
|
rows += _num_samples(eval_pair[0])
|
|
2317
2310
|
return rows
|
|
2318
2311
|
|
|
2312
|
+
def _search_key_names(self) -> list[str]:
|
|
2313
|
+
return [key.name for key in self.search_keys.values()]
|
|
2314
|
+
|
|
2319
2315
|
def _report_sample_names(self) -> list[str]:
|
|
2320
2316
|
samples = [self.bundle.get("quality_metrics_train_segment")]
|
|
2321
2317
|
for idx, _ in enumerate(self.eval_set or []):
|
|
2322
2318
|
samples.append(self.bundle.get("quality_metrics_eval_segment").format(idx + 1))
|
|
2323
2319
|
return samples
|
|
2324
2320
|
|
|
2321
|
+
def _report_dataset_samples(self) -> dict[str, pd.DataFrame]:
|
|
2322
|
+
names = self._report_sample_names()
|
|
2323
|
+
if self.df_with_original_index is not None:
|
|
2324
|
+
return self._report_samples_from_combined(self.df_with_original_index, names, self.fit_search_keys)
|
|
2325
|
+
if self.X is None or self.y is None:
|
|
2326
|
+
return {}
|
|
2327
|
+
date_col = self._get_date_column(self.search_keys)
|
|
2328
|
+
frames = {names[0]: self._report_xy_frame(self.X, self.y, date_col)}
|
|
2329
|
+
for idx, (eval_x, eval_y) in enumerate(self.eval_set or []):
|
|
2330
|
+
if idx + 1 >= len(names):
|
|
2331
|
+
break
|
|
2332
|
+
frames[names[idx + 1]] = self._report_xy_frame(eval_x, eval_y, date_col)
|
|
2333
|
+
return frames
|
|
2334
|
+
|
|
2335
|
+
def _report_samples_from_combined(
|
|
2336
|
+
self, df: pd.DataFrame, names: list[str], search_keys: dict[str, SearchKey] | None
|
|
2337
|
+
) -> dict[str, pd.DataFrame]:
|
|
2338
|
+
date_col = self._get_date_column(search_keys or {})
|
|
2339
|
+
if EVAL_SET_INDEX not in df.columns:
|
|
2340
|
+
return {names[0]: self._report_df_frame(df, date_col)} if names else {}
|
|
2341
|
+
frames: dict[str, pd.DataFrame] = {}
|
|
2342
|
+
for eval_idx in sorted(pd.unique(df[EVAL_SET_INDEX].dropna())):
|
|
2343
|
+
name_idx = int(eval_idx)
|
|
2344
|
+
if name_idx >= len(names):
|
|
2345
|
+
continue
|
|
2346
|
+
frames[names[name_idx]] = self._report_df_frame(df[df[EVAL_SET_INDEX] == eval_idx], date_col)
|
|
2347
|
+
return frames
|
|
2348
|
+
|
|
2349
|
+
def _report_xy_frame(self, X, y, date_col: str | None) -> pd.DataFrame:
|
|
2350
|
+
date = X[date_col] if isinstance(X, pd.DataFrame) and date_col and date_col in X.columns else None
|
|
2351
|
+
return make_report_frame(y, date=date)
|
|
2352
|
+
|
|
2353
|
+
def _report_df_frame(self, df: pd.DataFrame, date_col: str | None) -> pd.DataFrame:
|
|
2354
|
+
target = df[TARGET] if TARGET in df.columns else pd.Series(np.nan, index=df.index)
|
|
2355
|
+
return self._report_xy_frame(df, target, date_col)
|
|
2356
|
+
|
|
2357
|
+
def _report_scored_samples(self) -> dict[str, pd.DataFrame]:
|
|
2358
|
+
cached = self._peek_cached_sampled()
|
|
2359
|
+
if cached is None:
|
|
2360
|
+
return {}
|
|
2361
|
+
X_sampled, y_sampled, enriched_X, eval_set_sampled_dict, search_keys, columns_renaming, _generated = cached
|
|
2362
|
+
score_col = self._report_score_column(enriched_X)
|
|
2363
|
+
if score_col is None:
|
|
2364
|
+
return {}
|
|
2365
|
+
date_col = self._get_date_column(search_keys or {})
|
|
2366
|
+
names = self._report_sample_names()
|
|
2367
|
+
frames = {
|
|
2368
|
+
names[0]: self._report_scored_frame(
|
|
2369
|
+
X_sampled, y_sampled, enriched_X, score_col, date_col, columns_renaming
|
|
2370
|
+
)
|
|
2371
|
+
}
|
|
2372
|
+
for idx, (eval_X, enriched_eval_X, eval_y) in eval_set_sampled_dict.items():
|
|
2373
|
+
name_idx = idx + 1
|
|
2374
|
+
if name_idx >= len(names):
|
|
2375
|
+
continue
|
|
2376
|
+
frames[names[name_idx]] = self._report_scored_frame(
|
|
2377
|
+
eval_X, eval_y, enriched_eval_X, score_col, date_col, columns_renaming
|
|
2378
|
+
)
|
|
2379
|
+
return frames
|
|
2380
|
+
|
|
2381
|
+
def _report_scored_frame(
|
|
2382
|
+
self,
|
|
2383
|
+
X: pd.DataFrame,
|
|
2384
|
+
y,
|
|
2385
|
+
enriched: pd.DataFrame,
|
|
2386
|
+
score_col: str,
|
|
2387
|
+
date_col: str | None,
|
|
2388
|
+
columns_renaming: dict[str, str] | None,
|
|
2389
|
+
) -> pd.DataFrame:
|
|
2390
|
+
target = y
|
|
2391
|
+
if target is None and TARGET in enriched.columns:
|
|
2392
|
+
target = enriched[TARGET]
|
|
2393
|
+
date_name = self._report_existing_column(date_col, columns_renaming, X, enriched)
|
|
2394
|
+
baseline_name = self._report_existing_column(self.baseline_score_column, columns_renaming, X, enriched)
|
|
2395
|
+
return make_report_frame(
|
|
2396
|
+
target,
|
|
2397
|
+
date=self._report_column_values(date_name, X, enriched),
|
|
2398
|
+
score=enriched[score_col] if score_col in enriched.columns else None,
|
|
2399
|
+
baseline=self._report_column_values(baseline_name, X, enriched),
|
|
2400
|
+
)
|
|
2401
|
+
|
|
2402
|
+
def _report_score_column(self, enriched_X: pd.DataFrame) -> str | None:
|
|
2403
|
+
name = self._get_single_ensemble_score_name()
|
|
2404
|
+
if name and name in enriched_X.columns:
|
|
2405
|
+
return name
|
|
2406
|
+
return None
|
|
2407
|
+
|
|
2408
|
+
def _report_existing_column(
|
|
2409
|
+
self, name: str | None, renaming: dict[str, str] | None, *frames: pd.DataFrame
|
|
2410
|
+
) -> str | None:
|
|
2411
|
+
if not name:
|
|
2412
|
+
return None
|
|
2413
|
+
aliases = self._column_name_aliases([name], renaming or {})
|
|
2414
|
+
for frame in frames:
|
|
2415
|
+
if not isinstance(frame, pd.DataFrame):
|
|
2416
|
+
continue
|
|
2417
|
+
for col in frame.columns:
|
|
2418
|
+
if col in aliases:
|
|
2419
|
+
return col
|
|
2420
|
+
return None
|
|
2421
|
+
|
|
2422
|
+
@staticmethod
|
|
2423
|
+
def _report_column_values(column: str | None, *frames: pd.DataFrame):
|
|
2424
|
+
if not column:
|
|
2425
|
+
return None
|
|
2426
|
+
for frame in frames:
|
|
2427
|
+
if isinstance(frame, pd.DataFrame) and column in frame.columns:
|
|
2428
|
+
return frame[column]
|
|
2429
|
+
return None
|
|
2430
|
+
|
|
2431
|
+
def _peek_cached_sampled(self):
|
|
2432
|
+
if not self.__cached_sampled_datasets:
|
|
2433
|
+
return None
|
|
2434
|
+
return max(self.__cached_sampled_datasets.values(), key=lambda item: len(item[0]) if item[0] is not None else 0)
|
|
2435
|
+
|
|
2325
2436
|
def _assemble_report_data(self):
|
|
2326
2437
|
search_id = self._search_task.search_task_id if self._search_task is not None else (self.search_id or "")
|
|
2327
2438
|
return assemble_report_data(
|
|
2328
2439
|
search_id=search_id,
|
|
2329
|
-
search_keys=
|
|
2440
|
+
search_keys=self._search_key_names(),
|
|
2330
2441
|
search_duration_seconds=self.search_duration_seconds,
|
|
2331
2442
|
reference_rows=self._reference_rows(),
|
|
2332
2443
|
samples=self._report_sample_names(),
|
|
@@ -2334,21 +2445,17 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2334
2445
|
metric_name=self.metrics_metric_name,
|
|
2335
2446
|
model_features=self._ensemble_model_feature_count(),
|
|
2336
2447
|
is_binary=self.model_task_type == ModelTaskType.BINARY,
|
|
2448
|
+
dataset_samples=self._report_dataset_samples(),
|
|
2449
|
+
scored_samples=self._report_scored_samples(),
|
|
2337
2450
|
bundle=self.bundle,
|
|
2338
2451
|
)
|
|
2339
2452
|
|
|
2340
|
-
def
|
|
2453
|
+
def _score_report_html(self) -> str | None:
|
|
2341
2454
|
if self._search_task is None or not self._has_single_ensemble_score():
|
|
2342
2455
|
return None
|
|
2343
2456
|
report_data = self._assemble_report_data()
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
2347
|
-
path = reports_dir / f"upgini-report-{report_data.metadata.search_id}.html"
|
|
2348
|
-
path.write_text(html, encoding="utf-8")
|
|
2349
|
-
self.report_file_path = str(path)
|
|
2350
|
-
self.logger.info(f"Score report saved to {path}")
|
|
2351
|
-
return self.report_file_path
|
|
2457
|
+
self.report_html = generate_html_report(report_data)
|
|
2458
|
+
return self.report_html
|
|
2352
2459
|
|
|
2353
2460
|
def _is_ensemble_feature(self, column_name: str) -> bool:
|
|
2354
2461
|
renaming = self.fit_columns_renaming or {}
|
|
@@ -4267,7 +4374,7 @@ if response.status_code == 200:
|
|
|
4267
4374
|
self.metrics = None
|
|
4268
4375
|
self.metrics_raw = None
|
|
4269
4376
|
self.metrics_metric_name = None
|
|
4270
|
-
self.
|
|
4377
|
+
self.report_html = None
|
|
4271
4378
|
self.fit_columns_renaming = None
|
|
4272
4379
|
self.fit_dropped_features = set()
|
|
4273
4380
|
self.fit_generated_features = []
|
|
@@ -6085,13 +6192,16 @@ if response.status_code == 200:
|
|
|
6085
6192
|
|
|
6086
6193
|
def __show_report_button(self, display_id: str | None = None, display_handle=None):
|
|
6087
6194
|
try:
|
|
6088
|
-
|
|
6089
|
-
if
|
|
6195
|
+
report_html = self._score_report_html() or self.report_html
|
|
6196
|
+
if report_html:
|
|
6090
6197
|
if not ipython_available():
|
|
6091
|
-
print(f"Full report saved to {report_path}")
|
|
6092
6198
|
return
|
|
6199
|
+
search_id = self._search_task.search_task_id if self._search_task is not None else (self.search_id or "")
|
|
6093
6200
|
return show_button_open_report(
|
|
6094
|
-
|
|
6201
|
+
report_html,
|
|
6202
|
+
download_name=f"upgini-report-{search_id}.html",
|
|
6203
|
+
display_id=display_id,
|
|
6204
|
+
display_handle=display_handle,
|
|
6095
6205
|
)
|
|
6096
6206
|
|
|
6097
6207
|
eval_sets_drift_df = self._get_eval_sets_drift_summary()
|
|
@@ -6103,7 +6213,7 @@ if response.status_code == 200:
|
|
|
6103
6213
|
autofe_descriptions_df=self.get_autofe_features_description(),
|
|
6104
6214
|
search_id=self._search_task.search_task_id,
|
|
6105
6215
|
email=self.rest_client.get_current_email(),
|
|
6106
|
-
search_keys=
|
|
6216
|
+
search_keys=self._search_key_names(),
|
|
6107
6217
|
eval_sets_drift_df=eval_sets_drift_df,
|
|
6108
6218
|
display_id=display_id,
|
|
6109
6219
|
display_handle=display_handle,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from upgini.report.assemble import UPGINI_REPORT_BRANDING_URL, assemble_report_data, format_search_duration
|
|
2
|
+
from upgini.report.data import ReportData
|
|
3
|
+
from upgini.report.html import generate_html_report
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ReportData",
|
|
7
|
+
"UPGINI_REPORT_BRANDING_URL",
|
|
8
|
+
"assemble_report_data",
|
|
9
|
+
"format_search_duration",
|
|
10
|
+
"generate_html_report",
|
|
11
|
+
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from datetime import datetime, timezone
|
|
4
5
|
from typing import Optional
|
|
5
6
|
|
|
@@ -7,8 +8,11 @@ import pandas as pd
|
|
|
7
8
|
|
|
8
9
|
from upgini.__about__ import __version__
|
|
9
10
|
from upgini.report.data import QualitySample, ReportData, ReportMetadata, SampleStats, SearchResultsSummary
|
|
11
|
+
from upgini.report.stats import compute_report_charts
|
|
10
12
|
from upgini.resource_bundle import ResourceBundle
|
|
11
13
|
|
|
14
|
+
UPGINI_REPORT_BRANDING_URL = "UPGINI_REPORT_BRANDING_URL"
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
def format_search_duration(seconds: Optional[float]) -> Optional[str]:
|
|
14
18
|
if seconds is None:
|
|
@@ -36,11 +40,19 @@ def assemble_report_data(
|
|
|
36
40
|
metric_name: Optional[str] = None,
|
|
37
41
|
model_features: Optional[int] = None,
|
|
38
42
|
is_binary: bool = False,
|
|
39
|
-
|
|
43
|
+
dataset_samples: Optional[dict[str, pd.DataFrame]] = None,
|
|
44
|
+
scored_samples: Optional[dict[str, pd.DataFrame]] = None,
|
|
40
45
|
bundle: ResourceBundle,
|
|
41
46
|
) -> ReportData:
|
|
42
47
|
generated_at = generated_at or datetime.now(timezone.utc)
|
|
43
48
|
sample_names = list(samples or [])
|
|
49
|
+
chart_stats, charts = compute_report_charts(
|
|
50
|
+
sample_names=sample_names,
|
|
51
|
+
dataset_samples=dataset_samples,
|
|
52
|
+
scored_samples=scored_samples,
|
|
53
|
+
metric_name=metric_name,
|
|
54
|
+
is_binary=is_binary,
|
|
55
|
+
)
|
|
44
56
|
return ReportData(
|
|
45
57
|
metadata=ReportMetadata(
|
|
46
58
|
search_id=search_id,
|
|
@@ -50,15 +62,23 @@ def assemble_report_data(
|
|
|
50
62
|
search_duration=format_search_duration(search_duration_seconds),
|
|
51
63
|
reference_rows=reference_rows,
|
|
52
64
|
samples=sample_names,
|
|
53
|
-
logo_url=
|
|
65
|
+
logo_url=_branding_url(),
|
|
54
66
|
),
|
|
55
67
|
quality_by_sample=_quality_samples(metrics_df, metric_name, bundle),
|
|
56
68
|
summary=SearchResultsSummary(model_features=model_features),
|
|
57
|
-
sample_stats=_sample_stats(sample_names, metrics_df, bundle),
|
|
69
|
+
sample_stats=chart_stats or _sample_stats(sample_names, metrics_df, bundle),
|
|
58
70
|
is_binary=is_binary,
|
|
71
|
+
charts=charts,
|
|
59
72
|
)
|
|
60
73
|
|
|
61
74
|
|
|
75
|
+
def _branding_url() -> Optional[str]:
|
|
76
|
+
env_url = os.getenv(UPGINI_REPORT_BRANDING_URL)
|
|
77
|
+
if env_url and env_url.strip():
|
|
78
|
+
return env_url.strip()
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
62
82
|
def _quality_samples(
|
|
63
83
|
metrics_df: Optional[pd.DataFrame], metric_name: Optional[str], bundle: ResourceBundle
|
|
64
84
|
) -> list[QualitySample]:
|
|
@@ -79,8 +99,9 @@ def _quality_samples(
|
|
|
79
99
|
evaluation_scope=(_as_str(row[segment_col]) or "") if segment_col in metrics_df.columns else "",
|
|
80
100
|
metric=metric_name or "",
|
|
81
101
|
baseline=_as_float(row[baseline_col]) if baseline_col in metrics_df.columns else None,
|
|
102
|
+
baseline_std=_as_float(row[baseline_std_col]) if baseline_std_col in metrics_df.columns else None,
|
|
82
103
|
enriched=_as_float(row[enriched_col]) if enriched_col in metrics_df.columns else None,
|
|
83
|
-
|
|
104
|
+
enriched_std=_as_float(row[enriched_std_col]) if enriched_std_col in metrics_df.columns else None,
|
|
84
105
|
uplift=_as_float(row[uplift_col]) if uplift_col in metrics_df.columns else None,
|
|
85
106
|
relative_uplift=_as_str(row[uplift_pct_col]) if uplift_pct_col in metrics_df.columns else None,
|
|
86
107
|
)
|
|
@@ -112,16 +133,6 @@ def _sample_stats(
|
|
|
112
133
|
return stats
|
|
113
134
|
|
|
114
135
|
|
|
115
|
-
def _metric_std(row, columns, enriched_std_col: Optional[str], baseline_std_col: Optional[str]) -> Optional[float]:
|
|
116
|
-
if enriched_std_col in columns:
|
|
117
|
-
std = _as_float(row[enriched_std_col])
|
|
118
|
-
if std is not None:
|
|
119
|
-
return std
|
|
120
|
-
if baseline_std_col in columns:
|
|
121
|
-
return _as_float(row[baseline_std_col])
|
|
122
|
-
return None
|
|
123
|
-
|
|
124
|
-
|
|
125
136
|
def _as_str(value) -> Optional[str]:
|
|
126
137
|
return None if pd.isna(value) else str(value)
|
|
127
138
|
|
|
@@ -21,11 +21,16 @@ class QualitySample:
|
|
|
21
21
|
evaluation_scope: str
|
|
22
22
|
metric: str
|
|
23
23
|
baseline: Optional[float] = None
|
|
24
|
+
baseline_std: Optional[float] = None
|
|
24
25
|
enriched: Optional[float] = None
|
|
25
|
-
|
|
26
|
+
enriched_std: Optional[float] = None
|
|
26
27
|
uplift: Optional[float] = None
|
|
27
28
|
relative_uplift: Optional[str] = None
|
|
28
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
|
+
|
|
29
34
|
|
|
30
35
|
@dataclass
|
|
31
36
|
class SampleStats:
|
|
@@ -74,6 +79,70 @@ class ModelFeatureShap:
|
|
|
74
79
|
mean_abs_shap: Optional[float] = None
|
|
75
80
|
|
|
76
81
|
|
|
82
|
+
@dataclass
|
|
83
|
+
class MonthlySamplePoint:
|
|
84
|
+
month: str
|
|
85
|
+
sample: str
|
|
86
|
+
total: int
|
|
87
|
+
labeled: int
|
|
88
|
+
unlabeled: int
|
|
89
|
+
positive: int
|
|
90
|
+
mean: Optional[float] = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class QualityMonthly:
|
|
95
|
+
evaluation_scope: str
|
|
96
|
+
metric: str
|
|
97
|
+
baseline: list[Optional[float]] = field(default_factory=list)
|
|
98
|
+
enriched: list[Optional[float]] = field(default_factory=list)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class ScoreMonthlyStats:
|
|
103
|
+
sample: str
|
|
104
|
+
mean_score: list[Optional[float]] = field(default_factory=list)
|
|
105
|
+
mean_target: list[Optional[float]] = field(default_factory=list)
|
|
106
|
+
labeled: list[Optional[int]] = field(default_factory=list)
|
|
107
|
+
unlabeled: list[Optional[int]] = field(default_factory=list)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class ScoreHistogram:
|
|
112
|
+
sample: str
|
|
113
|
+
target_0: list[float] = field(default_factory=list)
|
|
114
|
+
target_1: list[float] = field(default_factory=list)
|
|
115
|
+
n_target_0: int = 0
|
|
116
|
+
n_target_1: int = 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class ScorePsi:
|
|
121
|
+
sample: str
|
|
122
|
+
psi: list[Optional[float]] = field(default_factory=list)
|
|
123
|
+
rows: list[Optional[int]] = field(default_factory=list)
|
|
124
|
+
coverage: list[Optional[float]] = field(default_factory=list)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class ReportCharts:
|
|
129
|
+
timeline_months: list[str] = field(default_factory=list)
|
|
130
|
+
period_options: list[int] = field(default_factory=list)
|
|
131
|
+
default_period: int = 0
|
|
132
|
+
sample_monthly: list[MonthlySamplePoint] = field(default_factory=list)
|
|
133
|
+
monthly_count_max: int = 0
|
|
134
|
+
quality_monthly: list[QualityMonthly] = field(default_factory=list)
|
|
135
|
+
score_monthly: list[ScoreMonthlyStats] = field(default_factory=list)
|
|
136
|
+
score_count_axis_max: int = 0
|
|
137
|
+
histogram_bin_edges: list[float] = field(default_factory=list)
|
|
138
|
+
histograms: list[ScoreHistogram] = field(default_factory=list)
|
|
139
|
+
histogram_density_max: float = 0.0
|
|
140
|
+
score_psi: list[ScorePsi] = field(default_factory=list)
|
|
141
|
+
psi_axis_max: float = 0.32
|
|
142
|
+
psi_warning: float = 0.1
|
|
143
|
+
psi_critical: float = 0.25
|
|
144
|
+
|
|
145
|
+
|
|
77
146
|
@dataclass
|
|
78
147
|
class ReportData:
|
|
79
148
|
metadata: ReportMetadata
|
|
@@ -85,3 +154,4 @@ class ReportData:
|
|
|
85
154
|
sources: list[SourceRow] = field(default_factory=list)
|
|
86
155
|
autofe: list[dict[str, str]] = field(default_factory=list)
|
|
87
156
|
model_feature_shap: list[ModelFeatureShap] = field(default_factory=list)
|
|
157
|
+
charts: ReportCharts = field(default_factory=ReportCharts)
|