SuperModelingFactory 0.6.5__tar.gz → 0.6.7__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.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Model_Eval_Tool.py +55 -8
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +1 -1
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/weighted_eval_utils.py +102 -13
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Screen.py +169 -2
- supermodelingfactory-0.6.7/Modeling_Tool/Feature/Screen_Gates.py +470 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Weighted_Screen.py +94 -4
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/LRM_Tool.py +31 -1
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/LRM_Tool.pyi +1 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/_common.py +35 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/credit_model.py +191 -8
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/feature_validation.py +256 -6
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/field_meta.py +47 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/sample_analysis.py +188 -19
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/screening_artifact.py +46 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +489 -55
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +9 -4
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.6.5/SuperModelingFactory.egg-info → supermodelingfactory-0.6.7}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/README.md +1 -1
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7/SuperModelingFactory.egg-info}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/SOURCES.txt +1 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/pyproject.toml +1 -1
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/setup.py +1 -1
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/LICENSE +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/MANIFEST.in +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/ODPS_Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/ODPS_Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Report/__init__.py +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/requirements.txt +0 -0
- {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/setup.cfg +0 -0
{supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/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.5 → supermodelingfactory-0.6.7}/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.5 → supermodelingfactory-0.6.7}/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
|
|
{supermodelingfactory-0.6.5 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Screen.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import warnings
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Literal, Mapping
|
|
@@ -14,9 +15,11 @@ from Modeling_Tool._utils.sentinels import SMF_MISSING_BIN
|
|
|
14
15
|
|
|
15
16
|
from .Weighted_Screen import (
|
|
16
17
|
WeightedScreenResult,
|
|
18
|
+
_DROPPED_DETAIL_COLS,
|
|
17
19
|
_apply_missing_rate_stage,
|
|
18
20
|
_apply_stage_keep,
|
|
19
21
|
_corr_dedup_weighted,
|
|
22
|
+
_iv_band_keep,
|
|
20
23
|
_legacy_unweighted_screen,
|
|
21
24
|
_psi_from_distributions,
|
|
22
25
|
_resolve_splits,
|
|
@@ -37,6 +40,14 @@ _MONOTONE_INIT_KEYS = frozenset({
|
|
|
37
40
|
"missing_woe",
|
|
38
41
|
"special_values",
|
|
39
42
|
"bin_label_decimals",
|
|
43
|
+
"min_bad_count",
|
|
44
|
+
"min_good_count",
|
|
45
|
+
"small_bin_policy",
|
|
46
|
+
"monotone_direction",
|
|
47
|
+
"reference_target",
|
|
48
|
+
"direction_conflict_policy",
|
|
49
|
+
"missing_bin_strategy",
|
|
50
|
+
"refine_min_n_bins_policy",
|
|
40
51
|
})
|
|
41
52
|
_MONOTONE_FIT_KEYS = frozenset({"chi2_binning", "chi2_p", "chi2_init_size", "n_jobs"})
|
|
42
53
|
|
|
@@ -50,6 +61,7 @@ class FeatureScreenConfig:
|
|
|
50
61
|
psi_use_woe_bins: bool = False
|
|
51
62
|
iv_enabled: bool = True
|
|
52
63
|
iv_threshold: float = 0.02
|
|
64
|
+
iv_upper_threshold: float | None = None
|
|
53
65
|
iv_bins: int = 10
|
|
54
66
|
iv_min_bin_prop: float = 0.05
|
|
55
67
|
iv_equal_freq: bool = True
|
|
@@ -77,6 +89,28 @@ class FeatureScreenConfig:
|
|
|
77
89
|
content: float = 1e-6
|
|
78
90
|
precision: int = 5
|
|
79
91
|
min_bin_prop: float = 0.05
|
|
92
|
+
# --- Post-corr selection gates (G03/G04/G05/G06); all legacy-off ------
|
|
93
|
+
# G03 group stability (requires SelectionEvidence — FVP only)
|
|
94
|
+
monthly_iv_min: float | None = None
|
|
95
|
+
monthly_iv_cv_max: float | None = None
|
|
96
|
+
direction_consistency_min: float | None = None
|
|
97
|
+
min_group_n: int | None = None
|
|
98
|
+
insufficient_group_policy: Literal["keep_warn", "drop", "raise"] = "keep_warn"
|
|
99
|
+
# G04 multi-target joint gate (requires SelectionEvidence — FVP only)
|
|
100
|
+
target_rules: Literal["all", "any", "min_pass_count"] | None = None
|
|
101
|
+
min_pass_count: int | None = None
|
|
102
|
+
per_target_iv_range: Any = None # (low, high) or {target: (low, high)}
|
|
103
|
+
direction_reference_target: str | None = None
|
|
104
|
+
# G05 hard top-N truncation
|
|
105
|
+
max_selected_features: int | None = None
|
|
106
|
+
min_selected_features: int | None = None
|
|
107
|
+
ranking_metric: str = "iv"
|
|
108
|
+
tie_breaker: str = "name"
|
|
109
|
+
# G06 VIF gate (needs the optional statsmodels extra)
|
|
110
|
+
vif_enabled: bool = False
|
|
111
|
+
vif_threshold: float = 10.0
|
|
112
|
+
vif_min_features: int = 2
|
|
113
|
+
vif_tie_break_metric: str = "iv"
|
|
80
114
|
|
|
81
115
|
|
|
82
116
|
def screen_config_from_mapping(
|
|
@@ -100,6 +134,9 @@ def screen_config_from_mapping(
|
|
|
100
134
|
psi_use_woe_bins=bool(cfg.get("psi_use_woe_bins", False)),
|
|
101
135
|
iv_enabled=bool(cfg.get("iv_enabled", True)),
|
|
102
136
|
iv_threshold=float(cfg.get("iv_threshold", 0.02)),
|
|
137
|
+
iv_upper_threshold=(
|
|
138
|
+
float(cfg["iv_upper_threshold"]) if cfg.get("iv_upper_threshold") is not None else None
|
|
139
|
+
),
|
|
103
140
|
iv_bins=iv_nbins,
|
|
104
141
|
iv_min_bin_prop=float(cfg.get("iv_min_bin_prop", 0.05)),
|
|
105
142
|
iv_equal_freq=bool(cfg.get("iv_equal_freq", True)),
|
|
@@ -125,6 +162,23 @@ def screen_config_from_mapping(
|
|
|
125
162
|
content=float(cfg.get("content", 1e-6)),
|
|
126
163
|
precision=int(cfg.get("precision", 5)),
|
|
127
164
|
min_bin_prop=float(cfg.get("min_bin_prop", cfg.get("iv_min_bin_prop", 0.05))),
|
|
165
|
+
monthly_iv_min=cfg.get("monthly_iv_min"),
|
|
166
|
+
monthly_iv_cv_max=cfg.get("monthly_iv_cv_max"),
|
|
167
|
+
direction_consistency_min=cfg.get("direction_consistency_min"),
|
|
168
|
+
min_group_n=cfg.get("min_group_n"),
|
|
169
|
+
insufficient_group_policy=str(cfg.get("insufficient_group_policy", "keep_warn")), # type: ignore[arg-type]
|
|
170
|
+
target_rules=cfg.get("target_rules"),
|
|
171
|
+
min_pass_count=cfg.get("min_pass_count"),
|
|
172
|
+
per_target_iv_range=cfg.get("per_target_iv_range"),
|
|
173
|
+
direction_reference_target=cfg.get("direction_reference_target"),
|
|
174
|
+
max_selected_features=cfg.get("max_selected_features"),
|
|
175
|
+
min_selected_features=cfg.get("min_selected_features"),
|
|
176
|
+
ranking_metric=str(cfg.get("ranking_metric", "iv")),
|
|
177
|
+
tie_breaker=str(cfg.get("tie_breaker", "name")),
|
|
178
|
+
vif_enabled=bool(cfg.get("vif_enabled", False)),
|
|
179
|
+
vif_threshold=float(cfg.get("vif_threshold", 10.0)),
|
|
180
|
+
vif_min_features=int(cfg.get("vif_min_features", 2)),
|
|
181
|
+
vif_tie_break_metric=str(cfg.get("vif_tie_break_metric", "iv")),
|
|
128
182
|
)
|
|
129
183
|
|
|
130
184
|
|
|
@@ -213,12 +267,15 @@ def _woe_bins_unweighted_screen(
|
|
|
213
267
|
config: FeatureScreenConfig,
|
|
214
268
|
*,
|
|
215
269
|
prefit_woe_engine: Any | None = None,
|
|
270
|
+
selection_evidence: Any | None = None,
|
|
216
271
|
) -> FeatureScreenResult:
|
|
217
272
|
from Modeling_Tool import CorrelationFilter, PSICalculator, VarExtractionInsights
|
|
218
273
|
|
|
219
274
|
ins, oos, oot = splits["ins"], splits["oos"], splits["oot"]
|
|
220
275
|
current = list(feature_cols)
|
|
221
276
|
summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, None)]
|
|
277
|
+
dropped_rows: list[dict] = []
|
|
278
|
+
stage_tables: dict[str, pd.DataFrame] = {}
|
|
222
279
|
current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
|
|
223
280
|
ins,
|
|
224
281
|
current,
|
|
@@ -228,6 +285,7 @@ def _woe_bins_unweighted_screen(
|
|
|
228
285
|
on_empty_stage=config.on_empty_stage,
|
|
229
286
|
)
|
|
230
287
|
binner = _resolve_screening_binner(splits, current, target_col, config, prefit_woe_engine)
|
|
288
|
+
engine_fit_features = list(current)
|
|
231
289
|
|
|
232
290
|
psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
|
|
233
291
|
if config.psi_enabled:
|
|
@@ -290,7 +348,7 @@ def _woe_bins_unweighted_screen(
|
|
|
290
348
|
)
|
|
291
349
|
if iv is not None and not iv.empty:
|
|
292
350
|
iv_table = iv.rename(columns={"iv": "iv_weighted"})[["var", "iv_weighted", "n_bins", "missing_rate"]]
|
|
293
|
-
keep = iv
|
|
351
|
+
keep = _iv_band_keep(iv, "iv", config.iv_threshold, config.iv_upper_threshold, dropped_rows)
|
|
294
352
|
n_before = len(current)
|
|
295
353
|
current = _apply_stage_keep(
|
|
296
354
|
current, keep, "iv", summary_rows,
|
|
@@ -312,7 +370,25 @@ def _woe_bins_unweighted_screen(
|
|
|
312
370
|
current = cf.remove_highly_correlated(current, max_iterations=config.corr_max_iterations)
|
|
313
371
|
summary_rows.append(_summary_row("corr", n_before, len(current), config.corr_threshold, None))
|
|
314
372
|
|
|
373
|
+
from .Screen_Gates import apply_post_corr_gates
|
|
374
|
+
|
|
375
|
+
gate_iv_map = (
|
|
376
|
+
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
377
|
+
)
|
|
378
|
+
current = apply_post_corr_gates(
|
|
379
|
+
ins, current, config, selection_evidence, gate_iv_map,
|
|
380
|
+
summary_rows, dropped_rows, stage_tables,
|
|
381
|
+
weight_col=None, on_empty_stage=config.on_empty_stage,
|
|
382
|
+
)
|
|
383
|
+
|
|
315
384
|
summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, None))
|
|
385
|
+
engine_obj, engine_meta = _screen_engine_payload(
|
|
386
|
+
binner,
|
|
387
|
+
fit_features=engine_fit_features,
|
|
388
|
+
target_col=target_col,
|
|
389
|
+
weight_col=None,
|
|
390
|
+
summary_rows=summary_rows,
|
|
391
|
+
)
|
|
316
392
|
return FeatureScreenResult(
|
|
317
393
|
selected_features=list(current),
|
|
318
394
|
iv_table=iv_table,
|
|
@@ -321,6 +397,13 @@ def _woe_bins_unweighted_screen(
|
|
|
321
397
|
summary=pd.DataFrame(summary_rows),
|
|
322
398
|
missing_rate_table=missing_rate_table,
|
|
323
399
|
missing_rate_dropped=missing_rate_dropped,
|
|
400
|
+
woe_engine=engine_obj,
|
|
401
|
+
woe_engine_meta=engine_meta,
|
|
402
|
+
dropped_detail=(
|
|
403
|
+
pd.DataFrame(dropped_rows, columns=_DROPPED_DETAIL_COLS)
|
|
404
|
+
if dropped_rows else pd.DataFrame(columns=_DROPPED_DETAIL_COLS)
|
|
405
|
+
),
|
|
406
|
+
stage_tables=stage_tables,
|
|
324
407
|
)
|
|
325
408
|
|
|
326
409
|
|
|
@@ -330,6 +413,52 @@ def _bins_to_numpy(bins: Any) -> np.ndarray:
|
|
|
330
413
|
return np.asarray(bins, dtype=object)
|
|
331
414
|
|
|
332
415
|
|
|
416
|
+
def _screen_engine_payload(
|
|
417
|
+
binner: Any | None,
|
|
418
|
+
*,
|
|
419
|
+
fit_features: list[str],
|
|
420
|
+
target_col: str,
|
|
421
|
+
weight_col: str | None,
|
|
422
|
+
summary_rows: list[dict],
|
|
423
|
+
) -> tuple[Any | None, dict[str, Any]]:
|
|
424
|
+
"""Build the (woe_engine, woe_engine_meta) result payload (G00).
|
|
425
|
+
|
|
426
|
+
Monotone engines attach as-is — they hold only per-feature bin tables.
|
|
427
|
+
WOE_Master engines are never attached (they retain the full training
|
|
428
|
+
frame at ``self.train_data``); their woe_table + metadata travel instead.
|
|
429
|
+
"""
|
|
430
|
+
if binner is None:
|
|
431
|
+
return None, {}
|
|
432
|
+
engine_name = type(binner).__name__
|
|
433
|
+
meta: dict[str, Any] = {
|
|
434
|
+
"engine_name": engine_name,
|
|
435
|
+
"fit_features": list(fit_features),
|
|
436
|
+
"target_col": target_col,
|
|
437
|
+
"weight_col": weight_col,
|
|
438
|
+
"screen_stages": [row.get("stage") for row in summary_rows],
|
|
439
|
+
}
|
|
440
|
+
if engine_name == "WOE_Master":
|
|
441
|
+
from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
meta["woe_table"] = as_woe_engine(binner).get_woe_table(varlist=list(fit_features))
|
|
445
|
+
except Exception as exc:
|
|
446
|
+
meta["woe_table"] = None
|
|
447
|
+
meta["woe_table_error"] = repr(exc)
|
|
448
|
+
meta["engine_attached"] = False
|
|
449
|
+
warnings.warn(
|
|
450
|
+
"screening WOE engine is a WOE_Master, which retains the full "
|
|
451
|
+
"training frame — attaching its woe_table and metadata only. "
|
|
452
|
+
"Use woe_engine='monotone' to get a reusable, serializable "
|
|
453
|
+
"screening engine on the result.",
|
|
454
|
+
UserWarning,
|
|
455
|
+
stacklevel=3,
|
|
456
|
+
)
|
|
457
|
+
return None, meta
|
|
458
|
+
meta["engine_attached"] = True
|
|
459
|
+
return binner, meta
|
|
460
|
+
|
|
461
|
+
|
|
333
462
|
def _weighted_woe_bins_screen(
|
|
334
463
|
splits: dict[str, pd.DataFrame],
|
|
335
464
|
feature_cols: list[str],
|
|
@@ -338,6 +467,7 @@ def _weighted_woe_bins_screen(
|
|
|
338
467
|
config: FeatureScreenConfig,
|
|
339
468
|
*,
|
|
340
469
|
prefit_woe_engine: Any | None = None,
|
|
470
|
+
selection_evidence: Any | None = None,
|
|
341
471
|
) -> FeatureScreenResult:
|
|
342
472
|
from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
|
|
343
473
|
|
|
@@ -346,6 +476,8 @@ def _weighted_woe_bins_screen(
|
|
|
346
476
|
y_ins = ins[target_col].astype(float).to_numpy()
|
|
347
477
|
current = list(feature_cols)
|
|
348
478
|
summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, weight_col)]
|
|
479
|
+
dropped_rows: list[dict] = []
|
|
480
|
+
stage_tables: dict[str, pd.DataFrame] = {}
|
|
349
481
|
current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
|
|
350
482
|
ins,
|
|
351
483
|
current,
|
|
@@ -369,6 +501,7 @@ def _weighted_woe_bins_screen(
|
|
|
369
501
|
categorical_features=config.categorical_features,
|
|
370
502
|
)
|
|
371
503
|
adapter = as_woe_engine(binner)
|
|
504
|
+
engine_fit_features = list(current)
|
|
372
505
|
|
|
373
506
|
# Assign fitted bins once per split. PSI and IV both consume these labels;
|
|
374
507
|
# doing it here avoids a full WOE transform for every feature and stage.
|
|
@@ -447,7 +580,7 @@ def _weighted_woe_bins_screen(
|
|
|
447
580
|
columns=["var", "iv_weighted", "n_bins", "missing_rate"],
|
|
448
581
|
)
|
|
449
582
|
if not iv_table.empty:
|
|
450
|
-
keep = iv_table
|
|
583
|
+
keep = _iv_band_keep(iv_table, "iv_weighted", config.iv_threshold, config.iv_upper_threshold, dropped_rows)
|
|
451
584
|
n_before = len(current)
|
|
452
585
|
current = _apply_stage_keep(
|
|
453
586
|
current, keep, "iv", summary_rows,
|
|
@@ -479,7 +612,25 @@ def _weighted_woe_bins_screen(
|
|
|
479
612
|
)
|
|
480
613
|
summary_rows.append(_summary_row("corr", n_before, len(current), config.corr_threshold, weight_col))
|
|
481
614
|
|
|
615
|
+
from .Screen_Gates import apply_post_corr_gates
|
|
616
|
+
|
|
617
|
+
gate_iv_map = (
|
|
618
|
+
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
619
|
+
)
|
|
620
|
+
current = apply_post_corr_gates(
|
|
621
|
+
ins, current, config, selection_evidence, gate_iv_map,
|
|
622
|
+
summary_rows, dropped_rows, stage_tables,
|
|
623
|
+
weight_col=weight_col, on_empty_stage=config.on_empty_stage,
|
|
624
|
+
)
|
|
625
|
+
|
|
482
626
|
summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, weight_col))
|
|
627
|
+
engine_obj, engine_meta = _screen_engine_payload(
|
|
628
|
+
binner,
|
|
629
|
+
fit_features=engine_fit_features,
|
|
630
|
+
target_col=target_col,
|
|
631
|
+
weight_col=weight_col,
|
|
632
|
+
summary_rows=summary_rows,
|
|
633
|
+
)
|
|
483
634
|
return FeatureScreenResult(
|
|
484
635
|
selected_features=list(current),
|
|
485
636
|
iv_table=iv_table,
|
|
@@ -488,6 +639,13 @@ def _weighted_woe_bins_screen(
|
|
|
488
639
|
summary=pd.DataFrame(summary_rows),
|
|
489
640
|
missing_rate_table=missing_rate_table,
|
|
490
641
|
missing_rate_dropped=missing_rate_dropped,
|
|
642
|
+
woe_engine=engine_obj,
|
|
643
|
+
woe_engine_meta=engine_meta,
|
|
644
|
+
dropped_detail=(
|
|
645
|
+
pd.DataFrame(dropped_rows, columns=_DROPPED_DETAIL_COLS)
|
|
646
|
+
if dropped_rows else pd.DataFrame(columns=_DROPPED_DETAIL_COLS)
|
|
647
|
+
),
|
|
648
|
+
stage_tables=stage_tables,
|
|
491
649
|
)
|
|
492
650
|
|
|
493
651
|
|
|
@@ -499,6 +657,7 @@ def feature_screen(
|
|
|
499
657
|
weight_col: str | None = None,
|
|
500
658
|
config: FeatureScreenConfig | None = None,
|
|
501
659
|
prefit_woe_engine: Any | None = None,
|
|
660
|
+
selection_evidence: Any | None = None,
|
|
502
661
|
) -> FeatureScreenResult:
|
|
503
662
|
"""Run PSI -> IV -> correlation screening on pre-split INS/OOS/OOT frames."""
|
|
504
663
|
cfg = config or FeatureScreenConfig()
|
|
@@ -513,6 +672,7 @@ def feature_screen(
|
|
|
513
672
|
weight_col,
|
|
514
673
|
cfg,
|
|
515
674
|
prefit_woe_engine=prefit_woe_engine,
|
|
675
|
+
selection_evidence=selection_evidence,
|
|
516
676
|
)
|
|
517
677
|
return _weighted_screen_impl(
|
|
518
678
|
splits,
|
|
@@ -524,6 +684,7 @@ def feature_screen(
|
|
|
524
684
|
psi_compare_splits=list(cfg.psi_compare_splits),
|
|
525
685
|
iv_enabled=cfg.iv_enabled,
|
|
526
686
|
iv_threshold=cfg.iv_threshold,
|
|
687
|
+
iv_upper_threshold=cfg.iv_upper_threshold,
|
|
527
688
|
iv_bins=cfg.iv_bins,
|
|
528
689
|
min_bin_prop=cfg.min_bin_prop,
|
|
529
690
|
corr_enabled=cfg.corr_enabled,
|
|
@@ -538,6 +699,8 @@ def feature_screen(
|
|
|
538
699
|
prefit_woe_engine=prefit_woe_engine,
|
|
539
700
|
missing_rate_threshold=cfg.missing_rate_threshold,
|
|
540
701
|
missing_rate_ref=cfg.missing_rate_ref,
|
|
702
|
+
gates_config=cfg,
|
|
703
|
+
selection_evidence=selection_evidence,
|
|
541
704
|
)
|
|
542
705
|
|
|
543
706
|
if use_woe_bins or prefit_woe_engine is not None:
|
|
@@ -547,6 +710,7 @@ def feature_screen(
|
|
|
547
710
|
target_col,
|
|
548
711
|
cfg,
|
|
549
712
|
prefit_woe_engine=prefit_woe_engine,
|
|
713
|
+
selection_evidence=selection_evidence,
|
|
550
714
|
)
|
|
551
715
|
|
|
552
716
|
return _legacy_unweighted_screen(
|
|
@@ -558,6 +722,7 @@ def feature_screen(
|
|
|
558
722
|
psi_compare_splits=list(cfg.psi_compare_splits),
|
|
559
723
|
iv_enabled=cfg.iv_enabled,
|
|
560
724
|
iv_threshold=cfg.iv_threshold,
|
|
725
|
+
iv_upper_threshold=cfg.iv_upper_threshold,
|
|
561
726
|
iv_bins=cfg.iv_bins,
|
|
562
727
|
iv_min_bin_prop=cfg.iv_min_bin_prop,
|
|
563
728
|
corr_enabled=cfg.corr_enabled,
|
|
@@ -570,6 +735,8 @@ def feature_screen(
|
|
|
570
735
|
on_empty_stage=cfg.on_empty_stage,
|
|
571
736
|
missing_rate_threshold=cfg.missing_rate_threshold,
|
|
572
737
|
missing_rate_ref=cfg.missing_rate_ref,
|
|
738
|
+
gates_config=cfg,
|
|
739
|
+
selection_evidence=selection_evidence,
|
|
573
740
|
)
|
|
574
741
|
|
|
575
742
|
|