SuperModelingFactory 0.5.8__tar.gz → 0.5.10__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.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Binning_Tool.py +57 -29
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Binning_Tool.pyi +1 -1
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/utils.py +22 -21
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/_common.py +6 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/credit_model.py +1 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/feature_validation.py +72 -23
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/score_comparison.py +13 -5
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/screening_artifact.py +29 -6
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Master.py +27 -14
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Tool.py +189 -103
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Tool.pyi +1 -1
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/PKG-INFO +2 -2
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/README.md +1 -1
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/pyproject.toml +1 -1
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/setup.py +1 -1
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/LICENSE +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/MANIFEST.in +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/Weighted_Screen.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/field_meta.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Report/__init__.py +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/requirements.txt +0 -0
- {supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/setup.cfg +0 -0
{supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Binning_Tool.py
RENAMED
|
@@ -706,6 +706,46 @@ def get_bin_range(edges, precision = 5, ascending = False, left_sign = '(', righ
|
|
|
706
706
|
return res
|
|
707
707
|
|
|
708
708
|
|
|
709
|
+
def _materialize_bin_columns(data, binned, bin_range_list, bin_num_col, bin_range_col):
|
|
710
|
+
"""Attach categorical bin numbers and labels without Python row callbacks."""
|
|
711
|
+
codes = binned.cat.codes.to_numpy(dtype=np.intp, copy=False)
|
|
712
|
+
range_values = np.empty(len(codes), dtype=object)
|
|
713
|
+
range_values[:] = np.nan
|
|
714
|
+
valid = codes >= 0
|
|
715
|
+
if valid.any():
|
|
716
|
+
labels = np.asarray(bin_range_list, dtype=object)
|
|
717
|
+
if codes[valid].max() >= len(labels):
|
|
718
|
+
raise ValueError("Binning category codes do not match bin range labels")
|
|
719
|
+
range_values[valid] = np.take(labels, codes[valid])
|
|
720
|
+
|
|
721
|
+
data[bin_num_col] = binned.astype(object)
|
|
722
|
+
data[bin_range_col] = range_values
|
|
723
|
+
return data
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def _parse_bin_range_bounds(data, col="_bin_range"):
|
|
727
|
+
"""Parse interval labels into numeric left and right bounds vectorially."""
|
|
728
|
+
if col not in data.columns:
|
|
729
|
+
raise KeyError(f"Column {col!r} is not present in the bin mapping table")
|
|
730
|
+
|
|
731
|
+
ranges = data[col].astype("string").str.strip()
|
|
732
|
+
bounds = ranges.str.extract(
|
|
733
|
+
r"^[\[\(]\s*([^,]+?)\s*,\s*([^\]\)]+?)\s*[\]\)]$",
|
|
734
|
+
expand=True,
|
|
735
|
+
)
|
|
736
|
+
invalid = bounds.isna().any(axis=1)
|
|
737
|
+
if invalid.any():
|
|
738
|
+
examples = ranges.loc[invalid].dropna().head(3).tolist()
|
|
739
|
+
raise ValueError(f"Unable to parse bin range labels in {col!r}: {examples}")
|
|
740
|
+
|
|
741
|
+
try:
|
|
742
|
+
left = bounds.iloc[:, 0].str.strip().astype(float).to_numpy()
|
|
743
|
+
right = bounds.iloc[:, 1].str.strip().astype(float).to_numpy()
|
|
744
|
+
except (TypeError, ValueError) as exc:
|
|
745
|
+
raise ValueError(f"Bin range column {col!r} contains non-numeric bounds") from exc
|
|
746
|
+
return left, right
|
|
747
|
+
|
|
748
|
+
|
|
709
749
|
def get_bin_range_list(data, col = "_bin_range"):
|
|
710
750
|
"""
|
|
711
751
|
将分箱区间字符串列转换为边界值列表。
|
|
@@ -730,21 +770,8 @@ def get_bin_range_list(data, col = "_bin_range"):
|
|
|
730
770
|
>>> unique_range = get_bin_range_list(data, col="bin_range")
|
|
731
771
|
"""
|
|
732
772
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
.replace("(", "")\
|
|
736
|
-
.replace(")", "")\
|
|
737
|
-
.split(","))
|
|
738
|
-
bin_range = [np.inf if v.strip() == 'inf' else -np.inf if v.strip() == '-inf' else float(v.strip()) for x in data["bin_value_list"].tolist() for v in x]
|
|
739
|
-
|
|
740
|
-
unique_range = []
|
|
741
|
-
for x in bin_range:
|
|
742
|
-
if x not in unique_range:
|
|
743
|
-
unique_range.append(x)
|
|
744
|
-
|
|
745
|
-
unique_range.sort()
|
|
746
|
-
|
|
747
|
-
return unique_range
|
|
773
|
+
left, right = _parse_bin_range_bounds(data, col=col)
|
|
774
|
+
return np.unique(np.concatenate([left, right])).tolist()
|
|
748
775
|
|
|
749
776
|
|
|
750
777
|
def chi2_auto_binning(df_pvt, max_bins, min_cnt_in_bin, p=0.95):
|
|
@@ -1165,9 +1192,8 @@ class Binning:
|
|
|
1165
1192
|
bin_num_col = self.bin_colnames[0]
|
|
1166
1193
|
bin_range_col = self.bin_colnames[1]
|
|
1167
1194
|
|
|
1168
|
-
self.data
|
|
1169
|
-
|
|
1170
|
-
lambda x: bin_range_list[int(x)] if self.include_missing else bin_range_list[int(x - 1)]
|
|
1195
|
+
self.data = _materialize_bin_columns(
|
|
1196
|
+
self.data, binned, bin_range_list, bin_num_col, bin_range_col
|
|
1171
1197
|
)
|
|
1172
1198
|
|
|
1173
1199
|
self.result = self.data
|
|
@@ -1237,9 +1263,8 @@ class Binning:
|
|
|
1237
1263
|
bin_num_col = self.bin_colnames[0]
|
|
1238
1264
|
bin_range_col = self.bin_colnames[1]
|
|
1239
1265
|
|
|
1240
|
-
self.data
|
|
1241
|
-
|
|
1242
|
-
lambda x: bin_range_list[int(x)] if self.include_missing else bin_range_list[int(x - 1)]
|
|
1266
|
+
self.data = _materialize_bin_columns(
|
|
1267
|
+
self.data, binned, bin_range_list, bin_num_col, bin_range_col
|
|
1243
1268
|
)
|
|
1244
1269
|
|
|
1245
1270
|
self.bin_edges = sorted([np.inf if str(x).lower() == 'inf' else -np.inf if str(x).lower() == '-inf' else x for x in bin_edges])
|
|
@@ -1385,10 +1410,9 @@ def chi2_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, t
|
|
|
1385
1410
|
binned = binning_series
|
|
1386
1411
|
rename_catlist = [i for i in range(1, len(bin_range_list) + 1)] if not include_missing else [i for i in range(0, len(bin_range_list))]
|
|
1387
1412
|
binned = binned.cat.rename_categories(rename_catlist)
|
|
1388
|
-
data
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
data[bin_range_col] = data[bin_num_col].apply(lambda x: bin_range_list[int(x)] if include_missing else bin_range_list[int(x - 1)])
|
|
1413
|
+
data = _materialize_bin_columns(
|
|
1414
|
+
data, binned, bin_range_list, bin_num_col, bin_range_col
|
|
1415
|
+
)
|
|
1392
1416
|
|
|
1393
1417
|
fnl_res = data
|
|
1394
1418
|
|
|
@@ -1399,7 +1423,7 @@ def chi2_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, t
|
|
|
1399
1423
|
|
|
1400
1424
|
def run_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, include_missing = True, equal_freq = True,
|
|
1401
1425
|
bin_colnames = ("bin_num", "bin_range"), ascending = False, right = True, include_lowest = False,
|
|
1402
|
-
tree_binning = False, target = None, random_state=42, spec_values = []):
|
|
1426
|
+
tree_binning = False, target = None, random_state=42, spec_values = [], fillna = -999999):
|
|
1403
1427
|
"""
|
|
1404
1428
|
通用分箱函数,支持等频或等距分箱。
|
|
1405
1429
|
|
|
@@ -1437,6 +1461,8 @@ def run_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, in
|
|
|
1437
1461
|
随机种子
|
|
1438
1462
|
spec_values : list, default []
|
|
1439
1463
|
特殊值列表
|
|
1464
|
+
fillna : scalar, default -999999
|
|
1465
|
+
`include_missing=True` 时用于承载缺失值的分箱哨兵。
|
|
1440
1466
|
|
|
1441
1467
|
Returns
|
|
1442
1468
|
-------
|
|
@@ -1481,7 +1507,8 @@ def run_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, in
|
|
|
1481
1507
|
ascending = ascending,
|
|
1482
1508
|
include_missing = include_missing,
|
|
1483
1509
|
random_state = random_state,
|
|
1484
|
-
spec_values = spec_values
|
|
1510
|
+
spec_values = spec_values,
|
|
1511
|
+
fillna = fillna)
|
|
1485
1512
|
|
|
1486
1513
|
left_sign='[' if include_lowest else '('
|
|
1487
1514
|
right_sign=']' if right else ')'
|
|
@@ -1489,8 +1516,9 @@ def run_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, in
|
|
|
1489
1516
|
|
|
1490
1517
|
rename_catlist = [i for i in range(1, len(bin_range_list) + 1)] if not include_missing else [i for i in range(0, len(bin_range_list))]
|
|
1491
1518
|
binned = binned.cat.rename_categories(rename_catlist)
|
|
1492
|
-
data
|
|
1493
|
-
|
|
1519
|
+
data = _materialize_bin_columns(
|
|
1520
|
+
data, binned, bin_range_list, bin_num_col, bin_range_col
|
|
1521
|
+
)
|
|
1494
1522
|
|
|
1495
1523
|
return data, bin_edges
|
|
1496
1524
|
|
{supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Core/Binning_Tool.pyi
RENAMED
|
@@ -44,5 +44,5 @@ class Binning:
|
|
|
44
44
|
def run(self): ...
|
|
45
45
|
def get_result(self, return_edges = True): ...
|
|
46
46
|
def chi2_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, tgt_name = None, include_missing = True, equal_freq = True, bin_colnames = ('_bin_num', '_bin_range'), ascending = True, chi2_p = 0.95, init_equi_bins = 100, fillna = -999999, spec_values = [], init_points = None): ...
|
|
47
|
-
def run_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, include_missing = True, equal_freq = True, bin_colnames = ('bin_num', 'bin_range'), ascending = False, right = True, include_lowest = False, tree_binning = False, target = None, random_state = 42, spec_values = []): ...
|
|
47
|
+
def run_binning(data, column, nbins = 10, precision = 5, min_bin_prop = 0.05, include_missing = True, equal_freq = True, bin_colnames = ('bin_num', 'bin_range'), ascending = False, right = True, include_lowest = False, tree_binning = False, target = None, random_state = 42, spec_values = [], fillna = -999999): ...
|
|
48
48
|
def super_binning(data, score, dep, nbins = 10, precision = 5, min_bin_prop = 0.05, include_missing = True, equal_freq = True, chi2_method = False, chi2_p = 0.95, init_equi_bins = 2000, fillna = -999999, spec_values = [], tree_binning = False, random_state = 42, return_edges = False, ascending = True, bin_colnames = ('_bin_num', '_bin_range')): ...
|
|
@@ -1688,6 +1688,19 @@ def parse_sql_file(sql_path:str=None,
|
|
|
1688
1688
|
|
|
1689
1689
|
|
|
1690
1690
|
|
|
1691
|
+
def _calc_woe_iv_values(data, bad_pct, good_pct, fillwoe=True, filliv=True):
|
|
1692
|
+
"""Calculate WOE and IV together with one vectorized logarithm."""
|
|
1693
|
+
if len(data[bad_pct]) > 0 and len(data[good_pct]) > 0:
|
|
1694
|
+
bad_values = data[bad_pct]
|
|
1695
|
+
good_values = data[good_pct]
|
|
1696
|
+
woe = np.log(bad_values / good_values)
|
|
1697
|
+
iv = (bad_values - good_values) * woe
|
|
1698
|
+
else:
|
|
1699
|
+
woe = 0 if fillwoe else np.nan
|
|
1700
|
+
iv = 0 if filliv else np.nan
|
|
1701
|
+
return woe, iv
|
|
1702
|
+
|
|
1703
|
+
|
|
1691
1704
|
def calc_woe(data, bad_pct, good_pct, fillwoe=True):
|
|
1692
1705
|
"""
|
|
1693
1706
|
计算WOE(Weight of Evidence)值。
|
|
@@ -1716,15 +1729,7 @@ def calc_woe(data, bad_pct, good_pct, fillwoe=True):
|
|
|
1716
1729
|
>>> calc_woe(df, 'bad_pct', 'good_pct')
|
|
1717
1730
|
"""
|
|
1718
1731
|
|
|
1719
|
-
|
|
1720
|
-
woe = np.log(data[bad_pct] / data[good_pct])
|
|
1721
|
-
else:
|
|
1722
|
-
if fillwoe:
|
|
1723
|
-
woe = 0
|
|
1724
|
-
else:
|
|
1725
|
-
woe = np.nan
|
|
1726
|
-
|
|
1727
|
-
return woe
|
|
1732
|
+
return _calc_woe_iv_values(data, bad_pct, good_pct, fillwoe=fillwoe)[0]
|
|
1728
1733
|
|
|
1729
1734
|
|
|
1730
1735
|
def calc_iv(data, bad_pct, good_pct, filliv=True):
|
|
@@ -1755,15 +1760,7 @@ def calc_iv(data, bad_pct, good_pct, filliv=True):
|
|
|
1755
1760
|
>>> calc_iv(df, 'bad_pct', 'good_pct')
|
|
1756
1761
|
"""
|
|
1757
1762
|
|
|
1758
|
-
|
|
1759
|
-
iv = (data[bad_pct] - data[good_pct]) * np.log(data[bad_pct] / data[good_pct])
|
|
1760
|
-
else:
|
|
1761
|
-
if filliv:
|
|
1762
|
-
iv = 0
|
|
1763
|
-
else:
|
|
1764
|
-
iv = np.nan
|
|
1765
|
-
|
|
1766
|
-
return iv
|
|
1763
|
+
return _calc_woe_iv_values(data, bad_pct, good_pct, filliv=filliv)[1]
|
|
1767
1764
|
|
|
1768
1765
|
|
|
1769
1766
|
def save_model(model, filename):
|
|
@@ -2413,9 +2410,13 @@ class WOEIVCalculator:
|
|
|
2413
2410
|
-------
|
|
2414
2411
|
tuple
|
|
2415
2412
|
"""
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2413
|
+
return _calc_woe_iv_values(
|
|
2414
|
+
self.data,
|
|
2415
|
+
self.bad_pct_col,
|
|
2416
|
+
self.good_pct_col,
|
|
2417
|
+
fillwoe=fillna,
|
|
2418
|
+
filliv=fillna,
|
|
2419
|
+
)
|
|
2419
2420
|
|
|
2420
2421
|
|
|
2421
2422
|
def get_feature_names(model, model_type=None):
|
{supermodelingfactory-0.5.8 → supermodelingfactory-0.5.10}/Modeling_Tool/Pipeline/_common.py
RENAMED
|
@@ -55,6 +55,12 @@ def as_list(value: Any | Iterable[Any] | None, default: list[Any] | None = None)
|
|
|
55
55
|
return list(value)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def normalize_split_values(values: pd.Series) -> pd.Series:
|
|
59
|
+
"""Normalize declared split labels for case-insensitive pipeline routing."""
|
|
60
|
+
normalized = values.astype("string").str.strip().str.lower()
|
|
61
|
+
return normalized.mask(normalized.eq(""), pd.NA)
|
|
62
|
+
|
|
63
|
+
|
|
58
64
|
def normalize_group_specs(
|
|
59
65
|
value: Any,
|
|
60
66
|
*,
|
|
@@ -17,6 +17,7 @@ from ._common import (
|
|
|
17
17
|
as_list,
|
|
18
18
|
make_dirs,
|
|
19
19
|
normalize_group_specs,
|
|
20
|
+
normalize_split_values,
|
|
20
21
|
safe_to_csv,
|
|
21
22
|
split_oot_by_flag,
|
|
22
23
|
validate_woe_fit_query_columns,
|
|
@@ -132,6 +133,7 @@ class FeatureValidationPipelineResult:
|
|
|
132
133
|
selected_features: list[str] = field(default_factory=list)
|
|
133
134
|
selection_summary: dict[str, Any] = field(default_factory=dict)
|
|
134
135
|
screening_artifact: Any | None = None
|
|
136
|
+
config_snapshot: dict[str, Any] = field(default_factory=dict)
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
class FeatureValidationPipeline:
|
|
@@ -180,6 +182,12 @@ class FeatureValidationPipeline:
|
|
|
180
182
|
incumbent_features = self._resolve_incumbent_features(work, new_features)
|
|
181
183
|
target_cols = [col for col in as_list(cfg.target_cols) if col in work.columns]
|
|
182
184
|
self._validate_input(work, new_features, incumbent_features, target_cols)
|
|
185
|
+
config_snapshot = self._build_config_snapshot(
|
|
186
|
+
new_features,
|
|
187
|
+
incumbent_features,
|
|
188
|
+
target_cols,
|
|
189
|
+
batch_mode=False,
|
|
190
|
+
)
|
|
183
191
|
|
|
184
192
|
output_dir = Path(cfg.output_dir)
|
|
185
193
|
if cfg.write_outputs or cfg.write_excel:
|
|
@@ -224,6 +232,7 @@ class FeatureValidationPipeline:
|
|
|
224
232
|
incumbent_features=incumbent_features,
|
|
225
233
|
target_cols=target_cols,
|
|
226
234
|
woe_artifacts=woe_artifacts,
|
|
235
|
+
config_snapshot=config_snapshot,
|
|
227
236
|
)
|
|
228
237
|
validation_summary = self._build_validation_summary(
|
|
229
238
|
data=combined,
|
|
@@ -270,6 +279,7 @@ class FeatureValidationPipeline:
|
|
|
270
279
|
selected_features=selected_features,
|
|
271
280
|
selection_summary=selection_summary,
|
|
272
281
|
screening_artifact=screening_artifact,
|
|
282
|
+
config_snapshot=config_snapshot,
|
|
273
283
|
)
|
|
274
284
|
|
|
275
285
|
def _resolve_input_type(self, data: pd.DataFrame | str | Path) -> str:
|
|
@@ -407,6 +417,7 @@ class FeatureValidationPipeline:
|
|
|
407
417
|
selected_features=list(result.selected_features),
|
|
408
418
|
selection_summary=dict(result.selection_summary or {}),
|
|
409
419
|
screening_artifact=result.screening_artifact,
|
|
420
|
+
config_snapshot=dict(result.config_snapshot or {}),
|
|
410
421
|
)
|
|
411
422
|
|
|
412
423
|
def _validate_batch_config(self, new_features: list[str]) -> None:
|
|
@@ -496,6 +507,10 @@ class FeatureValidationPipeline:
|
|
|
496
507
|
return self._dedupe([col for col in candidates if col and col in header_set])
|
|
497
508
|
|
|
498
509
|
def _stable_split_labels(self, base_df: pd.DataFrame, splits: dict[str, pd.DataFrame]) -> pd.Series:
|
|
510
|
+
cfg = self.config
|
|
511
|
+
if cfg.split_col and cfg.split_col in base_df.columns:
|
|
512
|
+
return normalize_split_values(base_df[cfg.split_col])
|
|
513
|
+
|
|
499
514
|
labels = pd.Series("ins", index=base_df.index, dtype=object)
|
|
500
515
|
oos_ids = set(splits.get("oos", pd.DataFrame()).get("_smf_batch_row_id", pd.Series(dtype=int)).tolist())
|
|
501
516
|
oot_ids = set(splits.get("oot", pd.DataFrame()).get("_smf_batch_row_id", pd.Series(dtype=int)).tolist())
|
|
@@ -503,6 +518,11 @@ class FeatureValidationPipeline:
|
|
|
503
518
|
row_ids = base_df["_smf_batch_row_id"]
|
|
504
519
|
labels.loc[row_ids.isin(oos_ids)] = "oos"
|
|
505
520
|
labels.loc[row_ids.isin(unique_oot_ids)] = "oot"
|
|
521
|
+
for name, frame in splits.items():
|
|
522
|
+
if name in {"ins", "oos", "oot"}:
|
|
523
|
+
continue
|
|
524
|
+
ids = set(frame.get("_smf_batch_row_id", pd.Series(dtype=int)).tolist())
|
|
525
|
+
labels.loc[row_ids.isin(ids)] = name
|
|
506
526
|
return labels
|
|
507
527
|
|
|
508
528
|
def _merge_batch_results(
|
|
@@ -554,6 +574,12 @@ class FeatureValidationPipeline:
|
|
|
554
574
|
selected_features = self._dedupe(
|
|
555
575
|
[feat for res in batch_results for feat in as_list(res.selected_features)]
|
|
556
576
|
)
|
|
577
|
+
config_snapshot = self._build_config_snapshot(
|
|
578
|
+
new_features,
|
|
579
|
+
incumbent_features,
|
|
580
|
+
target_cols,
|
|
581
|
+
batch_mode=True,
|
|
582
|
+
)
|
|
557
583
|
selection_summary: dict[str, Any] = {}
|
|
558
584
|
screening_artifact = None
|
|
559
585
|
if self.config.selection_enabled and target_cols:
|
|
@@ -561,12 +587,7 @@ class FeatureValidationPipeline:
|
|
|
561
587
|
"initial_features": list(new_features),
|
|
562
588
|
"final_features": selected_features,
|
|
563
589
|
"target_col": target_cols[0],
|
|
564
|
-
"config_snapshot":
|
|
565
|
-
"selection_enabled": True,
|
|
566
|
-
"selection_params": dict(self.config.selection_params or {}),
|
|
567
|
-
"weight_col": self.config.weight_col,
|
|
568
|
-
"batch_mode": True,
|
|
569
|
-
},
|
|
590
|
+
"config_snapshot": config_snapshot,
|
|
570
591
|
}
|
|
571
592
|
if selected_features:
|
|
572
593
|
from .screening_artifact import FeatureScreeningArtifact
|
|
@@ -619,6 +640,7 @@ class FeatureValidationPipeline:
|
|
|
619
640
|
selected_features=selected_features,
|
|
620
641
|
selection_summary=selection_summary,
|
|
621
642
|
screening_artifact=screening_artifact,
|
|
643
|
+
config_snapshot=config_snapshot,
|
|
622
644
|
)
|
|
623
645
|
|
|
624
646
|
@staticmethod
|
|
@@ -854,6 +876,30 @@ class FeatureValidationPipeline:
|
|
|
854
876
|
]
|
|
855
877
|
return pd.DataFrame(rows)
|
|
856
878
|
|
|
879
|
+
def _build_config_snapshot(
|
|
880
|
+
self,
|
|
881
|
+
new_features: list[str],
|
|
882
|
+
incumbent_features: list[str],
|
|
883
|
+
target_cols: list[str],
|
|
884
|
+
*,
|
|
885
|
+
batch_mode: bool,
|
|
886
|
+
) -> dict[str, Any]:
|
|
887
|
+
cfg = self.config
|
|
888
|
+
return {
|
|
889
|
+
"selection_enabled": bool(cfg.selection_enabled),
|
|
890
|
+
"selection_params": dict(cfg.selection_params or {}),
|
|
891
|
+
"weight_col": cfg.weight_col,
|
|
892
|
+
"target_col": target_cols[0] if target_cols else None,
|
|
893
|
+
"target_cols": list(target_cols),
|
|
894
|
+
"new_feature_cols": list(new_features),
|
|
895
|
+
"incumbent_feature_cols": list(incumbent_features),
|
|
896
|
+
"woe_engine": cfg.woe_engine,
|
|
897
|
+
"woe_fit_query": cfg.woe_fit_query,
|
|
898
|
+
"woe_params": dict(cfg.woe_params or {}),
|
|
899
|
+
"monotone_woe_params": dict(cfg.monotone_woe_params or {}),
|
|
900
|
+
"batch_mode": bool(batch_mode),
|
|
901
|
+
}
|
|
902
|
+
|
|
857
903
|
def _validate_input(
|
|
858
904
|
self,
|
|
859
905
|
data: pd.DataFrame,
|
|
@@ -864,6 +910,8 @@ class FeatureValidationPipeline:
|
|
|
864
910
|
cfg = self.config
|
|
865
911
|
missing = [col for col in [cfg.id_col, cfg.apply_time_col] if col and col not in data.columns]
|
|
866
912
|
missing += [col for col in new_features + incumbent_features + target_cols if col not in data.columns]
|
|
913
|
+
if cfg.weight_col and cfg.weight_col not in data.columns:
|
|
914
|
+
missing.append(cfg.weight_col)
|
|
867
915
|
if missing:
|
|
868
916
|
raise KeyError(f"Missing required columns: {sorted(set(missing))}")
|
|
869
917
|
if not new_features:
|
|
@@ -917,18 +965,20 @@ class FeatureValidationPipeline:
|
|
|
917
965
|
raise KeyError(f"Missing split_col {cfg.split_col!r}")
|
|
918
966
|
if sample_col in work.columns:
|
|
919
967
|
raw_split = work[sample_col]
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
raise ValueError(f"split_col {cfg.split_col!r} only supports ins/oos/oot values, got {invalid}")
|
|
925
|
-
ins = work[lower == "ins"].copy()
|
|
926
|
-
oos = work[lower == "oos"].copy()
|
|
927
|
-
oot = work[lower == "oot"].copy()
|
|
968
|
+
normalized = normalize_split_values(raw_split)
|
|
969
|
+
ins = work[normalized.eq("ins").fillna(False)].copy()
|
|
970
|
+
oos = work[normalized.eq("oos").fillna(False)].copy()
|
|
971
|
+
oot = work[normalized.eq("oot").fillna(False)].copy()
|
|
928
972
|
if len(ins) and len(oos):
|
|
929
973
|
if not len(oot):
|
|
930
974
|
oot = oos.copy()
|
|
931
|
-
|
|
975
|
+
splits = {"ins": ins, "oos": oos, "oot": oot}
|
|
976
|
+
if cfg.split_col:
|
|
977
|
+
reserved = set(splits)
|
|
978
|
+
for label in normalized.dropna().drop_duplicates().tolist():
|
|
979
|
+
if label not in reserved:
|
|
980
|
+
splits[str(label)] = work[normalized.eq(label).fillna(False)].copy()
|
|
981
|
+
return splits
|
|
932
982
|
if cfg.split_col:
|
|
933
983
|
raise ValueError(f"split_col {cfg.split_col!r} must contain non-empty ins and oos samples")
|
|
934
984
|
|
|
@@ -1251,6 +1301,7 @@ class FeatureValidationPipeline:
|
|
|
1251
1301
|
incumbent_features: list[str],
|
|
1252
1302
|
target_cols: list[str],
|
|
1253
1303
|
woe_artifacts: dict[str, Any],
|
|
1304
|
+
config_snapshot: dict[str, Any],
|
|
1254
1305
|
) -> tuple[list[str], dict[str, Any], Any | None]:
|
|
1255
1306
|
from Modeling_Tool.Feature.Feature_Screen import feature_screen
|
|
1256
1307
|
from .screening_artifact import FeatureScreeningArtifact, screen_result_to_summary
|
|
@@ -1274,24 +1325,22 @@ class FeatureValidationPipeline:
|
|
|
1274
1325
|
config=screen_cfg,
|
|
1275
1326
|
prefit_woe_engine=engine,
|
|
1276
1327
|
)
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
"selection_params": dict(cfg.selection_params or {}),
|
|
1328
|
+
selection_config_snapshot = dict(config_snapshot)
|
|
1329
|
+
selection_config_snapshot.update({
|
|
1280
1330
|
"missing_rate_threshold": screen_cfg.missing_rate_threshold,
|
|
1281
|
-
"weight_col": cfg.weight_col,
|
|
1282
1331
|
"psi_use_woe_bins": screen_cfg.psi_use_woe_bins,
|
|
1283
1332
|
"iv_use_woe_bins": screen_cfg.iv_use_woe_bins,
|
|
1284
1333
|
"corr_use_woe_bins": screen_cfg.corr_use_woe_bins,
|
|
1285
1334
|
"n_incumbent_features": len(incumbent_features),
|
|
1286
|
-
}
|
|
1335
|
+
})
|
|
1287
1336
|
selection_summary = {
|
|
1288
1337
|
"initial_features": list(new_features),
|
|
1289
1338
|
"final_features": list(result.selected_features),
|
|
1290
1339
|
"target_col": target,
|
|
1291
|
-
"config_snapshot":
|
|
1340
|
+
"config_snapshot": selection_config_snapshot,
|
|
1292
1341
|
}
|
|
1293
1342
|
selection_summary.update(screen_result_to_summary(result, new_features))
|
|
1294
|
-
selection_summary["config_snapshot"] =
|
|
1343
|
+
selection_summary["config_snapshot"] = selection_config_snapshot
|
|
1295
1344
|
artifact = FeatureScreeningArtifact.from_screen_result(
|
|
1296
1345
|
result,
|
|
1297
1346
|
initial_features=list(new_features),
|
|
@@ -1299,7 +1348,7 @@ class FeatureValidationPipeline:
|
|
|
1299
1348
|
weight_col=cfg.weight_col,
|
|
1300
1349
|
woe_artifacts=woe_artifacts,
|
|
1301
1350
|
source="fvp",
|
|
1302
|
-
config_snapshot=
|
|
1351
|
+
config_snapshot=selection_config_snapshot,
|
|
1303
1352
|
)
|
|
1304
1353
|
return list(result.selected_features), selection_summary, artifact
|
|
1305
1354
|
|
|
@@ -7,7 +7,14 @@ from typing import Any, Callable
|
|
|
7
7
|
|
|
8
8
|
import pandas as pd
|
|
9
9
|
|
|
10
|
-
from ._common import
|
|
10
|
+
from ._common import (
|
|
11
|
+
as_list,
|
|
12
|
+
make_dirs,
|
|
13
|
+
normalize_group_specs,
|
|
14
|
+
normalize_split_values,
|
|
15
|
+
safe_to_csv,
|
|
16
|
+
write_basic_excel,
|
|
17
|
+
)
|
|
11
18
|
|
|
12
19
|
_logger = logging.getLogger(__name__)
|
|
13
20
|
|
|
@@ -287,6 +294,8 @@ class ScoreComparisonPipeline:
|
|
|
287
294
|
data.loc[missing_mask, col] = pd.NA
|
|
288
295
|
elif cfg.include_missing:
|
|
289
296
|
data.loc[missing_mask, col] = "[Missing]"
|
|
297
|
+
if cfg.split_col and cfg.split_col in data.columns:
|
|
298
|
+
data[cfg.split_col] = normalize_split_values(data[cfg.split_col])
|
|
290
299
|
|
|
291
300
|
def _resolve_scores(self, data: pd.DataFrame) -> list[str]:
|
|
292
301
|
cfg = self.config
|
|
@@ -314,10 +323,9 @@ class ScoreComparisonPipeline:
|
|
|
314
323
|
if missing:
|
|
315
324
|
raise KeyError(f"Missing required columns: {missing}")
|
|
316
325
|
if cfg.split_col:
|
|
317
|
-
values = data[cfg.split_col].dropna()
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
raise ValueError(f"split_col {cfg.split_col!r} only supports ins/oos/oot values, got {invalid}")
|
|
326
|
+
values = normalize_split_values(data[cfg.split_col]).dropna()
|
|
327
|
+
if values.empty:
|
|
328
|
+
raise ValueError(f"split_col {cfg.split_col!r} must contain at least one non-empty value")
|
|
321
329
|
|
|
322
330
|
def _custom_metrics_func(self, sub_df: pd.DataFrame) -> pd.Series:
|
|
323
331
|
cfg = self.config
|
|
@@ -81,24 +81,47 @@ class FeatureScreeningArtifact:
|
|
|
81
81
|
result: Any,
|
|
82
82
|
*,
|
|
83
83
|
target_col: str | None = None,
|
|
84
|
+
weight_col: str | None = None,
|
|
84
85
|
) -> FeatureScreeningArtifact:
|
|
85
86
|
if getattr(result, "screening_artifact", None) is not None:
|
|
86
87
|
return result.screening_artifact
|
|
88
|
+
summary = dict(getattr(result, "selection_summary", {}) or {})
|
|
89
|
+
summary_snapshot = dict(summary.get("config_snapshot", {}) or {})
|
|
90
|
+
result_snapshot = dict(getattr(result, "config_snapshot", {}) or {})
|
|
91
|
+
config_snapshot = {**summary_snapshot, **result_snapshot}
|
|
87
92
|
resolved_target = target_col
|
|
88
93
|
if not resolved_target:
|
|
89
|
-
summary = getattr(result, "selection_summary", {}) or {}
|
|
90
94
|
resolved_target = summary.get("target_col")
|
|
95
|
+
if not resolved_target:
|
|
96
|
+
resolved_target = config_snapshot.get("target_col")
|
|
97
|
+
if not resolved_target:
|
|
98
|
+
target_cols = list(config_snapshot.get("target_cols") or [])
|
|
99
|
+
resolved_target = target_cols[0] if target_cols else None
|
|
91
100
|
if not resolved_target:
|
|
92
101
|
raise ValueError("target_col is required when building artifact from FVP result.")
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
selected_features = list(getattr(result, "selected_features", []) or [])
|
|
103
|
+
if not selected_features and config_snapshot.get("selection_enabled") is False:
|
|
104
|
+
selected_features = list(config_snapshot.get("new_feature_cols") or [])
|
|
105
|
+
if not summary and config_snapshot.get("selection_enabled") is False:
|
|
106
|
+
summary = {
|
|
107
|
+
"initial_features": list(selected_features),
|
|
108
|
+
"final_features": list(selected_features),
|
|
109
|
+
"target_col": str(resolved_target),
|
|
110
|
+
"selection_skipped": True,
|
|
111
|
+
"config_snapshot": config_snapshot,
|
|
112
|
+
}
|
|
113
|
+
resolved_weight_col = (
|
|
114
|
+
config_snapshot["weight_col"]
|
|
115
|
+
if "weight_col" in config_snapshot
|
|
116
|
+
else weight_col
|
|
117
|
+
)
|
|
95
118
|
return cls(
|
|
96
|
-
selected_features=
|
|
119
|
+
selected_features=selected_features,
|
|
97
120
|
selection_summary=summary,
|
|
98
121
|
woe_artifacts=getattr(result, "woe_artifacts", None),
|
|
99
122
|
source="fvp",
|
|
100
123
|
target_col=str(resolved_target),
|
|
101
|
-
weight_col=
|
|
124
|
+
weight_col=resolved_weight_col,
|
|
102
125
|
config_snapshot=config_snapshot,
|
|
103
126
|
created_at=datetime.now(timezone.utc).isoformat(),
|
|
104
127
|
)
|
|
@@ -124,4 +147,4 @@ class FeatureScreeningArtifact:
|
|
|
124
147
|
__all__ = [
|
|
125
148
|
"FeatureScreeningArtifact",
|
|
126
149
|
"screen_result_to_summary",
|
|
127
|
-
]
|
|
150
|
+
]
|