SuperModelingFactory 0.6.7__tar.gz → 0.6.8__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.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Feature_Screen.py +38 -11
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Screen_Gates.py +27 -6
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Weighted_Screen.py +154 -23
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/feature_validation.py +53 -12
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/README.md +1 -1
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/pyproject.toml +1 -1
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/setup.py +1 -1
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/LICENSE +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/MANIFEST.in +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/ODPS_Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/ODPS_Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/_common.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/credit_model.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/field_meta.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Report/__init__.py +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/requirements.txt +0 -0
- {supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/setup.cfg +0 -0
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Feature_Screen.py
RENAMED
|
@@ -19,14 +19,16 @@ from .Weighted_Screen import (
|
|
|
19
19
|
_apply_missing_rate_stage,
|
|
20
20
|
_apply_stage_keep,
|
|
21
21
|
_corr_dedup_weighted,
|
|
22
|
+
_gate_ranking_iv_map,
|
|
22
23
|
_iv_band_keep,
|
|
23
24
|
_legacy_unweighted_screen,
|
|
24
25
|
_psi_from_distributions,
|
|
25
26
|
_resolve_splits,
|
|
26
27
|
_summary_row,
|
|
28
|
+
_unit_weight_floored_iv,
|
|
27
29
|
_weighted_bin_distribution,
|
|
28
30
|
_weighted_corr_for_screen,
|
|
29
|
-
|
|
31
|
+
_weighted_iv_detail,
|
|
30
32
|
_weighted_screen_impl,
|
|
31
33
|
)
|
|
32
34
|
|
|
@@ -322,6 +324,7 @@ def _woe_bins_unweighted_screen(
|
|
|
322
324
|
summary_rows.append(_summary_row("psi", n_before, len(current), config.psi_threshold, None))
|
|
323
325
|
|
|
324
326
|
iv_table = pd.DataFrame(columns=["var", "iv_weighted", "n_bins", "missing_rate"])
|
|
327
|
+
iv_floored_map: dict[str, float] = {}
|
|
325
328
|
if config.iv_enabled:
|
|
326
329
|
use_binner = config.iv_use_woe_bins and binner is not None
|
|
327
330
|
vi = VarExtractionInsights(
|
|
@@ -348,7 +351,20 @@ def _woe_bins_unweighted_screen(
|
|
|
348
351
|
)
|
|
349
352
|
if iv is not None and not iv.empty:
|
|
350
353
|
iv_table = iv.rename(columns={"iv": "iv_weighted"})[["var", "iv_weighted", "n_bins", "missing_rate"]]
|
|
351
|
-
|
|
354
|
+
if config.iv_upper_threshold is not None:
|
|
355
|
+
gate_frame = iv[["var", "iv"]].copy()
|
|
356
|
+
gate_frame["iv_floored"] = _unit_weight_floored_iv(
|
|
357
|
+
ins, list(gate_frame["var"]), target_col,
|
|
358
|
+
iv_bins=config.iv_bins, min_bin_prop=config.iv_min_bin_prop,
|
|
359
|
+
content=config.content,
|
|
360
|
+
)
|
|
361
|
+
iv_floored_map = dict(zip(gate_frame["var"], gate_frame["iv_floored"]))
|
|
362
|
+
keep = _iv_band_keep(
|
|
363
|
+
gate_frame, "iv", config.iv_threshold, config.iv_upper_threshold,
|
|
364
|
+
dropped_rows, upper_col="iv_floored",
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
keep = _iv_band_keep(iv, "iv", config.iv_threshold, config.iv_upper_threshold, dropped_rows)
|
|
352
368
|
n_before = len(current)
|
|
353
369
|
current = _apply_stage_keep(
|
|
354
370
|
current, keep, "iv", summary_rows,
|
|
@@ -372,9 +388,7 @@ def _woe_bins_unweighted_screen(
|
|
|
372
388
|
|
|
373
389
|
from .Screen_Gates import apply_post_corr_gates
|
|
374
390
|
|
|
375
|
-
gate_iv_map = (
|
|
376
|
-
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
377
|
-
)
|
|
391
|
+
gate_iv_map = _gate_ranking_iv_map(iv_table, iv_floored_map, config.iv_upper_threshold)
|
|
378
392
|
current = apply_post_corr_gates(
|
|
379
393
|
ins, current, config, selection_evidence, gate_iv_map,
|
|
380
394
|
summary_rows, dropped_rows, stage_tables,
|
|
@@ -553,7 +567,9 @@ def _weighted_woe_bins_screen(
|
|
|
553
567
|
psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
|
|
554
568
|
|
|
555
569
|
iv_records: list[dict] = []
|
|
570
|
+
iv_floored_map: dict[str, float] = {}
|
|
556
571
|
if config.iv_enabled:
|
|
572
|
+
floor_content = config.content if config.iv_upper_threshold is not None else None
|
|
557
573
|
for var in current:
|
|
558
574
|
if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
|
|
559
575
|
continue
|
|
@@ -564,12 +580,16 @@ def _weighted_woe_bins_screen(
|
|
|
564
580
|
else:
|
|
565
581
|
# Categorical/object feature: IV itself is computed from the
|
|
566
582
|
# already-assigned bins; ``x`` only feeds np.isfinite() inside
|
|
567
|
-
#
|
|
583
|
+
# _weighted_iv_detail to derive the weighted
|
|
568
584
|
# missing rate. A hard float cast crashes on string levels
|
|
569
585
|
# (e.g. '4.高中'), so encode observed -> 1.0 / missing -> NaN,
|
|
570
586
|
# matching the notna semantics of _missing_rate_for_series.
|
|
571
587
|
x_ins = np.where(x_series.notna().to_numpy(), 1.0, np.nan)
|
|
572
|
-
iv_val, n_b, miss =
|
|
588
|
+
iv_val, n_b, miss, iv_floored = _weighted_iv_detail(
|
|
589
|
+
y_ins, w_ins, bins_ins, x_ins, content=floor_content,
|
|
590
|
+
)
|
|
591
|
+
if floor_content is not None:
|
|
592
|
+
iv_floored_map[var] = iv_floored
|
|
573
593
|
iv_records.append({
|
|
574
594
|
"var": var,
|
|
575
595
|
"iv_weighted": iv_val,
|
|
@@ -580,7 +600,15 @@ def _weighted_woe_bins_screen(
|
|
|
580
600
|
columns=["var", "iv_weighted", "n_bins", "missing_rate"],
|
|
581
601
|
)
|
|
582
602
|
if not iv_table.empty:
|
|
583
|
-
|
|
603
|
+
if config.iv_upper_threshold is not None:
|
|
604
|
+
gate_frame = iv_table[["var", "iv_weighted"]].copy()
|
|
605
|
+
gate_frame["iv_floored"] = gate_frame["var"].map(iv_floored_map)
|
|
606
|
+
keep = _iv_band_keep(
|
|
607
|
+
gate_frame, "iv_weighted", config.iv_threshold,
|
|
608
|
+
config.iv_upper_threshold, dropped_rows, upper_col="iv_floored",
|
|
609
|
+
)
|
|
610
|
+
else:
|
|
611
|
+
keep = _iv_band_keep(iv_table, "iv_weighted", config.iv_threshold, config.iv_upper_threshold, dropped_rows)
|
|
584
612
|
n_before = len(current)
|
|
585
613
|
current = _apply_stage_keep(
|
|
586
614
|
current, keep, "iv", summary_rows,
|
|
@@ -614,9 +642,7 @@ def _weighted_woe_bins_screen(
|
|
|
614
642
|
|
|
615
643
|
from .Screen_Gates import apply_post_corr_gates
|
|
616
644
|
|
|
617
|
-
gate_iv_map = (
|
|
618
|
-
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
619
|
-
)
|
|
645
|
+
gate_iv_map = _gate_ranking_iv_map(iv_table, iv_floored_map, config.iv_upper_threshold)
|
|
620
646
|
current = apply_post_corr_gates(
|
|
621
647
|
ins, current, config, selection_evidence, gate_iv_map,
|
|
622
648
|
summary_rows, dropped_rows, stage_tables,
|
|
@@ -737,6 +763,7 @@ def feature_screen(
|
|
|
737
763
|
missing_rate_ref=cfg.missing_rate_ref,
|
|
738
764
|
gates_config=cfg,
|
|
739
765
|
selection_evidence=selection_evidence,
|
|
766
|
+
content=cfg.content,
|
|
740
767
|
)
|
|
741
768
|
|
|
742
769
|
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Screen_Gates.py
RENAMED
|
@@ -27,20 +27,41 @@ import pandas as pd
|
|
|
27
27
|
from .Weighted_Screen import _apply_stage_keep, _summary_row
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def point_biserial_direction(
|
|
30
|
+
def point_biserial_direction(
|
|
31
|
+
x: pd.Series, y: pd.Series, sample_weight: np.ndarray | None = None
|
|
32
|
+
) -> int:
|
|
31
33
|
"""Sign of the point-biserial association between a numeric feature and a
|
|
32
34
|
binary target: +1 when higher x means higher bad rate (WOE increasing),
|
|
33
35
|
-1 for the opposite, 0 when undefined. The single shared definition of
|
|
34
|
-
"direction" for G03/G04.
|
|
36
|
+
"direction" for G03/G04.
|
|
37
|
+
|
|
38
|
+
``sample_weight`` is positional-aligned with the passed ``x``/``y``
|
|
39
|
+
(before NaN masking). A class whose total weight is zero — like a class
|
|
40
|
+
with no rows — and a weighted tie both return 0, mirroring the
|
|
41
|
+
unweighted NaN/tie semantics."""
|
|
35
42
|
xv = pd.to_numeric(x, errors="coerce")
|
|
36
43
|
yv = pd.to_numeric(y, errors="coerce")
|
|
37
44
|
mask = xv.notna() & yv.notna()
|
|
38
45
|
if not bool(mask.any()):
|
|
39
46
|
return 0
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
if sample_weight is None:
|
|
48
|
+
xv = xv[mask]
|
|
49
|
+
yv = yv[mask]
|
|
50
|
+
mean_bad = xv[yv == 1].mean()
|
|
51
|
+
mean_good = xv[yv == 0].mean()
|
|
52
|
+
else:
|
|
53
|
+
mask_np = mask.to_numpy()
|
|
54
|
+
w = np.asarray(sample_weight, dtype=float)[mask_np]
|
|
55
|
+
xa = xv.to_numpy(dtype=float)[mask_np]
|
|
56
|
+
ya = yv.to_numpy(dtype=float)[mask_np]
|
|
57
|
+
w_bad = w[ya == 1]
|
|
58
|
+
w_good = w[ya == 0]
|
|
59
|
+
mean_bad = (
|
|
60
|
+
float(np.average(xa[ya == 1], weights=w_bad)) if w_bad.sum() > 0 else float("nan")
|
|
61
|
+
)
|
|
62
|
+
mean_good = (
|
|
63
|
+
float(np.average(xa[ya == 0], weights=w_good)) if w_good.sum() > 0 else float("nan")
|
|
64
|
+
)
|
|
44
65
|
if pd.isna(mean_bad) or pd.isna(mean_good) or mean_bad == mean_good:
|
|
45
66
|
return 0
|
|
46
67
|
return 1 if mean_bad > mean_good else -1
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Weighted_Screen.py
RENAMED
|
@@ -72,24 +72,94 @@ def _iv_band_keep(
|
|
|
72
72
|
upper: float | None,
|
|
73
73
|
dropped_rows: list | None = None,
|
|
74
74
|
var_col: str = "var",
|
|
75
|
+
upper_col: str | None = None,
|
|
75
76
|
) -> list[str]:
|
|
76
77
|
"""IV band gate (G02): keep lower <= iv, and iv <= upper when an upper
|
|
77
78
|
threshold is set. Upper-bound drops (suspiciously high IV — leakage /
|
|
78
|
-
look-ahead candidates) are recorded in dropped_rows.
|
|
79
|
+
look-ahead candidates) are recorded in dropped_rows.
|
|
80
|
+
|
|
81
|
+
``upper_col`` names an alternative column for the upper test only. The
|
|
82
|
+
degenerate-guarded IV excludes class-pure bins, so near-perfect
|
|
83
|
+
separators — the exact features the upper band targets — understate it;
|
|
84
|
+
callers pass a zero-cell-floored IV here while the lower band and the
|
|
85
|
+
reported table keep the legacy column. Rows with NaN in ``upper_col``
|
|
86
|
+
fall back to ``iv_col``."""
|
|
79
87
|
keep_mask = iv_frame[iv_col] >= lower
|
|
80
88
|
if upper is not None:
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
if upper_col is not None and upper_col in iv_frame.columns:
|
|
90
|
+
gate = iv_frame[upper_col].where(iv_frame[upper_col].notna(), iv_frame[iv_col])
|
|
91
|
+
metric = upper_col
|
|
92
|
+
else:
|
|
93
|
+
gate = iv_frame[iv_col]
|
|
94
|
+
metric = "iv"
|
|
95
|
+
above = iv_frame[gate > upper]
|
|
96
|
+
keep_mask &= gate <= upper
|
|
83
97
|
if dropped_rows is not None:
|
|
84
|
-
for
|
|
98
|
+
for idx, row in above.iterrows():
|
|
85
99
|
dropped_rows.append({
|
|
86
|
-
"var": row[var_col], "stage": "iv", "metric":
|
|
87
|
-
"value": float(
|
|
100
|
+
"var": row[var_col], "stage": "iv", "metric": metric,
|
|
101
|
+
"value": float(gate.loc[idx]), "threshold": upper,
|
|
88
102
|
"reason": "iv_above_upper",
|
|
89
103
|
})
|
|
90
104
|
return iv_frame.loc[keep_mask, var_col].tolist()
|
|
91
105
|
|
|
92
106
|
|
|
107
|
+
def _unit_weight_floored_iv(
|
|
108
|
+
ins: pd.DataFrame,
|
|
109
|
+
varlist: list[str],
|
|
110
|
+
target_col: str,
|
|
111
|
+
*,
|
|
112
|
+
iv_bins: int,
|
|
113
|
+
min_bin_prop: float,
|
|
114
|
+
content: float,
|
|
115
|
+
precision: int = 5,
|
|
116
|
+
) -> list[float]:
|
|
117
|
+
"""Zero-cell-floored equal-freq IV with unit weights, for the G02 upper
|
|
118
|
+
band on the gains-table path: its filliv convention zeroes pure bins and
|
|
119
|
+
would otherwise understate hard leakage the same way the degenerate
|
|
120
|
+
guard does. Non-numeric columns return NaN so the gate falls back to
|
|
121
|
+
the reported IV. The remainder-IV warning is suppressed — only the
|
|
122
|
+
floored diagnostic is consumed here."""
|
|
123
|
+
y = ins[target_col].astype(float).to_numpy()
|
|
124
|
+
w = np.ones(len(ins), dtype=float)
|
|
125
|
+
out: list[float] = []
|
|
126
|
+
for var in varlist:
|
|
127
|
+
if var not in ins.columns or not pd.api.types.is_numeric_dtype(ins[var]):
|
|
128
|
+
out.append(float("nan"))
|
|
129
|
+
continue
|
|
130
|
+
with warnings.catch_warnings():
|
|
131
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
132
|
+
_, _, _, floored = _weighted_iv_for_var(
|
|
133
|
+
ins[var].to_numpy(dtype=float), y, w, iv_bins, min_bin_prop, precision,
|
|
134
|
+
var_name=var, on_null_edges="silent", content=content,
|
|
135
|
+
)
|
|
136
|
+
out.append(floored)
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _gate_ranking_iv_map(
|
|
141
|
+
iv_table: pd.DataFrame,
|
|
142
|
+
iv_floored_map: dict[str, float] | None,
|
|
143
|
+
iv_upper_threshold: float | None,
|
|
144
|
+
) -> dict[str, float]:
|
|
145
|
+
"""Ranking metric for the post-corr gates (G05 truncation, G06 VIF
|
|
146
|
+
tie-break). Legacy basis is the degenerate-GUARDED reported IV. When the
|
|
147
|
+
G02 upper band is armed, leaks are already dropped by the band, so
|
|
148
|
+
ranking survivors on the guarded IV would understate legit features with
|
|
149
|
+
class-pure bins; substitute the zero-cell-floored IV where finite (NaN
|
|
150
|
+
floored values — e.g. non-numeric columns on the unit-weight diagnostic —
|
|
151
|
+
keep the guarded value, matching _iv_band_keep's fallback). upper=None
|
|
152
|
+
returns the legacy map verbatim."""
|
|
153
|
+
gate_iv_map = (
|
|
154
|
+
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
155
|
+
)
|
|
156
|
+
if iv_upper_threshold is not None:
|
|
157
|
+
for var, floored in (iv_floored_map or {}).items():
|
|
158
|
+
if var in gate_iv_map and np.isfinite(floored):
|
|
159
|
+
gate_iv_map[var] = float(floored)
|
|
160
|
+
return gate_iv_map
|
|
161
|
+
|
|
162
|
+
|
|
93
163
|
def _resolve_splits(data: pd.DataFrame, split_col: str) -> dict[str, pd.DataFrame]:
|
|
94
164
|
if split_col not in data.columns:
|
|
95
165
|
raise KeyError(f"Missing split_col {split_col!r}")
|
|
@@ -209,7 +279,7 @@ def _psi_from_distributions(expected: pd.Series, actual: pd.Series, content: flo
|
|
|
209
279
|
return float(((a - e) * np.log(a / e)).sum())
|
|
210
280
|
|
|
211
281
|
|
|
212
|
-
def
|
|
282
|
+
def _weighted_iv_detail(
|
|
213
283
|
y: np.ndarray,
|
|
214
284
|
w: np.ndarray,
|
|
215
285
|
bins: np.ndarray,
|
|
@@ -217,7 +287,21 @@ def _weighted_iv_from_assigned_bins(
|
|
|
217
287
|
*,
|
|
218
288
|
var_name: str | None = None,
|
|
219
289
|
include_missing_bin: bool = False,
|
|
220
|
-
|
|
290
|
+
content: float | None = None,
|
|
291
|
+
) -> tuple[float, int, float, float]:
|
|
292
|
+
"""Weighted IV plus an optional zero-cell-floored variant.
|
|
293
|
+
|
|
294
|
+
The reported IV (first element) excludes class-degenerate bins via
|
|
295
|
+
iv_guard. That is right for the *lower* informativeness bound, but it
|
|
296
|
+
understates IV for near-perfectly separating features: a pure bin
|
|
297
|
+
carries the strongest possible signal yet contributes nothing, so the
|
|
298
|
+
exact leakage candidates iv_upper_threshold (G02) exists to catch sail
|
|
299
|
+
under the band. When ``content`` is given, the fourth element floors
|
|
300
|
+
each bin's class shares at ``content`` (the same floor the PSI path
|
|
301
|
+
uses) so pure bins contribute a large finite term, matching the
|
|
302
|
+
WOE-engine convention; features with no degenerate bins get
|
|
303
|
+
iv_floored == iv. When ``content`` is None the fourth element is NaN.
|
|
304
|
+
"""
|
|
221
305
|
included = np.ones(len(bins), dtype=bool)
|
|
222
306
|
if not include_missing_bin:
|
|
223
307
|
included &= bins != _MISSING_BIN
|
|
@@ -225,9 +309,10 @@ def _weighted_iv_from_assigned_bins(
|
|
|
225
309
|
total_good = float(np.sum(w[included] * (1.0 - y[included])))
|
|
226
310
|
if total_bad <= 0 or total_good <= 0:
|
|
227
311
|
missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
|
|
228
|
-
return 0.0, 0, missing_rate
|
|
312
|
+
return 0.0, 0, missing_rate, (0.0 if content is not None else float("nan"))
|
|
229
313
|
|
|
230
314
|
iv = 0.0
|
|
315
|
+
iv_floored = 0.0 if content is not None else float("nan")
|
|
231
316
|
n_bins = 0
|
|
232
317
|
n_contributing_bins = 0
|
|
233
318
|
n_degenerate = 0
|
|
@@ -238,7 +323,13 @@ def _weighted_iv_from_assigned_bins(
|
|
|
238
323
|
m = bins == b
|
|
239
324
|
bad_w = float(np.sum(w[m] * y[m]))
|
|
240
325
|
good_w = float(np.sum(w[m] * (1.0 - y[m])))
|
|
241
|
-
|
|
326
|
+
bad_share = bad_w / total_bad
|
|
327
|
+
good_share = good_w / total_good
|
|
328
|
+
contrib, is_degenerate = iv_guard(bad_share, good_share)
|
|
329
|
+
if content is not None:
|
|
330
|
+
floor_bad = max(bad_share, content)
|
|
331
|
+
floor_good = max(good_share, content)
|
|
332
|
+
iv_floored += float((floor_bad - floor_good) * np.log(floor_bad / floor_good))
|
|
242
333
|
if is_degenerate:
|
|
243
334
|
n_degenerate += 1
|
|
244
335
|
continue
|
|
@@ -254,7 +345,7 @@ def _weighted_iv_from_assigned_bins(
|
|
|
254
345
|
stacklevel=2,
|
|
255
346
|
)
|
|
256
347
|
missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
|
|
257
|
-
return 0.0, 0, missing_rate
|
|
348
|
+
return 0.0, 0, missing_rate, iv_floored
|
|
258
349
|
|
|
259
350
|
if n_degenerate:
|
|
260
351
|
prefix = f"{var_name}: " if var_name else ""
|
|
@@ -265,7 +356,22 @@ def _weighted_iv_from_assigned_bins(
|
|
|
265
356
|
stacklevel=2,
|
|
266
357
|
)
|
|
267
358
|
missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
|
|
268
|
-
return float(iv), n_bins, missing_rate
|
|
359
|
+
return float(iv), n_bins, missing_rate, iv_floored
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _weighted_iv_from_assigned_bins(
|
|
363
|
+
y: np.ndarray,
|
|
364
|
+
w: np.ndarray,
|
|
365
|
+
bins: np.ndarray,
|
|
366
|
+
x: np.ndarray,
|
|
367
|
+
*,
|
|
368
|
+
var_name: str | None = None,
|
|
369
|
+
include_missing_bin: bool = False,
|
|
370
|
+
) -> tuple[float, int, float]:
|
|
371
|
+
iv, n_bins, missing_rate, _ = _weighted_iv_detail(
|
|
372
|
+
y, w, bins, x, var_name=var_name, include_missing_bin=include_missing_bin,
|
|
373
|
+
)
|
|
374
|
+
return iv, n_bins, missing_rate
|
|
269
375
|
|
|
270
376
|
|
|
271
377
|
def _weighted_iv_for_var(
|
|
@@ -279,16 +385,18 @@ def _weighted_iv_for_var(
|
|
|
279
385
|
var_name: str | None = None,
|
|
280
386
|
include_missing_bin: bool = False,
|
|
281
387
|
on_null_edges: Literal["raise", "warn_and_zero", "silent"] = "raise",
|
|
282
|
-
|
|
388
|
+
content: float | None = None,
|
|
389
|
+
) -> tuple[float, int, float, float]:
|
|
283
390
|
edges = _weighted_equal_freq_edges(x, w, n_bins, min_bin_prop, precision=precision)
|
|
284
391
|
bins = _assign_bins(x, edges, name=var_name, on_null_edges=on_null_edges)
|
|
285
|
-
return
|
|
392
|
+
return _weighted_iv_detail(
|
|
286
393
|
y,
|
|
287
394
|
w,
|
|
288
395
|
bins,
|
|
289
396
|
x,
|
|
290
397
|
var_name=var_name,
|
|
291
398
|
include_missing_bin=include_missing_bin,
|
|
399
|
+
content=content,
|
|
292
400
|
)
|
|
293
401
|
|
|
294
402
|
|
|
@@ -721,6 +829,7 @@ def _legacy_unweighted_screen(
|
|
|
721
829
|
missing_rate_ref: Any = None,
|
|
722
830
|
gates_config: Any | None = None,
|
|
723
831
|
selection_evidence: Any | None = None,
|
|
832
|
+
content: float = 1e-6,
|
|
724
833
|
) -> WeightedScreenResult:
|
|
725
834
|
from Modeling_Tool import CorrelationFilter, PSICalculator, VarExtractionInsights
|
|
726
835
|
|
|
@@ -772,6 +881,7 @@ def _legacy_unweighted_screen(
|
|
|
772
881
|
summary_rows.append(_summary_row("psi", n_before, len(current), psi_threshold, None))
|
|
773
882
|
|
|
774
883
|
iv_table = pd.DataFrame(columns=["var", "iv_weighted", "n_bins", "missing_rate"])
|
|
884
|
+
iv_floored_map: dict[str, float] = {}
|
|
775
885
|
if iv_enabled:
|
|
776
886
|
vi = VarExtractionInsights(
|
|
777
887
|
data=ins,
|
|
@@ -794,7 +904,19 @@ def _legacy_unweighted_screen(
|
|
|
794
904
|
plot_path=plot_path,
|
|
795
905
|
)
|
|
796
906
|
iv_table = iv.rename(columns={"iv": "iv_weighted"})[["var", "iv_weighted", "n_bins", "missing_rate"]]
|
|
797
|
-
|
|
907
|
+
if iv_upper_threshold is not None:
|
|
908
|
+
gate_frame = iv[["var", "iv"]].copy()
|
|
909
|
+
gate_frame["iv_floored"] = _unit_weight_floored_iv(
|
|
910
|
+
ins, list(gate_frame["var"]), target_col,
|
|
911
|
+
iv_bins=iv_bins, min_bin_prop=iv_min_bin_prop, content=content,
|
|
912
|
+
)
|
|
913
|
+
iv_floored_map = dict(zip(gate_frame["var"], gate_frame["iv_floored"]))
|
|
914
|
+
keep = _iv_band_keep(
|
|
915
|
+
gate_frame, "iv", iv_threshold, iv_upper_threshold, dropped_rows,
|
|
916
|
+
upper_col="iv_floored",
|
|
917
|
+
)
|
|
918
|
+
else:
|
|
919
|
+
keep = _iv_band_keep(iv, "iv", iv_threshold, iv_upper_threshold, dropped_rows)
|
|
798
920
|
n_before = len(current)
|
|
799
921
|
current = _apply_stage_keep(
|
|
800
922
|
current, keep, "iv", summary_rows,
|
|
@@ -816,9 +938,7 @@ def _legacy_unweighted_screen(
|
|
|
816
938
|
if gates_config is not None:
|
|
817
939
|
from .Screen_Gates import apply_post_corr_gates
|
|
818
940
|
|
|
819
|
-
gate_iv_map = (
|
|
820
|
-
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
821
|
-
)
|
|
941
|
+
gate_iv_map = _gate_ranking_iv_map(iv_table, iv_floored_map, iv_upper_threshold)
|
|
822
942
|
current = apply_post_corr_gates(
|
|
823
943
|
ins, current, gates_config, selection_evidence, gate_iv_map,
|
|
824
944
|
summary_rows, dropped_rows, stage_tables,
|
|
@@ -941,11 +1061,13 @@ def _weighted_screen_impl(
|
|
|
941
1061
|
psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
|
|
942
1062
|
|
|
943
1063
|
iv_records: list[dict] = []
|
|
1064
|
+
iv_floored_map: dict[str, float] = {}
|
|
944
1065
|
if iv_enabled:
|
|
1066
|
+
floor_content = content if iv_upper_threshold is not None else None
|
|
945
1067
|
for var in current:
|
|
946
1068
|
if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
|
|
947
1069
|
continue
|
|
948
|
-
iv_val, n_b, miss = _weighted_iv_for_var(
|
|
1070
|
+
iv_val, n_b, miss, iv_floored = _weighted_iv_for_var(
|
|
949
1071
|
ins[var].to_numpy(dtype=float),
|
|
950
1072
|
y_ins,
|
|
951
1073
|
w_ins,
|
|
@@ -953,7 +1075,10 @@ def _weighted_screen_impl(
|
|
|
953
1075
|
min_bin_prop,
|
|
954
1076
|
precision,
|
|
955
1077
|
var_name=var,
|
|
1078
|
+
content=floor_content,
|
|
956
1079
|
)
|
|
1080
|
+
if floor_content is not None:
|
|
1081
|
+
iv_floored_map[var] = iv_floored
|
|
957
1082
|
iv_records.append({
|
|
958
1083
|
"var": var,
|
|
959
1084
|
"iv_weighted": iv_val,
|
|
@@ -964,7 +1089,15 @@ def _weighted_screen_impl(
|
|
|
964
1089
|
columns=["var", "iv_weighted", "n_bins", "missing_rate"],
|
|
965
1090
|
)
|
|
966
1091
|
if not iv_table.empty:
|
|
967
|
-
|
|
1092
|
+
if iv_upper_threshold is not None:
|
|
1093
|
+
gate_frame = iv_table[["var", "iv_weighted"]].copy()
|
|
1094
|
+
gate_frame["iv_floored"] = gate_frame["var"].map(iv_floored_map)
|
|
1095
|
+
keep = _iv_band_keep(
|
|
1096
|
+
gate_frame, "iv_weighted", iv_threshold, iv_upper_threshold,
|
|
1097
|
+
dropped_rows, upper_col="iv_floored",
|
|
1098
|
+
)
|
|
1099
|
+
else:
|
|
1100
|
+
keep = _iv_band_keep(iv_table, "iv_weighted", iv_threshold, iv_upper_threshold, dropped_rows)
|
|
968
1101
|
n_before = len(current)
|
|
969
1102
|
current = _apply_stage_keep(
|
|
970
1103
|
current, keep, "iv", summary_rows,
|
|
@@ -994,9 +1127,7 @@ def _weighted_screen_impl(
|
|
|
994
1127
|
if gates_config is not None:
|
|
995
1128
|
from .Screen_Gates import apply_post_corr_gates
|
|
996
1129
|
|
|
997
|
-
gate_iv_map = (
|
|
998
|
-
dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
999
|
-
)
|
|
1130
|
+
gate_iv_map = _gate_ranking_iv_map(iv_table, iv_floored_map, iv_upper_threshold)
|
|
1000
1131
|
current = apply_post_corr_gates(
|
|
1001
1132
|
ins, current, gates_config, selection_evidence, gate_iv_map,
|
|
1002
1133
|
summary_rows, dropped_rows, stage_tables,
|
|
@@ -1447,12 +1447,29 @@ class FeatureValidationPipeline:
|
|
|
1447
1447
|
return adapter.assign_bins_frame(data, usable)
|
|
1448
1448
|
|
|
1449
1449
|
@staticmethod
|
|
1450
|
-
def _evidence_iv(
|
|
1450
|
+
def _evidence_iv(
|
|
1451
|
+
bins: np.ndarray,
|
|
1452
|
+
target_values: np.ndarray,
|
|
1453
|
+
sample_weight: np.ndarray | None = None,
|
|
1454
|
+
) -> float:
|
|
1451
1455
|
"""Same WOE/IV formula as _ivks_from_assigned_bins_grouped, minus the
|
|
1452
|
-
iv_cut/min_group_size report filters (gates need unfiltered evidence).
|
|
1456
|
+
iv_cut/min_group_size report filters (gates need unfiltered evidence).
|
|
1457
|
+
|
|
1458
|
+
``sample_weight`` (positional-aligned with ``bins``) switches counts
|
|
1459
|
+
to weighted mass so gate evidence matches the weighted modeling
|
|
1460
|
+
basis; None reproduces the unweighted arithmetic exactly. Weights are
|
|
1461
|
+
expected on frequency scale (e.g. FT weights): the max(total, 1.0)
|
|
1462
|
+
floors are kept verbatim, so a zero-mass class or slice degrades to
|
|
1463
|
+
iv == 0.0 (never NaN) and sub-unit total mass is damped — both err
|
|
1464
|
+
toward dropping under a lower IV floor."""
|
|
1453
1465
|
bin_codes, _ = pd.factorize(bins, sort=False)
|
|
1454
|
-
|
|
1455
|
-
|
|
1466
|
+
if sample_weight is None:
|
|
1467
|
+
counts = np.bincount(bin_codes).astype(float)
|
|
1468
|
+
bad = np.bincount(bin_codes, weights=target_values)
|
|
1469
|
+
else:
|
|
1470
|
+
w = np.asarray(sample_weight, dtype=float)
|
|
1471
|
+
counts = np.bincount(bin_codes, weights=w)
|
|
1472
|
+
bad = np.bincount(bin_codes, weights=w * np.asarray(target_values, dtype=float))
|
|
1456
1473
|
good = counts - bad
|
|
1457
1474
|
bad_pct = bad / max(float(bad.sum()), 1.0)
|
|
1458
1475
|
good_pct = good / max(float(good.sum()), 1.0)
|
|
@@ -1467,7 +1484,12 @@ class FeatureValidationPipeline:
|
|
|
1467
1484
|
screen_cfg: Any,
|
|
1468
1485
|
) -> Any | None:
|
|
1469
1486
|
"""Lazy G03/G04 evidence closures over INS, priced only on the
|
|
1470
|
-
post-corr survivor set the gates hand them.
|
|
1487
|
+
post-corr survivor set the gates hand them.
|
|
1488
|
+
|
|
1489
|
+
Evidence IVs and directions respect ``cfg.weight_col`` so gate
|
|
1490
|
+
decisions share the weighted modeling basis; the ``n`` column stays a
|
|
1491
|
+
RAW observation count (min_group_n gauges statistical sufficiency,
|
|
1492
|
+
which weights do not add to)."""
|
|
1471
1493
|
cfg = self.config
|
|
1472
1494
|
group_gates_on = any(
|
|
1473
1495
|
getattr(screen_cfg, name, None) is not None
|
|
@@ -1483,6 +1505,13 @@ class FeatureValidationPipeline:
|
|
|
1483
1505
|
|
|
1484
1506
|
ins = splits["ins"]
|
|
1485
1507
|
primary = target_cols[0]
|
|
1508
|
+
w_ins = None
|
|
1509
|
+
if cfg.weight_col:
|
|
1510
|
+
from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
|
|
1511
|
+
|
|
1512
|
+
w_ins = resolve_sample_weight(
|
|
1513
|
+
data=ins, weight_col=cfg.weight_col, expected_len=len(ins)
|
|
1514
|
+
)
|
|
1486
1515
|
group_dims = list(cfg.selection_group_dims or [])
|
|
1487
1516
|
if group_gates_on:
|
|
1488
1517
|
if not group_dims:
|
|
@@ -1498,7 +1527,9 @@ class FeatureValidationPipeline:
|
|
|
1498
1527
|
)
|
|
1499
1528
|
|
|
1500
1529
|
def per_group_iv_fn(features: list[str]) -> pd.DataFrame:
|
|
1501
|
-
|
|
1530
|
+
obs_mask = ins[primary].notna()
|
|
1531
|
+
data = ins[obs_mask]
|
|
1532
|
+
w_data = w_ins[obs_mask.to_numpy()] if w_ins is not None else None
|
|
1502
1533
|
bins_frame = self._evidence_bins_frame(data, features, primary, woe_artifacts)
|
|
1503
1534
|
if bins_frame is None:
|
|
1504
1535
|
raise ValueError(
|
|
@@ -1514,6 +1545,7 @@ class FeatureValidationPipeline:
|
|
|
1514
1545
|
if isinstance(group_value, tuple) else str(group_value)
|
|
1515
1546
|
)
|
|
1516
1547
|
y_g = y[positions]
|
|
1548
|
+
w_g = w_data[positions] if w_data is not None else None
|
|
1517
1549
|
sub = data.iloc[positions]
|
|
1518
1550
|
for var in features:
|
|
1519
1551
|
if var not in bins_frame.columns:
|
|
@@ -1524,15 +1556,18 @@ class FeatureValidationPipeline:
|
|
|
1524
1556
|
"var": var,
|
|
1525
1557
|
"group": label,
|
|
1526
1558
|
"n": int(len(positions)),
|
|
1527
|
-
"iv": self._evidence_iv(bins, y_g),
|
|
1528
|
-
"direction": point_biserial_direction(
|
|
1559
|
+
"iv": self._evidence_iv(bins, y_g, sample_weight=w_g),
|
|
1560
|
+
"direction": point_biserial_direction(
|
|
1561
|
+
sub[var], sub[primary], sample_weight=w_g
|
|
1562
|
+
),
|
|
1529
1563
|
})
|
|
1530
1564
|
return pd.DataFrame(rows, columns=["var", "group", "n", "iv", "direction"])
|
|
1531
1565
|
|
|
1532
1566
|
def per_target_fn(features: list[str]) -> pd.DataFrame:
|
|
1533
1567
|
rows: list[dict] = []
|
|
1534
1568
|
for target in target_cols:
|
|
1535
|
-
|
|
1569
|
+
obs_mask = ins[target].notna()
|
|
1570
|
+
data = ins[obs_mask]
|
|
1536
1571
|
if not len(data):
|
|
1537
1572
|
rows.extend(
|
|
1538
1573
|
{"var": var, "target": target, "iv": np.nan,
|
|
@@ -1540,13 +1575,16 @@ class FeatureValidationPipeline:
|
|
|
1540
1575
|
for var in features
|
|
1541
1576
|
)
|
|
1542
1577
|
continue
|
|
1578
|
+
w_t = w_ins[obs_mask.to_numpy()] if w_ins is not None else None
|
|
1543
1579
|
bins_frame = self._evidence_bins_frame(data, features, target, woe_artifacts)
|
|
1544
1580
|
y = data[target].astype(int).to_numpy()
|
|
1545
1581
|
for var in features:
|
|
1546
1582
|
if bins_frame is None or var not in bins_frame.columns:
|
|
1547
1583
|
rows.append({
|
|
1548
1584
|
"var": var, "target": target, "iv": np.nan,
|
|
1549
|
-
"direction": point_biserial_direction(
|
|
1585
|
+
"direction": point_biserial_direction(
|
|
1586
|
+
data[var], data[target], sample_weight=w_t
|
|
1587
|
+
)
|
|
1550
1588
|
if var in data.columns else 0,
|
|
1551
1589
|
"status": "engine_missing",
|
|
1552
1590
|
})
|
|
@@ -1556,8 +1594,10 @@ class FeatureValidationPipeline:
|
|
|
1556
1594
|
rows.append({
|
|
1557
1595
|
"var": var,
|
|
1558
1596
|
"target": target,
|
|
1559
|
-
"iv": self._evidence_iv(bins, y),
|
|
1560
|
-
"direction": point_biserial_direction(
|
|
1597
|
+
"iv": self._evidence_iv(bins, y, sample_weight=w_t),
|
|
1598
|
+
"direction": point_biserial_direction(
|
|
1599
|
+
data[var], data[target], sample_weight=w_t
|
|
1600
|
+
),
|
|
1561
1601
|
"status": "ok",
|
|
1562
1602
|
})
|
|
1563
1603
|
return pd.DataFrame(rows, columns=["var", "target", "iv", "direction", "status"])
|
|
@@ -1627,6 +1667,7 @@ class FeatureValidationPipeline:
|
|
|
1627
1667
|
"vif_enabled": screen_cfg.vif_enabled,
|
|
1628
1668
|
"vif_threshold": screen_cfg.vif_threshold,
|
|
1629
1669
|
"selection_group_dims": list(cfg.selection_group_dims or []),
|
|
1670
|
+
"evidence_weight_col": cfg.weight_col,
|
|
1630
1671
|
})
|
|
1631
1672
|
selection_summary = {
|
|
1632
1673
|
"initial_features": list(new_features),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SuperModelingFactory
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.
|
|
5
5
|
Home-page: https://github.com/Kyle-J-Sun/SuperModelingFactory
|
|
6
6
|
Author: Kyle Sun
|
|
@@ -265,7 +265,7 @@ em.close_workbook()
|
|
|
265
265
|
|
|
266
266
|
## 版本
|
|
267
267
|
|
|
268
|
-
- **Version**: 0.6.
|
|
268
|
+
- **Version**: 0.6.8
|
|
269
269
|
- **Author**: Jingkai Sun
|
|
270
270
|
|
|
271
271
|
## 许可证
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SuperModelingFactory
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.
|
|
5
5
|
Home-page: https://github.com/Kyle-J-Sun/SuperModelingFactory
|
|
6
6
|
Author: Kyle Sun
|
|
@@ -265,7 +265,7 @@ em.close_workbook()
|
|
|
265
265
|
|
|
266
266
|
## 版本
|
|
267
267
|
|
|
268
|
-
- **Version**: 0.6.
|
|
268
|
+
- **Version**: 0.6.8
|
|
269
269
|
- **Author**: Jingkai Sun
|
|
270
270
|
|
|
271
271
|
## 许可证
|
|
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "SuperModelingFactory"
|
|
15
|
-
version = "0.6.
|
|
15
|
+
version = "0.6.8"
|
|
16
16
|
description = "Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting."
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
requires-python = ">=3.10"
|
|
@@ -15,7 +15,7 @@ def _read(path: str) -> str:
|
|
|
15
15
|
|
|
16
16
|
setup(
|
|
17
17
|
name="SuperModelingFactory",
|
|
18
|
-
version=os.environ.get("SMF_VERSION", "0.6.
|
|
18
|
+
version=os.environ.get("SMF_VERSION", "0.6.8"),
|
|
19
19
|
description="Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.",
|
|
20
20
|
long_description=_read("README.md"),
|
|
21
21
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Binning_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Binning_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Json_Data_Converter.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Model_Registry_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Parallel_Engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/Proc_Compare.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/XOR_Encryptor.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/XOR_Encryptor.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Core/sample_weight_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Evaluation_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Evaluation_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Model_Eval_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/Model_Eval_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/evaluate_model.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/evaluate_model.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Eval/weighted_eval_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Explainability/__init__.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Distribution_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Feature_Insights.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/Feature_Insights.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Feature/PSI_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/Backward_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/Backward_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Model/GBM_Search_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/credit_model.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/field_meta.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/mock_sample.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/orchestrator.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/reject_inference.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/sample_analysis.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Pipeline/score_comparison.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Reject_Infer.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Reject_Infer.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Sample_Split.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/Sample/Sample_Split.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Monotone_Binner.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Plot_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Report_Builder.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/WOE_Report_Builder.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/plot_woe_tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/WOE/plot_woe_tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/Modeling_Tool/ref_font/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.7 → supermodelingfactory-0.6.8}/SuperModelingFactory.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|