SuperModelingFactory 0.5.9__tar.gz → 0.6.0__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.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Binning_Tool.py +57 -29
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Binning_Tool.pyi +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Proc_Compare.py +189 -36
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/kDataFrame.py +8 -16
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/utils.py +32 -28
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/Model_Eval_Tool.py +26 -46
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/evaluate_model.py +48 -19
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/weighted_eval_utils.py +13 -2
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Explainability/Coalition_Structure.py +5 -5
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Explainability/Model_Explainer.py +106 -26
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/Distribution_Tool.py +105 -29
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/Distribution_Tool.pyi +3 -3
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/Feature_Insights.py +82 -36
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/Feature_Screen.py +18 -4
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/PSI_Tool.py +244 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/PSI_Tool.pyi +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +181 -61
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/Weighted_Screen.py +94 -20
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/feature_validation.py +322 -96
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/field_meta.py +9 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/sample_analysis.py +124 -101
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/score_consistency_uat.py +2 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/Sample_Split.py +32 -3
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/Sample_Split.pyi +1 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +73 -50
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Adapter.py +45 -6
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Adapter.pyi +1 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Master.py +27 -14
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +30 -9
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Tool.py +271 -138
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Tool.pyi +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/PKG-INFO +2 -2
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/README.md +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/pyproject.toml +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/setup.py +1 -1
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/LICENSE +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/MANIFEST.in +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/_common.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/credit_model.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Report/__init__.py +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/requirements.txt +0 -0
- {supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/setup.cfg +0 -0
{supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/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.9 → supermodelingfactory-0.6.0}/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')): ...
|
{supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Core/Proc_Compare.py
RENAMED
|
@@ -34,6 +34,7 @@ class ProcCompareConfig:
|
|
|
34
34
|
chunk_size: int = 200_000
|
|
35
35
|
n_partitions: int = 16
|
|
36
36
|
backend: CompareBackend = "sequential"
|
|
37
|
+
compare_block_size: int = 64
|
|
37
38
|
|
|
38
39
|
numeric_tol: float = 1e-8
|
|
39
40
|
numeric_rtol: float = 0.0
|
|
@@ -107,6 +108,8 @@ class ProcCompareEngine:
|
|
|
107
108
|
raise ValueError("chunk_size must be a positive integer.")
|
|
108
109
|
if cfg.n_partitions <= 0:
|
|
109
110
|
raise ValueError("n_partitions must be a positive integer.")
|
|
111
|
+
if cfg.compare_block_size <= 0:
|
|
112
|
+
raise ValueError("compare_block_size must be a positive integer.")
|
|
110
113
|
if cfg.numeric_tol < 0 or cfg.numeric_rtol < 0 or cfg.datetime_tol_seconds < 0:
|
|
111
114
|
raise ValueError("tolerance values must be non-negative.")
|
|
112
115
|
if cfg.top_n <= 0:
|
|
@@ -294,44 +297,54 @@ class ProcCompareEngine:
|
|
|
294
297
|
mismatch_frames = []
|
|
295
298
|
mismatch_cols_by_row: dict[int, list[str]] = {}
|
|
296
299
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
common_values = common_mask.to_numpy(dtype=bool)
|
|
301
|
+
n_compared = int(common_values.sum())
|
|
302
|
+
block_size = int(self.config.compare_block_size)
|
|
303
|
+
for start in range(0, len(compare_cols), block_size):
|
|
304
|
+
block_cols = compare_cols[start : start + block_size]
|
|
305
|
+
comp = self._compare_column_block(merged, block_cols, common_values)
|
|
306
|
+
mismatch_matrix = comp["mismatch"]
|
|
307
|
+
one_null_matrix = comp["one_null"]
|
|
308
|
+
both_null_matrix = comp["both_null"]
|
|
309
|
+
diff_matrix = comp["diff"]
|
|
310
|
+
abs_diff_matrix = comp["abs_diff"]
|
|
311
|
+
left_values = comp["left_values"]
|
|
312
|
+
right_values = comp["right_values"]
|
|
313
|
+
|
|
314
|
+
for col_idx, col in enumerate(block_cols):
|
|
315
|
+
mismatch_positions = np.flatnonzero(mismatch_matrix[:, col_idx])
|
|
316
|
+
n_mismatch = int(len(mismatch_positions))
|
|
317
|
+
for idx in mismatch_positions:
|
|
306
318
|
mismatch_cols_by_row.setdefault(int(idx), []).append(col)
|
|
307
|
-
if self.config.detail_mode != "none":
|
|
308
|
-
detail = merged.
|
|
319
|
+
if n_mismatch and self.config.detail_mode != "none":
|
|
320
|
+
detail = merged.iloc[mismatch_positions][key_cols].copy()
|
|
309
321
|
detail["column"] = col
|
|
310
|
-
detail["left_value"] =
|
|
311
|
-
detail["right_value"] =
|
|
312
|
-
detail["diff"] =
|
|
313
|
-
detail["abs_diff"] =
|
|
322
|
+
detail["left_value"] = left_values[mismatch_positions, col_idx]
|
|
323
|
+
detail["right_value"] = right_values[mismatch_positions, col_idx]
|
|
324
|
+
detail["diff"] = diff_matrix[mismatch_positions, col_idx]
|
|
325
|
+
detail["abs_diff"] = abs_diff_matrix[mismatch_positions, col_idx]
|
|
314
326
|
mismatch_frames.append(detail)
|
|
315
327
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
diff_values = diff_matrix[:, col_idx]
|
|
329
|
+
abs_diff_values = abs_diff_matrix[:, col_idx]
|
|
330
|
+
diff_valid = np.isfinite(diff_values)
|
|
331
|
+
diff_sum = float(np.nansum(diff_values)) if diff_valid.any() else 0.0
|
|
332
|
+
diff_count = int(diff_valid.sum())
|
|
333
|
+
column_rows.append(
|
|
334
|
+
{
|
|
335
|
+
"column": col,
|
|
336
|
+
"n_compared": n_compared,
|
|
337
|
+
"n_equal": n_compared - n_mismatch,
|
|
338
|
+
"n_mismatch": n_mismatch,
|
|
339
|
+
"pct_mismatch": round(n_mismatch / n_compared * 100, 6) if n_compared else 0.0,
|
|
340
|
+
"n_one_side_null": int(one_null_matrix[:, col_idx].sum()),
|
|
341
|
+
"n_both_null": int(both_null_matrix[:, col_idx].sum()),
|
|
342
|
+
"mean_diff": diff_sum / diff_count if diff_count else np.nan,
|
|
343
|
+
"max_abs_diff": float(np.nanmax(abs_diff_values)) if np.isfinite(abs_diff_values).any() else np.nan,
|
|
344
|
+
"_diff_sum": diff_sum,
|
|
345
|
+
"_diff_count": diff_count,
|
|
346
|
+
}
|
|
347
|
+
)
|
|
335
348
|
|
|
336
349
|
for idx, cols in mismatch_cols_by_row.items():
|
|
337
350
|
row_summary.loc[idx, "n_cell_mismatch"] = len(cols)
|
|
@@ -375,6 +388,146 @@ class ProcCompareEngine:
|
|
|
375
388
|
duplicate_key_summary=duplicate_key_summary,
|
|
376
389
|
)
|
|
377
390
|
|
|
391
|
+
def _compare_column_block(
|
|
392
|
+
self,
|
|
393
|
+
merged: pd.DataFrame,
|
|
394
|
+
columns: list[str],
|
|
395
|
+
common_mask: np.ndarray,
|
|
396
|
+
) -> dict[str, np.ndarray]:
|
|
397
|
+
"""Compare a bounded field block with vectorized type-specific kernels."""
|
|
398
|
+
n_rows = len(merged)
|
|
399
|
+
n_cols = len(columns)
|
|
400
|
+
left_series = [self._normalize_series_missing(merged[f"{col}_left"]) for col in columns]
|
|
401
|
+
right_series = [self._normalize_series_missing(merged[f"{col}_right"]) for col in columns]
|
|
402
|
+
left_values = np.column_stack([series.to_numpy(dtype=object) for series in left_series])
|
|
403
|
+
right_values = np.column_stack([series.to_numpy(dtype=object) for series in right_series])
|
|
404
|
+
mismatch = np.zeros((n_rows, n_cols), dtype=bool)
|
|
405
|
+
one_null = np.zeros((n_rows, n_cols), dtype=bool)
|
|
406
|
+
both_null = np.zeros((n_rows, n_cols), dtype=bool)
|
|
407
|
+
diff = np.full((n_rows, n_cols), np.nan, dtype=float)
|
|
408
|
+
abs_diff = np.full((n_rows, n_cols), np.nan, dtype=float)
|
|
409
|
+
|
|
410
|
+
datetime_idx = [
|
|
411
|
+
idx
|
|
412
|
+
for idx, col in enumerate(columns)
|
|
413
|
+
if self._should_compare_datetime(col, left_series[idx], right_series[idx])
|
|
414
|
+
]
|
|
415
|
+
datetime_set = set(datetime_idx)
|
|
416
|
+
numeric_idx = [
|
|
417
|
+
idx
|
|
418
|
+
for idx in range(n_cols)
|
|
419
|
+
if idx not in datetime_set
|
|
420
|
+
and self._should_compare_numeric(left_series[idx], right_series[idx])
|
|
421
|
+
]
|
|
422
|
+
numeric_set = set(numeric_idx)
|
|
423
|
+
string_idx = [
|
|
424
|
+
idx
|
|
425
|
+
for idx in range(n_cols)
|
|
426
|
+
if idx not in datetime_set and idx not in numeric_set
|
|
427
|
+
]
|
|
428
|
+
common = common_mask[:, None]
|
|
429
|
+
|
|
430
|
+
if datetime_idx:
|
|
431
|
+
left_dt = np.column_stack(
|
|
432
|
+
[self._datetime_seconds(left_series[idx]) for idx in datetime_idx]
|
|
433
|
+
)
|
|
434
|
+
right_dt = np.column_stack(
|
|
435
|
+
[self._datetime_seconds(right_series[idx]) for idx in datetime_idx]
|
|
436
|
+
)
|
|
437
|
+
block_diff = left_dt - right_dt
|
|
438
|
+
block_abs = np.abs(block_diff)
|
|
439
|
+
left_null = ~np.isfinite(left_dt)
|
|
440
|
+
right_null = ~np.isfinite(right_dt)
|
|
441
|
+
tolerances = np.asarray(
|
|
442
|
+
[self._column_datetime_tol(columns[idx]) for idx in datetime_idx],
|
|
443
|
+
dtype=float,
|
|
444
|
+
)
|
|
445
|
+
block_one = (left_null ^ right_null) & common
|
|
446
|
+
block_both = left_null & right_null & common
|
|
447
|
+
block_mismatch = (block_abs > tolerances[None, :]) | block_one
|
|
448
|
+
if not self.config.both_null_equal:
|
|
449
|
+
block_mismatch |= block_both
|
|
450
|
+
block_mismatch &= common
|
|
451
|
+
diff[:, datetime_idx] = block_diff
|
|
452
|
+
abs_diff[:, datetime_idx] = block_abs
|
|
453
|
+
one_null[:, datetime_idx] = block_one
|
|
454
|
+
both_null[:, datetime_idx] = block_both
|
|
455
|
+
mismatch[:, datetime_idx] = block_mismatch
|
|
456
|
+
|
|
457
|
+
if numeric_idx:
|
|
458
|
+
left_num = np.column_stack(
|
|
459
|
+
[pd.to_numeric(left_series[idx], errors="coerce").to_numpy(dtype=float, na_value=np.nan) for idx in numeric_idx]
|
|
460
|
+
)
|
|
461
|
+
right_num = np.column_stack(
|
|
462
|
+
[pd.to_numeric(right_series[idx], errors="coerce").to_numpy(dtype=float, na_value=np.nan) for idx in numeric_idx]
|
|
463
|
+
)
|
|
464
|
+
block_diff = left_num - right_num
|
|
465
|
+
block_abs = np.abs(block_diff)
|
|
466
|
+
left_null = ~np.isfinite(left_num)
|
|
467
|
+
right_null = ~np.isfinite(right_num)
|
|
468
|
+
tolerances = np.asarray(
|
|
469
|
+
[self._column_numeric_tol(columns[idx]) for idx in numeric_idx],
|
|
470
|
+
dtype=float,
|
|
471
|
+
)
|
|
472
|
+
limit = tolerances[:, 0][None, :] + tolerances[:, 1][None, :] * np.abs(right_num)
|
|
473
|
+
block_one = (left_null ^ right_null) & common
|
|
474
|
+
block_both = left_null & right_null & common
|
|
475
|
+
block_mismatch = (block_abs > limit) | block_one
|
|
476
|
+
if not self.config.both_null_equal:
|
|
477
|
+
block_mismatch |= block_both
|
|
478
|
+
block_mismatch &= common
|
|
479
|
+
diff[:, numeric_idx] = block_diff
|
|
480
|
+
abs_diff[:, numeric_idx] = block_abs
|
|
481
|
+
one_null[:, numeric_idx] = block_one
|
|
482
|
+
both_null[:, numeric_idx] = block_both
|
|
483
|
+
mismatch[:, numeric_idx] = block_mismatch
|
|
484
|
+
|
|
485
|
+
if string_idx:
|
|
486
|
+
left_null = np.column_stack([left_series[idx].isna().to_numpy() for idx in string_idx])
|
|
487
|
+
right_null = np.column_stack([right_series[idx].isna().to_numpy() for idx in string_idx])
|
|
488
|
+
left_text = np.column_stack(
|
|
489
|
+
[left_series[idx].astype("string").fillna("").astype(str).to_numpy() for idx in string_idx]
|
|
490
|
+
)
|
|
491
|
+
right_text = np.column_stack(
|
|
492
|
+
[right_series[idx].astype("string").fillna("").astype(str).to_numpy() for idx in string_idx]
|
|
493
|
+
)
|
|
494
|
+
block_one = (left_null ^ right_null) & common
|
|
495
|
+
block_both = left_null & right_null & common
|
|
496
|
+
block_mismatch = (left_text != right_text) | block_one
|
|
497
|
+
if self.config.both_null_equal:
|
|
498
|
+
block_mismatch &= ~block_both
|
|
499
|
+
else:
|
|
500
|
+
block_mismatch |= block_both
|
|
501
|
+
block_mismatch &= common
|
|
502
|
+
one_null[:, string_idx] = block_one
|
|
503
|
+
both_null[:, string_idx] = block_both
|
|
504
|
+
mismatch[:, string_idx] = block_mismatch
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
"mismatch": mismatch,
|
|
508
|
+
"one_null": one_null,
|
|
509
|
+
"both_null": both_null,
|
|
510
|
+
"diff": diff,
|
|
511
|
+
"abs_diff": abs_diff,
|
|
512
|
+
"left_values": left_values,
|
|
513
|
+
"right_values": right_values,
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@staticmethod
|
|
517
|
+
def _datetime_seconds(series: pd.Series) -> np.ndarray:
|
|
518
|
+
values = ProcCompareEngine._to_datetime_series(series)
|
|
519
|
+
missing = values.isna().to_numpy()
|
|
520
|
+
result = values.astype("int64").to_numpy(dtype=float) / 1_000_000_000.0
|
|
521
|
+
result[missing] = np.nan
|
|
522
|
+
return result
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
def _to_datetime_series(series: pd.Series) -> pd.Series:
|
|
526
|
+
try:
|
|
527
|
+
return pd.to_datetime(series, errors="coerce", format="mixed")
|
|
528
|
+
except (TypeError, ValueError):
|
|
529
|
+
return pd.to_datetime(series, errors="coerce")
|
|
530
|
+
|
|
378
531
|
def _compare_series(
|
|
379
532
|
self,
|
|
380
533
|
col: str,
|
|
@@ -391,8 +544,8 @@ class ProcCompareEngine:
|
|
|
391
544
|
abs_diff = pd.Series(np.nan, index=left.index, dtype="float64")
|
|
392
545
|
|
|
393
546
|
if self._should_compare_datetime(col, left, right):
|
|
394
|
-
left_dt =
|
|
395
|
-
right_dt =
|
|
547
|
+
left_dt = self._to_datetime_series(left)
|
|
548
|
+
right_dt = self._to_datetime_series(right)
|
|
396
549
|
diff = (left_dt - right_dt).dt.total_seconds()
|
|
397
550
|
abs_diff = diff.abs()
|
|
398
551
|
tol = self._column_datetime_tol(col)
|
|
@@ -57,13 +57,7 @@ class kSeries(Series):
|
|
|
57
57
|
"""
|
|
58
58
|
Scale the model scores (for internt segment of MCI model)
|
|
59
59
|
"""
|
|
60
|
-
|
|
61
|
-
def app_func(x):
|
|
62
|
-
scores = x * 1.112
|
|
63
|
-
if (scores > 0.9999999):
|
|
64
|
-
return 0.9999999
|
|
65
|
-
return scores
|
|
66
|
-
return data.apply(app_func)
|
|
60
|
+
return (self * 1.112).clip(upper=0.9999999)
|
|
67
61
|
|
|
68
62
|
def proc_freq(self) -> pd.DataFrame:
|
|
69
63
|
"""
|
|
@@ -128,8 +122,12 @@ class kDataFrame(DataFrame):
|
|
|
128
122
|
data = self
|
|
129
123
|
else:
|
|
130
124
|
data = self.copy()
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
data[vintage_colname] = (
|
|
126
|
+
data[by]
|
|
127
|
+
.astype("string")
|
|
128
|
+
.str.extract(r"(\d{4}-\d{2})", expand=False)
|
|
129
|
+
.str.replace("-", "", regex=False)
|
|
130
|
+
)
|
|
133
131
|
if inplace:
|
|
134
132
|
self = data
|
|
135
133
|
return data
|
|
@@ -165,13 +163,7 @@ class kDataFrame(DataFrame):
|
|
|
165
163
|
"""
|
|
166
164
|
Scale the model scores (for internt segment of MCI model)
|
|
167
165
|
"""
|
|
168
|
-
|
|
169
|
-
def app_func(x):
|
|
170
|
-
scores = x * 1.112
|
|
171
|
-
if (scores > 0.9999999):
|
|
172
|
-
return 0.9999999
|
|
173
|
-
return scores
|
|
174
|
-
return data[pb_score].apply(app_func)
|
|
166
|
+
return (self[pb_score] * 1.112).clip(upper=0.9999999)
|
|
175
167
|
|
|
176
168
|
def proc_freq(self, var: str):
|
|
177
169
|
"""
|
|
@@ -342,8 +342,12 @@ def convert_to_vintage(data, vintage_colname = 'VINTAGE', by = 'TRAN_TMS', retur
|
|
|
342
342
|
>>> df = pd.DataFrame({'TRAN_TMS': ['2025-03-15 10:00:00', '2025-03-20 11:00:00']})
|
|
343
343
|
>>> convert_to_vintage(df)
|
|
344
344
|
"""
|
|
345
|
-
|
|
346
|
-
|
|
345
|
+
data[vintage_colname] = (
|
|
346
|
+
data[by]
|
|
347
|
+
.astype("string")
|
|
348
|
+
.str.extract(r"(\d{4}-\d{2})", expand=False)
|
|
349
|
+
.str.replace("-", "", regex=False)
|
|
350
|
+
)
|
|
347
351
|
|
|
348
352
|
if return_kDF:
|
|
349
353
|
return kDataFrame(data)
|
|
@@ -1052,10 +1056,9 @@ def bool_to_str(data):
|
|
|
1052
1056
|
>>> bool_to_str(df)
|
|
1053
1057
|
"""
|
|
1054
1058
|
dfc = data.copy()
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
dfc = dfc.astype({k: str})
|
|
1059
|
+
bool_cols = [col for col in data.columns if pd.api.types.is_bool_dtype(data[col])]
|
|
1060
|
+
if bool_cols:
|
|
1061
|
+
dfc[bool_cols] = dfc[bool_cols].astype(str)
|
|
1059
1062
|
return dfc
|
|
1060
1063
|
|
|
1061
1064
|
|
|
@@ -1688,6 +1691,19 @@ def parse_sql_file(sql_path:str=None,
|
|
|
1688
1691
|
|
|
1689
1692
|
|
|
1690
1693
|
|
|
1694
|
+
def _calc_woe_iv_values(data, bad_pct, good_pct, fillwoe=True, filliv=True):
|
|
1695
|
+
"""Calculate WOE and IV together with one vectorized logarithm."""
|
|
1696
|
+
if len(data[bad_pct]) > 0 and len(data[good_pct]) > 0:
|
|
1697
|
+
bad_values = data[bad_pct]
|
|
1698
|
+
good_values = data[good_pct]
|
|
1699
|
+
woe = np.log(bad_values / good_values)
|
|
1700
|
+
iv = (bad_values - good_values) * woe
|
|
1701
|
+
else:
|
|
1702
|
+
woe = 0 if fillwoe else np.nan
|
|
1703
|
+
iv = 0 if filliv else np.nan
|
|
1704
|
+
return woe, iv
|
|
1705
|
+
|
|
1706
|
+
|
|
1691
1707
|
def calc_woe(data, bad_pct, good_pct, fillwoe=True):
|
|
1692
1708
|
"""
|
|
1693
1709
|
计算WOE(Weight of Evidence)值。
|
|
@@ -1716,15 +1732,7 @@ def calc_woe(data, bad_pct, good_pct, fillwoe=True):
|
|
|
1716
1732
|
>>> calc_woe(df, 'bad_pct', 'good_pct')
|
|
1717
1733
|
"""
|
|
1718
1734
|
|
|
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
|
|
1735
|
+
return _calc_woe_iv_values(data, bad_pct, good_pct, fillwoe=fillwoe)[0]
|
|
1728
1736
|
|
|
1729
1737
|
|
|
1730
1738
|
def calc_iv(data, bad_pct, good_pct, filliv=True):
|
|
@@ -1755,15 +1763,7 @@ def calc_iv(data, bad_pct, good_pct, filliv=True):
|
|
|
1755
1763
|
>>> calc_iv(df, 'bad_pct', 'good_pct')
|
|
1756
1764
|
"""
|
|
1757
1765
|
|
|
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
|
|
1766
|
+
return _calc_woe_iv_values(data, bad_pct, good_pct, filliv=filliv)[1]
|
|
1767
1767
|
|
|
1768
1768
|
|
|
1769
1769
|
def save_model(model, filename):
|
|
@@ -2413,9 +2413,13 @@ class WOEIVCalculator:
|
|
|
2413
2413
|
-------
|
|
2414
2414
|
tuple
|
|
2415
2415
|
"""
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2416
|
+
return _calc_woe_iv_values(
|
|
2417
|
+
self.data,
|
|
2418
|
+
self.bad_pct_col,
|
|
2419
|
+
self.good_pct_col,
|
|
2420
|
+
fillwoe=fillna,
|
|
2421
|
+
filliv=fillna,
|
|
2422
|
+
)
|
|
2419
2423
|
|
|
2420
2424
|
|
|
2421
2425
|
def get_feature_names(model, model_type=None):
|
|
@@ -2669,4 +2673,4 @@ def get_feature_names_batch(models, model_type=None):
|
|
|
2669
2673
|
elif isinstance(models, list):
|
|
2670
2674
|
return [get_feature_names(model, model_type=model_type) for model in models]
|
|
2671
2675
|
else:
|
|
2672
|
-
raise TypeError("models参数应为dict或list类型")
|
|
2676
|
+
raise TypeError("models参数应为dict或list类型")
|
{supermodelingfactory-0.5.9 → supermodelingfactory-0.6.0}/Modeling_Tool/Eval/Model_Eval_Tool.py
RENAMED
|
@@ -88,51 +88,31 @@ def _get_gains_table_scr(data, score, dep, nbins = 10, precision = 5,
|
|
|
88
88
|
bin_colnames = ("_bin_num", "_bin_range"),
|
|
89
89
|
ascending = ascending)
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
lift = avg_bad / res[dep].mean()
|
|
117
|
-
prop = n / grand_total
|
|
118
|
-
|
|
119
|
-
# 返回Series
|
|
120
|
-
return pd.Series({
|
|
121
|
-
'MIN': min_val,
|
|
122
|
-
'MAX': max_val,
|
|
123
|
-
'N': n,
|
|
124
|
-
'PROP': prop,
|
|
125
|
-
'PERF_CNT': perf_cnt,
|
|
126
|
-
'AVG_SCORE': avg_score,
|
|
127
|
-
'UNIQUE_SCORE': unique_score,
|
|
128
|
-
'AVG_BAD': avg_bad,
|
|
129
|
-
'AVG_GOOD': avg_good,
|
|
130
|
-
'N_BAD': n_bad,
|
|
131
|
-
'N_GOOD': n_good,
|
|
132
|
-
'LIFT': lift
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
gains_table = res.groupby(["_bin_num", "_bin_range"], dropna=False).apply(_compute_gains_tmp)
|
|
91
|
+
res = res.copy()
|
|
92
|
+
res["_smf_bad_ind"] = res[dep].eq(1).astype(np.int64)
|
|
93
|
+
res["_smf_good_ind"] = res[dep].eq(0).astype(np.int64)
|
|
94
|
+
grouped = res.groupby(["_bin_num", "_bin_range"], dropna=False)
|
|
95
|
+
gains_table = grouped.agg(
|
|
96
|
+
MIN=(score, "min"),
|
|
97
|
+
MAX=(score, "max"),
|
|
98
|
+
N=(dep, "size"),
|
|
99
|
+
PERF_CNT=(dep, "count"),
|
|
100
|
+
AVG_SCORE=(score, "mean"),
|
|
101
|
+
UNIQUE_SCORE=(score, "nunique"),
|
|
102
|
+
N_BAD=("_smf_bad_ind", "sum"),
|
|
103
|
+
N_GOOD=("_smf_good_ind", "sum"),
|
|
104
|
+
)
|
|
105
|
+
perf_denom = gains_table["PERF_CNT"].replace(0, np.nan)
|
|
106
|
+
gains_table["PROP"] = gains_table["N"] / max(len(res), 1)
|
|
107
|
+
gains_table["AVG_BAD"] = (gains_table["N_BAD"] / perf_denom).fillna(0.0)
|
|
108
|
+
gains_table["AVG_GOOD"] = (gains_table["N_GOOD"] / perf_denom).fillna(0.0)
|
|
109
|
+
gains_table["LIFT"] = gains_table["AVG_BAD"] / res[dep].mean()
|
|
110
|
+
gains_table = gains_table[
|
|
111
|
+
[
|
|
112
|
+
"MIN", "MAX", "N", "PROP", "PERF_CNT", "AVG_SCORE",
|
|
113
|
+
"UNIQUE_SCORE", "AVG_BAD", "AVG_GOOD", "N_BAD", "N_GOOD", "LIFT",
|
|
114
|
+
]
|
|
115
|
+
]
|
|
136
116
|
|
|
137
117
|
gains_table["BAD_PCT_IN_EACH_BIN"] = gains_table["N_BAD"] / gains_table["N_BAD"].sum()
|
|
138
118
|
gains_table["GOOD_PCT_IN_EACH_BIN"] = gains_table["N_GOOD"] / gains_table["N_GOOD"].sum()
|
|
@@ -146,7 +126,7 @@ def _get_gains_table_scr(data, score, dep, nbins = 10, precision = 5,
|
|
|
146
126
|
|
|
147
127
|
|
|
148
128
|
gains_table["TRUE_BAD_SHIFT"] = (gains_table['AVG_BAD'].shift(1) / gains_table['AVG_BAD'] - 1) if not ascending else (gains_table['AVG_BAD'] / gains_table['AVG_BAD'].shift(1) - 1)
|
|
149
|
-
gains_table["RANK_ORDER_BUMP"] = gains_table["TRUE_BAD_SHIFT"].
|
|
129
|
+
gains_table["RANK_ORDER_BUMP"] = gains_table["TRUE_BAD_SHIFT"].lt(0).astype(int)
|
|
150
130
|
|
|
151
131
|
gains_table["WOE"] = calc_woe(data = gains_table, bad_pct = "BAD_PCT_IN_EACH_BIN", good_pct = "GOOD_PCT_IN_EACH_BIN")
|
|
152
132
|
gains_table["IV"] = calc_iv(data = gains_table, bad_pct = "BAD_PCT_IN_EACH_BIN", good_pct = "GOOD_PCT_IN_EACH_BIN")
|