SuperModelingFactory 0.6.4__tar.gz → 0.6.6__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.
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Model_Eval_Tool.py +55 -8
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/weighted_eval_utils.py +102 -13
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/_common.py +63 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/credit_model.py +386 -29
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/feature_validation.py +26 -2
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/field_meta.py +16 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.6.4/SuperModelingFactory.egg-info → supermodelingfactory-0.6.6}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/README.md +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6/SuperModelingFactory.egg-info}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/pyproject.toml +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/setup.py +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/LICENSE +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/MANIFEST.in +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/ODPS_Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/ODPS_Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Weighted_Screen.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Report/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/requirements.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/setup.cfg +0 -0
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Model_Eval_Tool.py
RENAMED
|
@@ -2169,7 +2169,7 @@ class PerformanceEvaluator:
|
|
|
2169
2169
|
dist_bins = 20, pct_bins = 10, precision = 5, min_bin_prop = 0.05,
|
|
2170
2170
|
include_missing = False, equal_freq = True, chi2_method = False,
|
|
2171
2171
|
init_equi_bins = 1000, chi2_p = 0.9, tree_binning = False, random_state = 42,
|
|
2172
|
-
weight_col = None):
|
|
2172
|
+
weight_col = None, spec_values = None, ascending = None):
|
|
2173
2173
|
"""
|
|
2174
2174
|
初始化性能评估器。
|
|
2175
2175
|
|
|
@@ -2225,9 +2225,29 @@ class PerformanceEvaluator:
|
|
|
2225
2225
|
self.tree_binning = tree_binning
|
|
2226
2226
|
self.random_state = random_state
|
|
2227
2227
|
self.weight_col = weight_col
|
|
2228
|
+
# spec_values: sentinel scores (e.g. -1 for all-missing overrides)
|
|
2229
|
+
# that get their own evaluation bin and are excluded from quantile
|
|
2230
|
+
# edges and ranking metrics. ascending: None keeps every underlying
|
|
2231
|
+
# function's own legacy default; an explicit bool is threaded through
|
|
2232
|
+
# summary/gains/plot and the weighted path uniformly.
|
|
2233
|
+
self.spec_values = list(spec_values) if spec_values else []
|
|
2234
|
+
self.ascending = ascending
|
|
2228
2235
|
self.datasets = {}
|
|
2229
2236
|
self.dataset_weight_cols = {}
|
|
2230
2237
|
self.evaluate_status = None
|
|
2238
|
+
|
|
2239
|
+
def _governed_eval_kwargs(self):
|
|
2240
|
+
"""Kwargs to thread spec_values/ascending into underlying eval calls.
|
|
2241
|
+
|
|
2242
|
+
Nothing is passed when unset, so every legacy default stays intact
|
|
2243
|
+
(unweighted summary False, comparison paths True, weighted False).
|
|
2244
|
+
"""
|
|
2245
|
+
kwargs = {}
|
|
2246
|
+
if self.spec_values:
|
|
2247
|
+
kwargs["spec_values"] = list(self.spec_values)
|
|
2248
|
+
if self.ascending is not None:
|
|
2249
|
+
kwargs["ascending"] = bool(self.ascending)
|
|
2250
|
+
return kwargs
|
|
2231
2251
|
|
|
2232
2252
|
def add_dataset(self, name, data, weight_col = None, overwrite = False):
|
|
2233
2253
|
"""
|
|
@@ -2351,6 +2371,8 @@ class PerformanceEvaluator:
|
|
|
2351
2371
|
scr_name,
|
|
2352
2372
|
weight_col=wc,
|
|
2353
2373
|
nbins=self.pct_bins,
|
|
2374
|
+
ascending=bool(self.ascending) if self.ascending is not None else False,
|
|
2375
|
+
spec_values=self.spec_values or None,
|
|
2354
2376
|
)
|
|
2355
2377
|
)
|
|
2356
2378
|
fnl_df = pd.DataFrame(rows)
|
|
@@ -2368,15 +2390,23 @@ class PerformanceEvaluator:
|
|
|
2368
2390
|
if scr_name is None:
|
|
2369
2391
|
scr_name = "_mdl_scr"
|
|
2370
2392
|
work_data[scr_name] = self.model.predict_proba(work_data.loc[:, self.feature_cols])[:, 1]
|
|
2371
|
-
payload = {
|
|
2372
|
-
"y_true": work_data[self.tgt_name],
|
|
2373
|
-
"y_score": work_data[scr_name],
|
|
2374
|
-
}
|
|
2375
2393
|
resolved_weight = _weighted_eval.resolve_weights(
|
|
2376
2394
|
work_data,
|
|
2377
2395
|
weight_col=wc,
|
|
2378
2396
|
expected_len=len(work_data),
|
|
2379
2397
|
)
|
|
2398
|
+
y_true = work_data[self.tgt_name].to_numpy()
|
|
2399
|
+
y_score = work_data[scr_name].to_numpy()
|
|
2400
|
+
if self.spec_values:
|
|
2401
|
+
keep = ~pd.Series(y_score).isin(self.spec_values).to_numpy()
|
|
2402
|
+
y_true = y_true[keep]
|
|
2403
|
+
y_score = y_score[keep]
|
|
2404
|
+
if resolved_weight is not None:
|
|
2405
|
+
resolved_weight = np.asarray(resolved_weight)[keep]
|
|
2406
|
+
payload = {
|
|
2407
|
+
"y_true": y_true,
|
|
2408
|
+
"y_score": y_score,
|
|
2409
|
+
}
|
|
2380
2410
|
if resolved_weight is not None:
|
|
2381
2411
|
payload["sample_weight"] = resolved_weight
|
|
2382
2412
|
eval_datasets[name] = payload
|
|
@@ -2445,6 +2475,21 @@ class PerformanceEvaluator:
|
|
|
2445
2475
|
return data[self.scr_name]
|
|
2446
2476
|
return self.model.predict_proba(data.loc[:, self.feature_cols])[:, 1]
|
|
2447
2477
|
|
|
2478
|
+
def _rank_payload(data):
|
|
2479
|
+
"""(y_true, y_score) with special sentinel scores removed.
|
|
2480
|
+
|
|
2481
|
+
Sentinels like the all-missing override -1 carry no ordering
|
|
2482
|
+
information: AUC/KS/distribution figures are computed on the
|
|
2483
|
+
non-special rows; their bin-level accounting lives in the gains
|
|
2484
|
+
table via spec_values.
|
|
2485
|
+
"""
|
|
2486
|
+
y_true = data[self.tgt_name]
|
|
2487
|
+
y_score = _get_score(data)
|
|
2488
|
+
if not self.spec_values:
|
|
2489
|
+
return y_true, y_score
|
|
2490
|
+
keep = ~pd.Series(np.asarray(y_score)).isin(self.spec_values).to_numpy()
|
|
2491
|
+
return np.asarray(y_true)[keep], np.asarray(y_score)[keep]
|
|
2492
|
+
|
|
2448
2493
|
def _get_benchmark_bin_edges():
|
|
2449
2494
|
if benchmark_dataset is None:
|
|
2450
2495
|
return None
|
|
@@ -2492,9 +2537,10 @@ class PerformanceEvaluator:
|
|
|
2492
2537
|
for name, data in dataset_dict.items():
|
|
2493
2538
|
if data is None:
|
|
2494
2539
|
continue
|
|
2540
|
+
y_true, y_score = _rank_payload(data)
|
|
2495
2541
|
eval_datasets[name] = {
|
|
2496
|
-
"y_true":
|
|
2497
|
-
"y_score":
|
|
2542
|
+
"y_true": y_true,
|
|
2543
|
+
"y_score": y_score
|
|
2498
2544
|
}
|
|
2499
2545
|
|
|
2500
2546
|
if len(eval_datasets) == 0:
|
|
@@ -2553,7 +2599,8 @@ class PerformanceEvaluator:
|
|
|
2553
2599
|
chi2_p = self.chi2_p,
|
|
2554
2600
|
retSummary = True,
|
|
2555
2601
|
tree_binning = False if benchmark_bin_edges is not None else self.tree_binning,
|
|
2556
|
-
random_state = self.random_state
|
|
2602
|
+
random_state = self.random_state,
|
|
2603
|
+
**self._governed_eval_kwargs(),
|
|
2557
2604
|
)
|
|
2558
2605
|
gains_res['index'] = name
|
|
2559
2606
|
gains_summ_list.append(gains_res)
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Model_Eval_Tool.pyi
RENAMED
|
@@ -32,6 +32,6 @@ class GainsTableCalculator:
|
|
|
32
32
|
def calculate(self, grp_name = None, min_data_size = 100, grp_colname = None, sync_range = True, retSummary = False, withSummary = False, wholeGroup = False, add_func = None): ...
|
|
33
33
|
|
|
34
34
|
class PerformanceEvaluator:
|
|
35
|
-
def __init__(self, tgt_name, scr_name = None, model = None, feature_cols = None, dist_bins = 20, pct_bins = 10, precision = 5, min_bin_prop = 0.05, include_missing = False, equal_freq = True, chi2_method = False, init_equi_bins = 1000, chi2_p = 0.9, tree_binning = False, random_state = 42, weight_col = None): ...
|
|
35
|
+
def __init__(self, tgt_name, scr_name = None, model = None, feature_cols = None, dist_bins = 20, pct_bins = 10, precision = 5, min_bin_prop = 0.05, include_missing = False, equal_freq = True, chi2_method = False, init_equi_bins = 1000, chi2_p = 0.9, tree_binning = False, random_state = 42, weight_col = None, spec_values = None, ascending = None): ...
|
|
36
36
|
def add_dataset(self, name, data, weight_col = None, overwrite = False): ...
|
|
37
37
|
def evaluate(self, oot_grp_name = None, min_data_size = 100, grp_colname = None, fig_save_path = None, rpt_save_path = None, to_show = False, display = True, gains_table = False, benchmark_dataset = None, weight_col = None): ...
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/weighted_eval_utils.py
RENAMED
|
@@ -95,10 +95,18 @@ def calc_pr(y_true, y_score, sample_weight=None):
|
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def rank_bins(score, weight, nbins):
|
|
98
|
+
def rank_bins(score, weight, nbins, ascending=False):
|
|
99
|
+
"""Weighted equal-frequency rank bins.
|
|
100
|
+
|
|
101
|
+
``ascending=False`` (default, legacy) sorts scores descending — bin 1 is
|
|
102
|
+
the highest-score bucket. ``ascending=True`` sorts scores ascending so
|
|
103
|
+
bin 1 is the lowest-score bucket, matching the unweighted
|
|
104
|
+
``gains_ascending=True`` convention.
|
|
105
|
+
"""
|
|
99
106
|
score = np.asarray(score, dtype=float)
|
|
100
107
|
weight = np.ones(len(score), dtype=float) if weight is None else np.asarray(weight, dtype=float)
|
|
101
|
-
|
|
108
|
+
sort_key = score if ascending else -score
|
|
109
|
+
order = np.lexsort((np.arange(len(score)), sort_key))
|
|
102
110
|
bins = np.empty(len(score), dtype=int)
|
|
103
111
|
total = float(weight.sum()) or float(len(score)) or 1.0
|
|
104
112
|
cum_weight = np.cumsum(weight[order])
|
|
@@ -108,18 +116,35 @@ def rank_bins(score, weight, nbins):
|
|
|
108
116
|
return bins
|
|
109
117
|
|
|
110
118
|
|
|
111
|
-
def
|
|
119
|
+
def _split_special_scores(df, score, spec_values):
|
|
120
|
+
"""Split a frame into (normal, special) rows by exact score membership.
|
|
121
|
+
|
|
122
|
+
Special score values (e.g. the all-missing override ``-1``) are business
|
|
123
|
+
sentinels, not model outputs: they must not participate in quantile
|
|
124
|
+
binning or ranking metrics.
|
|
125
|
+
"""
|
|
126
|
+
if not spec_values:
|
|
127
|
+
return df, None
|
|
128
|
+
spec_mask = df[score].isin(list(spec_values))
|
|
129
|
+
if not bool(spec_mask.any()):
|
|
130
|
+
return df, None
|
|
131
|
+
return df[~spec_mask], df[spec_mask]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_gains_table(data, dep, score, nbins=10, weight_col=None, weighted_binning=None,
|
|
135
|
+
ascending=False, spec_values=None, **kwargs):
|
|
112
136
|
cols = [dep, score]
|
|
113
137
|
if weight_col is not None and weight_col in data.columns:
|
|
114
138
|
cols.append(weight_col)
|
|
115
139
|
df = data[cols].copy()
|
|
140
|
+
df, special_df = _split_special_scores(df, score, spec_values)
|
|
116
141
|
weight = resolve_weights(df, weight_col=weight_col, expected_len=len(df))
|
|
117
142
|
if weight is None:
|
|
118
143
|
weight = np.ones(len(df), dtype=float)
|
|
119
144
|
|
|
120
145
|
y = df[dep].astype(float).to_numpy()
|
|
121
146
|
s = df[score].astype(float).to_numpy()
|
|
122
|
-
df["_bin_num"] = rank_bins(s, weight, nbins)
|
|
147
|
+
df["_bin_num"] = rank_bins(s, weight, nbins, ascending=ascending)
|
|
123
148
|
df["_bin_range"] = df["_bin_num"]
|
|
124
149
|
df["_w"] = weight
|
|
125
150
|
df["_bad_w"] = weight * y
|
|
@@ -165,13 +190,56 @@ def get_gains_table(data, dep, score, nbins=10, weight_col=None, weighted_binnin
|
|
|
165
190
|
out["KS_PER_BIN"] = (out["CUM_BAD_PCT"] - out["CUM_GOOD_PCT"]).abs()
|
|
166
191
|
out["KS"] = out["KS_PER_BIN"]
|
|
167
192
|
out["LIFT"] = out["AVG_BAD"] / overall_bad_rate if overall_bad_rate else np.nan
|
|
168
|
-
out["TRUE_BAD_SHIFT"] =
|
|
193
|
+
out["TRUE_BAD_SHIFT"] = (
|
|
194
|
+
(out["AVG_BAD"].shift(1) / out["AVG_BAD"] - 1)
|
|
195
|
+
if not ascending
|
|
196
|
+
else (out["AVG_BAD"] / out["AVG_BAD"].shift(1) - 1)
|
|
197
|
+
)
|
|
169
198
|
out["RANK_ORDER_BUMP"] = out["TRUE_BAD_SHIFT"].lt(0).astype(int)
|
|
170
199
|
with np.errstate(divide="ignore", invalid="ignore"):
|
|
171
200
|
out["WOE"] = np.log(out["BAD_PCT_IN_EACH_BIN"] / out["GOOD_PCT_IN_EACH_BIN"])
|
|
172
201
|
out["WOE"] = out["WOE"].replace([np.inf, -np.inf], 0).fillna(0)
|
|
173
202
|
out["IV"] = (out["BAD_PCT_IN_EACH_BIN"] - out["GOOD_PCT_IN_EACH_BIN"]) * out["WOE"]
|
|
174
203
|
out["AUC"] = safe_auc(y, s, sample_weight=weight)
|
|
204
|
+
|
|
205
|
+
if special_df is not None and len(special_df):
|
|
206
|
+
# Special sentinel scores (e.g. -1 for all-missing rows) get their own
|
|
207
|
+
# descriptive rows: never part of quantile edges, cumulative columns,
|
|
208
|
+
# or ranking metrics (those stay NaN by construction).
|
|
209
|
+
spec_weight = resolve_weights(special_df, weight_col=weight_col, expected_len=len(special_df))
|
|
210
|
+
if spec_weight is None:
|
|
211
|
+
spec_weight = np.ones(len(special_df), dtype=float)
|
|
212
|
+
grand_total = total_weight + float(np.sum(spec_weight))
|
|
213
|
+
spec_rows = []
|
|
214
|
+
for value, part in special_df.groupby(score, sort=True):
|
|
215
|
+
part_w = resolve_weights(part, weight_col=weight_col, expected_len=len(part))
|
|
216
|
+
if part_w is None:
|
|
217
|
+
part_w = np.ones(len(part), dtype=float)
|
|
218
|
+
part_y = part[dep].astype(float).to_numpy()
|
|
219
|
+
n_w = float(np.sum(part_w))
|
|
220
|
+
n_bad = float(np.sum(part_w * part_y))
|
|
221
|
+
label = f"special:{value}"
|
|
222
|
+
spec_rows.append(
|
|
223
|
+
pd.DataFrame(
|
|
224
|
+
{
|
|
225
|
+
"MIN": [value],
|
|
226
|
+
"MAX": [value],
|
|
227
|
+
"N": [n_w],
|
|
228
|
+
"N_RAW": [int(len(part))],
|
|
229
|
+
"PERF_CNT": [n_w],
|
|
230
|
+
"N_BAD": [n_bad],
|
|
231
|
+
"N_GOOD": [n_w - n_bad],
|
|
232
|
+
"AVG_SCORE": [float(value)],
|
|
233
|
+
"UNIQUE_SCORE": [1],
|
|
234
|
+
"PROP": [n_w / grand_total if grand_total else np.nan],
|
|
235
|
+
"AVG_BAD": [n_bad / n_w if n_w else np.nan],
|
|
236
|
+
"AVG_GOOD": [(n_w - n_bad) / n_w if n_w else np.nan],
|
|
237
|
+
},
|
|
238
|
+
index=pd.MultiIndex.from_tuples([(label, label)], names=out.index.names),
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
out = pd.concat([out] + spec_rows)
|
|
242
|
+
out["AUC"] = out["AUC"].iloc[0]
|
|
175
243
|
return out
|
|
176
244
|
|
|
177
245
|
|
|
@@ -212,32 +280,53 @@ def calc_fixed_pct(y_true, y_score, sample_weight=None, **kwargs):
|
|
|
212
280
|
return calc_equid_dist(y_true, y_score, sample_weight=sample_weight, **kwargs)
|
|
213
281
|
|
|
214
282
|
|
|
215
|
-
def dataset_summary(name, data, tgt_name, scr_name, weight_col=None, nbins=10
|
|
283
|
+
def dataset_summary(name, data, tgt_name, scr_name, weight_col=None, nbins=10,
|
|
284
|
+
ascending=False, spec_values=None):
|
|
216
285
|
weight = resolve_weights(data, weight_col=weight_col, expected_len=len(data))
|
|
217
286
|
y_true = data[tgt_name].to_numpy()
|
|
218
287
|
y_score = data[scr_name].to_numpy()
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
288
|
+
# Ranking metrics (AUC/KS/avgScore) are computed on non-special rows only:
|
|
289
|
+
# sentinel scores like -1 carry no ordering information.
|
|
290
|
+
rank_data, special_part = _split_special_scores(data, scr_name, spec_values)
|
|
291
|
+
rank_weight = resolve_weights(rank_data, weight_col=weight_col, expected_len=len(rank_data))
|
|
292
|
+
rank_true = rank_data[tgt_name].to_numpy()
|
|
293
|
+
rank_score = rank_data[scr_name].to_numpy()
|
|
294
|
+
roc_df = calc_roc(rank_true, rank_score, sample_weight=rank_weight)
|
|
295
|
+
gains = get_gains_table(
|
|
296
|
+
data, tgt_name, scr_name, nbins=nbins, weight_col=weight_col,
|
|
297
|
+
ascending=ascending, spec_values=spec_values,
|
|
298
|
+
)
|
|
299
|
+
summary = {
|
|
222
300
|
"index": name,
|
|
223
301
|
"dataset": name,
|
|
224
302
|
"DATASET": name,
|
|
225
|
-
"AUC": safe_auc(
|
|
303
|
+
"AUC": safe_auc(rank_true, rank_score, sample_weight=rank_weight),
|
|
226
304
|
"KS": float(roc_df["KS"].max()) if "KS" in roc_df else np.nan,
|
|
227
305
|
"LIFT": float(gains["LIFT"].max()) if "LIFT" in gains else np.nan,
|
|
228
306
|
"IV": float(gains["IV"].sum()) if "IV" in gains else np.nan,
|
|
229
307
|
"N": float(np.sum(weight)) if weight is not None else float(len(data)),
|
|
230
308
|
"N_RAW": int(len(data)),
|
|
231
309
|
"avgTrue": safe_weighted_average(y_true, weight),
|
|
232
|
-
"avgScore": safe_weighted_average(
|
|
310
|
+
"avgScore": safe_weighted_average(rank_score, rank_weight),
|
|
233
311
|
}
|
|
312
|
+
if special_part is not None:
|
|
313
|
+
spec_weight = resolve_weights(special_part, weight_col=weight_col, expected_len=len(special_part))
|
|
314
|
+
summary["N_SPECIAL"] = (
|
|
315
|
+
float(np.sum(spec_weight)) if spec_weight is not None else float(len(special_part))
|
|
316
|
+
)
|
|
317
|
+
summary["N_SPECIAL_RAW"] = int(len(special_part))
|
|
318
|
+
return summary
|
|
234
319
|
|
|
235
320
|
|
|
236
|
-
def get_perf_summary(train=None, validation=None, oot=None, tgt_name=None, scr_name=None, weight_col=None, nbins=10,
|
|
321
|
+
def get_perf_summary(train=None, validation=None, oot=None, tgt_name=None, scr_name=None, weight_col=None, nbins=10,
|
|
322
|
+
ascending=False, spec_values=None, **kwargs):
|
|
237
323
|
rows = []
|
|
238
324
|
for name, data in (("ins", train), ("oos", validation), ("oot", oot)):
|
|
239
325
|
if data is not None:
|
|
240
|
-
rows.append(dataset_summary(
|
|
326
|
+
rows.append(dataset_summary(
|
|
327
|
+
name, data, tgt_name, scr_name, weight_col=weight_col, nbins=nbins,
|
|
328
|
+
ascending=ascending, spec_values=spec_values,
|
|
329
|
+
))
|
|
241
330
|
return pd.DataFrame(rows)
|
|
242
331
|
|
|
243
332
|
|
|
@@ -532,6 +532,69 @@ def copy_column_length_checked(
|
|
|
532
532
|
dst[col] = src[col].to_numpy()
|
|
533
533
|
|
|
534
534
|
|
|
535
|
+
def all_missing_mask(data: pd.DataFrame, feature_cols: list[str]) -> np.ndarray:
|
|
536
|
+
"""Rows where every listed feature is missing.
|
|
537
|
+
|
|
538
|
+
Same membership test as ``Core.scoring``'s all-missing branch
|
|
539
|
+
(``pd.isnull(data[varlist]).sum(axis=1) == len(varlist)``), so the
|
|
540
|
+
pipeline's evaluation override and the production scoring API agree on
|
|
541
|
+
which rows count as un-scorable.
|
|
542
|
+
"""
|
|
543
|
+
features = list(feature_cols or [])
|
|
544
|
+
if not features:
|
|
545
|
+
return np.zeros(len(data), dtype=bool)
|
|
546
|
+
absent = [col for col in features if col not in data.columns]
|
|
547
|
+
if absent:
|
|
548
|
+
raise KeyError(
|
|
549
|
+
f"all-missing score override needs raw feature columns "
|
|
550
|
+
f"{absent[:5]} which are absent from the evaluation frame."
|
|
551
|
+
)
|
|
552
|
+
return (pd.isnull(data[features]).sum(axis=1) == len(features)).to_numpy()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def resolve_missing_oot(
|
|
556
|
+
oos: pd.DataFrame,
|
|
557
|
+
synthesize: bool,
|
|
558
|
+
pipeline: str,
|
|
559
|
+
) -> pd.DataFrame | None:
|
|
560
|
+
"""Decide what to do when a run has no real OOT rows.
|
|
561
|
+
|
|
562
|
+
``synthesize=True`` preserves the historical behavior — the OOS frame is
|
|
563
|
+
copied in as a stand-in OOT — but now emits a loud ``UserWarning`` so the
|
|
564
|
+
placeholder can never be mistaken for a real out-of-time sample.
|
|
565
|
+
``synthesize=False`` returns ``None``: the caller must then leave OOT out
|
|
566
|
+
of its splits (missing key or empty frame) so no downstream stage
|
|
567
|
+
evaluates, searches, or reports against a synthetic OOT.
|
|
568
|
+
"""
|
|
569
|
+
if synthesize:
|
|
570
|
+
warnings.warn(
|
|
571
|
+
f"{pipeline}: no real OOT rows found; OOS ({len(oos)} rows) was "
|
|
572
|
+
f"copied in as a stand-in OOT. Metrics labeled 'oot' are OOS "
|
|
573
|
+
f"metrics, NOT out-of-time performance. Set "
|
|
574
|
+
f"synthesize_missing_oot=False (or candidate_mode=True) to drop "
|
|
575
|
+
f"the placeholder instead.",
|
|
576
|
+
UserWarning,
|
|
577
|
+
stacklevel=3,
|
|
578
|
+
)
|
|
579
|
+
return oos.copy()
|
|
580
|
+
return None
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def assert_split_allowed(
|
|
584
|
+
split: str,
|
|
585
|
+
forbidden: list[str] | set[str] | None,
|
|
586
|
+
component: str,
|
|
587
|
+
) -> None:
|
|
588
|
+
"""Hard gate for split governance: raise if ``component`` is about to
|
|
589
|
+
consume a split listed in ``forbidden_splits``."""
|
|
590
|
+
if forbidden and split in set(forbidden):
|
|
591
|
+
raise ValueError(
|
|
592
|
+
f"{component} requested split {split!r}, but it is listed in "
|
|
593
|
+
f"forbidden_splits {sorted(set(forbidden))}. Remove the split from "
|
|
594
|
+
f"the component's configuration or lift the restriction."
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
|
|
535
598
|
def split_oot_by_flag(
|
|
536
599
|
data: pd.DataFrame,
|
|
537
600
|
oot_col: str | None,
|