SuperModelingFactory 0.6.5__tar.gz → 0.6.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Model_Eval_Tool.py +55 -8
  2. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +1 -1
  3. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/weighted_eval_utils.py +102 -13
  4. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/_common.py +20 -0
  5. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/credit_model.py +111 -8
  6. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/field_meta.py +6 -0
  7. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/__init__.py +1 -1
  8. {supermodelingfactory-0.6.5/SuperModelingFactory.egg-info → supermodelingfactory-0.6.6}/PKG-INFO +2 -2
  9. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/README.md +1 -1
  10. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6/SuperModelingFactory.egg-info}/PKG-INFO +2 -2
  11. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/pyproject.toml +1 -1
  12. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/setup.py +1 -1
  13. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/ExcelMaster/ExcelFormatTool.py +0 -0
  14. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/ExcelMaster/ExcelMaster.py +0 -0
  15. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/ExcelMaster/Template.py +0 -0
  16. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/ExcelMaster/Utility.py +0 -0
  17. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/ExcelMaster/__init__.py +0 -0
  18. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/LICENSE +0 -0
  19. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/MANIFEST.in +0 -0
  20. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  21. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  22. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  23. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  24. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  25. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  26. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  27. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
  28. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  29. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  30. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  31. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  32. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  33. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/__init__.py +0 -0
  34. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/kDataFrame.py +0 -0
  35. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  36. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  37. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Core/utils.py +0 -0
  38. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  39. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  40. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/__init__.py +0 -0
  41. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  42. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  43. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  44. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  45. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Explainability/__init__.py +0 -0
  46. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
  47. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
  48. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
  49. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  50. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
  51. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/ODPS_Distribution_Tool.py +0 -0
  52. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/ODPS_Distribution_Tool.pyi +0 -0
  53. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
  54. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
  55. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
  56. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/Weighted_Screen.py +0 -0
  57. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Feature/__init__.py +0 -0
  58. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  59. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  60. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  61. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  62. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  63. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/LRM_Tool.py +0 -0
  64. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
  65. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Model/__init__.py +0 -0
  66. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/__init__.py +0 -0
  67. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/feature_validation.py +0 -0
  68. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  69. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
  70. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
  71. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
  72. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
  73. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  74. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
  75. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  76. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  77. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  78. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  79. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  80. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  81. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/Sample/__init__.py +0 -0
  82. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  83. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/UAT/__init__.py +0 -0
  84. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  85. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  86. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Master.py +0 -0
  87. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
  88. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
  89. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
  90. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  91. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  92. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  93. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  94. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  95. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  96. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/__init__.py +0 -0
  97. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  98. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  99. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/__init__.py +0 -0
  100. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/nan_guard.py +0 -0
  101. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/robust.py +0 -0
  102. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/_utils/sentinels.py +0 -0
  103. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  104. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  105. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/__init__.py +0 -0
  106. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  107. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Report/Report_Tool.py +0 -0
  108. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/Report/__init__.py +0 -0
  109. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
  110. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  111. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  112. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/requires.txt +0 -0
  113. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  114. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/requirements.txt +0 -0
  115. {supermodelingfactory-0.6.5 → supermodelingfactory-0.6.6}/setup.cfg +0 -0
@@ -2169,7 +2169,7 @@ class PerformanceEvaluator:
2169
2169
  dist_bins = 20, pct_bins = 10, precision = 5, min_bin_prop = 0.05,
2170
2170
  include_missing = False, equal_freq = True, chi2_method = False,
2171
2171
  init_equi_bins = 1000, chi2_p = 0.9, tree_binning = False, random_state = 42,
2172
- weight_col = None):
2172
+ weight_col = None, spec_values = None, ascending = None):
2173
2173
  """
2174
2174
  初始化性能评估器。
2175
2175
 
@@ -2225,9 +2225,29 @@ class PerformanceEvaluator:
2225
2225
  self.tree_binning = tree_binning
2226
2226
  self.random_state = random_state
2227
2227
  self.weight_col = weight_col
2228
+ # spec_values: sentinel scores (e.g. -1 for all-missing overrides)
2229
+ # that get their own evaluation bin and are excluded from quantile
2230
+ # edges and ranking metrics. ascending: None keeps every underlying
2231
+ # function's own legacy default; an explicit bool is threaded through
2232
+ # summary/gains/plot and the weighted path uniformly.
2233
+ self.spec_values = list(spec_values) if spec_values else []
2234
+ self.ascending = ascending
2228
2235
  self.datasets = {}
2229
2236
  self.dataset_weight_cols = {}
2230
2237
  self.evaluate_status = None
2238
+
2239
+ def _governed_eval_kwargs(self):
2240
+ """Kwargs to thread spec_values/ascending into underlying eval calls.
2241
+
2242
+ Nothing is passed when unset, so every legacy default stays intact
2243
+ (unweighted summary False, comparison paths True, weighted False).
2244
+ """
2245
+ kwargs = {}
2246
+ if self.spec_values:
2247
+ kwargs["spec_values"] = list(self.spec_values)
2248
+ if self.ascending is not None:
2249
+ kwargs["ascending"] = bool(self.ascending)
2250
+ return kwargs
2231
2251
 
2232
2252
  def add_dataset(self, name, data, weight_col = None, overwrite = False):
2233
2253
  """
@@ -2351,6 +2371,8 @@ class PerformanceEvaluator:
2351
2371
  scr_name,
2352
2372
  weight_col=wc,
2353
2373
  nbins=self.pct_bins,
2374
+ ascending=bool(self.ascending) if self.ascending is not None else False,
2375
+ spec_values=self.spec_values or None,
2354
2376
  )
2355
2377
  )
2356
2378
  fnl_df = pd.DataFrame(rows)
@@ -2368,15 +2390,23 @@ class PerformanceEvaluator:
2368
2390
  if scr_name is None:
2369
2391
  scr_name = "_mdl_scr"
2370
2392
  work_data[scr_name] = self.model.predict_proba(work_data.loc[:, self.feature_cols])[:, 1]
2371
- payload = {
2372
- "y_true": work_data[self.tgt_name],
2373
- "y_score": work_data[scr_name],
2374
- }
2375
2393
  resolved_weight = _weighted_eval.resolve_weights(
2376
2394
  work_data,
2377
2395
  weight_col=wc,
2378
2396
  expected_len=len(work_data),
2379
2397
  )
2398
+ y_true = work_data[self.tgt_name].to_numpy()
2399
+ y_score = work_data[scr_name].to_numpy()
2400
+ if self.spec_values:
2401
+ keep = ~pd.Series(y_score).isin(self.spec_values).to_numpy()
2402
+ y_true = y_true[keep]
2403
+ y_score = y_score[keep]
2404
+ if resolved_weight is not None:
2405
+ resolved_weight = np.asarray(resolved_weight)[keep]
2406
+ payload = {
2407
+ "y_true": y_true,
2408
+ "y_score": y_score,
2409
+ }
2380
2410
  if resolved_weight is not None:
2381
2411
  payload["sample_weight"] = resolved_weight
2382
2412
  eval_datasets[name] = payload
@@ -2445,6 +2475,21 @@ class PerformanceEvaluator:
2445
2475
  return data[self.scr_name]
2446
2476
  return self.model.predict_proba(data.loc[:, self.feature_cols])[:, 1]
2447
2477
 
2478
+ def _rank_payload(data):
2479
+ """(y_true, y_score) with special sentinel scores removed.
2480
+
2481
+ Sentinels like the all-missing override -1 carry no ordering
2482
+ information: AUC/KS/distribution figures are computed on the
2483
+ non-special rows; their bin-level accounting lives in the gains
2484
+ table via spec_values.
2485
+ """
2486
+ y_true = data[self.tgt_name]
2487
+ y_score = _get_score(data)
2488
+ if not self.spec_values:
2489
+ return y_true, y_score
2490
+ keep = ~pd.Series(np.asarray(y_score)).isin(self.spec_values).to_numpy()
2491
+ return np.asarray(y_true)[keep], np.asarray(y_score)[keep]
2492
+
2448
2493
  def _get_benchmark_bin_edges():
2449
2494
  if benchmark_dataset is None:
2450
2495
  return None
@@ -2492,9 +2537,10 @@ class PerformanceEvaluator:
2492
2537
  for name, data in dataset_dict.items():
2493
2538
  if data is None:
2494
2539
  continue
2540
+ y_true, y_score = _rank_payload(data)
2495
2541
  eval_datasets[name] = {
2496
- "y_true": data[self.tgt_name],
2497
- "y_score": _get_score(data)
2542
+ "y_true": y_true,
2543
+ "y_score": y_score
2498
2544
  }
2499
2545
 
2500
2546
  if len(eval_datasets) == 0:
@@ -2553,7 +2599,8 @@ class PerformanceEvaluator:
2553
2599
  chi2_p = self.chi2_p,
2554
2600
  retSummary = True,
2555
2601
  tree_binning = False if benchmark_bin_edges is not None else self.tree_binning,
2556
- random_state = self.random_state
2602
+ random_state = self.random_state,
2603
+ **self._governed_eval_kwargs(),
2557
2604
  )
2558
2605
  gains_res['index'] = name
2559
2606
  gains_summ_list.append(gains_res)
@@ -32,6 +32,6 @@ class GainsTableCalculator:
32
32
  def calculate(self, grp_name = None, min_data_size = 100, grp_colname = None, sync_range = True, retSummary = False, withSummary = False, wholeGroup = False, add_func = None): ...
33
33
 
34
34
  class PerformanceEvaluator:
35
- def __init__(self, tgt_name, scr_name = None, model = None, feature_cols = None, dist_bins = 20, pct_bins = 10, precision = 5, min_bin_prop = 0.05, include_missing = False, equal_freq = True, chi2_method = False, init_equi_bins = 1000, chi2_p = 0.9, tree_binning = False, random_state = 42, weight_col = None): ...
35
+ def __init__(self, tgt_name, scr_name = None, model = None, feature_cols = None, dist_bins = 20, pct_bins = 10, precision = 5, min_bin_prop = 0.05, include_missing = False, equal_freq = True, chi2_method = False, init_equi_bins = 1000, chi2_p = 0.9, tree_binning = False, random_state = 42, weight_col = None, spec_values = None, ascending = None): ...
36
36
  def add_dataset(self, name, data, weight_col = None, overwrite = False): ...
37
37
  def evaluate(self, oot_grp_name = None, min_data_size = 100, grp_colname = None, fig_save_path = None, rpt_save_path = None, to_show = False, display = True, gains_table = False, benchmark_dataset = None, weight_col = None): ...
@@ -95,10 +95,18 @@ def calc_pr(y_true, y_score, sample_weight=None):
95
95
  )
96
96
 
97
97
 
98
- def rank_bins(score, weight, nbins):
98
+ def rank_bins(score, weight, nbins, ascending=False):
99
+ """Weighted equal-frequency rank bins.
100
+
101
+ ``ascending=False`` (default, legacy) sorts scores descending — bin 1 is
102
+ the highest-score bucket. ``ascending=True`` sorts scores ascending so
103
+ bin 1 is the lowest-score bucket, matching the unweighted
104
+ ``gains_ascending=True`` convention.
105
+ """
99
106
  score = np.asarray(score, dtype=float)
100
107
  weight = np.ones(len(score), dtype=float) if weight is None else np.asarray(weight, dtype=float)
101
- order = np.lexsort((np.arange(len(score)), -score))
108
+ sort_key = score if ascending else -score
109
+ order = np.lexsort((np.arange(len(score)), sort_key))
102
110
  bins = np.empty(len(score), dtype=int)
103
111
  total = float(weight.sum()) or float(len(score)) or 1.0
104
112
  cum_weight = np.cumsum(weight[order])
@@ -108,18 +116,35 @@ def rank_bins(score, weight, nbins):
108
116
  return bins
109
117
 
110
118
 
111
- def get_gains_table(data, dep, score, nbins=10, weight_col=None, weighted_binning=None, **kwargs):
119
+ def _split_special_scores(df, score, spec_values):
120
+ """Split a frame into (normal, special) rows by exact score membership.
121
+
122
+ Special score values (e.g. the all-missing override ``-1``) are business
123
+ sentinels, not model outputs: they must not participate in quantile
124
+ binning or ranking metrics.
125
+ """
126
+ if not spec_values:
127
+ return df, None
128
+ spec_mask = df[score].isin(list(spec_values))
129
+ if not bool(spec_mask.any()):
130
+ return df, None
131
+ return df[~spec_mask], df[spec_mask]
132
+
133
+
134
+ def get_gains_table(data, dep, score, nbins=10, weight_col=None, weighted_binning=None,
135
+ ascending=False, spec_values=None, **kwargs):
112
136
  cols = [dep, score]
113
137
  if weight_col is not None and weight_col in data.columns:
114
138
  cols.append(weight_col)
115
139
  df = data[cols].copy()
140
+ df, special_df = _split_special_scores(df, score, spec_values)
116
141
  weight = resolve_weights(df, weight_col=weight_col, expected_len=len(df))
117
142
  if weight is None:
118
143
  weight = np.ones(len(df), dtype=float)
119
144
 
120
145
  y = df[dep].astype(float).to_numpy()
121
146
  s = df[score].astype(float).to_numpy()
122
- df["_bin_num"] = rank_bins(s, weight, nbins)
147
+ df["_bin_num"] = rank_bins(s, weight, nbins, ascending=ascending)
123
148
  df["_bin_range"] = df["_bin_num"]
124
149
  df["_w"] = weight
125
150
  df["_bad_w"] = weight * y
@@ -165,13 +190,56 @@ def get_gains_table(data, dep, score, nbins=10, weight_col=None, weighted_binnin
165
190
  out["KS_PER_BIN"] = (out["CUM_BAD_PCT"] - out["CUM_GOOD_PCT"]).abs()
166
191
  out["KS"] = out["KS_PER_BIN"]
167
192
  out["LIFT"] = out["AVG_BAD"] / overall_bad_rate if overall_bad_rate else np.nan
168
- out["TRUE_BAD_SHIFT"] = out["AVG_BAD"].shift(1) / out["AVG_BAD"] - 1
193
+ out["TRUE_BAD_SHIFT"] = (
194
+ (out["AVG_BAD"].shift(1) / out["AVG_BAD"] - 1)
195
+ if not ascending
196
+ else (out["AVG_BAD"] / out["AVG_BAD"].shift(1) - 1)
197
+ )
169
198
  out["RANK_ORDER_BUMP"] = out["TRUE_BAD_SHIFT"].lt(0).astype(int)
170
199
  with np.errstate(divide="ignore", invalid="ignore"):
171
200
  out["WOE"] = np.log(out["BAD_PCT_IN_EACH_BIN"] / out["GOOD_PCT_IN_EACH_BIN"])
172
201
  out["WOE"] = out["WOE"].replace([np.inf, -np.inf], 0).fillna(0)
173
202
  out["IV"] = (out["BAD_PCT_IN_EACH_BIN"] - out["GOOD_PCT_IN_EACH_BIN"]) * out["WOE"]
174
203
  out["AUC"] = safe_auc(y, s, sample_weight=weight)
204
+
205
+ if special_df is not None and len(special_df):
206
+ # Special sentinel scores (e.g. -1 for all-missing rows) get their own
207
+ # descriptive rows: never part of quantile edges, cumulative columns,
208
+ # or ranking metrics (those stay NaN by construction).
209
+ spec_weight = resolve_weights(special_df, weight_col=weight_col, expected_len=len(special_df))
210
+ if spec_weight is None:
211
+ spec_weight = np.ones(len(special_df), dtype=float)
212
+ grand_total = total_weight + float(np.sum(spec_weight))
213
+ spec_rows = []
214
+ for value, part in special_df.groupby(score, sort=True):
215
+ part_w = resolve_weights(part, weight_col=weight_col, expected_len=len(part))
216
+ if part_w is None:
217
+ part_w = np.ones(len(part), dtype=float)
218
+ part_y = part[dep].astype(float).to_numpy()
219
+ n_w = float(np.sum(part_w))
220
+ n_bad = float(np.sum(part_w * part_y))
221
+ label = f"special:{value}"
222
+ spec_rows.append(
223
+ pd.DataFrame(
224
+ {
225
+ "MIN": [value],
226
+ "MAX": [value],
227
+ "N": [n_w],
228
+ "N_RAW": [int(len(part))],
229
+ "PERF_CNT": [n_w],
230
+ "N_BAD": [n_bad],
231
+ "N_GOOD": [n_w - n_bad],
232
+ "AVG_SCORE": [float(value)],
233
+ "UNIQUE_SCORE": [1],
234
+ "PROP": [n_w / grand_total if grand_total else np.nan],
235
+ "AVG_BAD": [n_bad / n_w if n_w else np.nan],
236
+ "AVG_GOOD": [(n_w - n_bad) / n_w if n_w else np.nan],
237
+ },
238
+ index=pd.MultiIndex.from_tuples([(label, label)], names=out.index.names),
239
+ )
240
+ )
241
+ out = pd.concat([out] + spec_rows)
242
+ out["AUC"] = out["AUC"].iloc[0]
175
243
  return out
176
244
 
177
245
 
@@ -212,32 +280,53 @@ def calc_fixed_pct(y_true, y_score, sample_weight=None, **kwargs):
212
280
  return calc_equid_dist(y_true, y_score, sample_weight=sample_weight, **kwargs)
213
281
 
214
282
 
215
- def dataset_summary(name, data, tgt_name, scr_name, weight_col=None, nbins=10):
283
+ def dataset_summary(name, data, tgt_name, scr_name, weight_col=None, nbins=10,
284
+ ascending=False, spec_values=None):
216
285
  weight = resolve_weights(data, weight_col=weight_col, expected_len=len(data))
217
286
  y_true = data[tgt_name].to_numpy()
218
287
  y_score = data[scr_name].to_numpy()
219
- roc_df = calc_roc(y_true, y_score, sample_weight=weight)
220
- gains = get_gains_table(data, tgt_name, scr_name, nbins=nbins, weight_col=weight_col)
221
- return {
288
+ # Ranking metrics (AUC/KS/avgScore) are computed on non-special rows only:
289
+ # sentinel scores like -1 carry no ordering information.
290
+ rank_data, special_part = _split_special_scores(data, scr_name, spec_values)
291
+ rank_weight = resolve_weights(rank_data, weight_col=weight_col, expected_len=len(rank_data))
292
+ rank_true = rank_data[tgt_name].to_numpy()
293
+ rank_score = rank_data[scr_name].to_numpy()
294
+ roc_df = calc_roc(rank_true, rank_score, sample_weight=rank_weight)
295
+ gains = get_gains_table(
296
+ data, tgt_name, scr_name, nbins=nbins, weight_col=weight_col,
297
+ ascending=ascending, spec_values=spec_values,
298
+ )
299
+ summary = {
222
300
  "index": name,
223
301
  "dataset": name,
224
302
  "DATASET": name,
225
- "AUC": safe_auc(y_true, y_score, sample_weight=weight),
303
+ "AUC": safe_auc(rank_true, rank_score, sample_weight=rank_weight),
226
304
  "KS": float(roc_df["KS"].max()) if "KS" in roc_df else np.nan,
227
305
  "LIFT": float(gains["LIFT"].max()) if "LIFT" in gains else np.nan,
228
306
  "IV": float(gains["IV"].sum()) if "IV" in gains else np.nan,
229
307
  "N": float(np.sum(weight)) if weight is not None else float(len(data)),
230
308
  "N_RAW": int(len(data)),
231
309
  "avgTrue": safe_weighted_average(y_true, weight),
232
- "avgScore": safe_weighted_average(y_score, weight),
310
+ "avgScore": safe_weighted_average(rank_score, rank_weight),
233
311
  }
312
+ if special_part is not None:
313
+ spec_weight = resolve_weights(special_part, weight_col=weight_col, expected_len=len(special_part))
314
+ summary["N_SPECIAL"] = (
315
+ float(np.sum(spec_weight)) if spec_weight is not None else float(len(special_part))
316
+ )
317
+ summary["N_SPECIAL_RAW"] = int(len(special_part))
318
+ return summary
234
319
 
235
320
 
236
- def get_perf_summary(train=None, validation=None, oot=None, tgt_name=None, scr_name=None, weight_col=None, nbins=10, **kwargs):
321
+ def get_perf_summary(train=None, validation=None, oot=None, tgt_name=None, scr_name=None, weight_col=None, nbins=10,
322
+ ascending=False, spec_values=None, **kwargs):
237
323
  rows = []
238
324
  for name, data in (("ins", train), ("oos", validation), ("oot", oot)):
239
325
  if data is not None:
240
- rows.append(dataset_summary(name, data, tgt_name, scr_name, weight_col=weight_col, nbins=nbins))
326
+ rows.append(dataset_summary(
327
+ name, data, tgt_name, scr_name, weight_col=weight_col, nbins=nbins,
328
+ ascending=ascending, spec_values=spec_values,
329
+ ))
241
330
  return pd.DataFrame(rows)
242
331
 
243
332
 
@@ -532,6 +532,26 @@ def copy_column_length_checked(
532
532
  dst[col] = src[col].to_numpy()
533
533
 
534
534
 
535
+ def all_missing_mask(data: pd.DataFrame, feature_cols: list[str]) -> np.ndarray:
536
+ """Rows where every listed feature is missing.
537
+
538
+ Same membership test as ``Core.scoring``'s all-missing branch
539
+ (``pd.isnull(data[varlist]).sum(axis=1) == len(varlist)``), so the
540
+ pipeline's evaluation override and the production scoring API agree on
541
+ which rows count as un-scorable.
542
+ """
543
+ features = list(feature_cols or [])
544
+ if not features:
545
+ return np.zeros(len(data), dtype=bool)
546
+ absent = [col for col in features if col not in data.columns]
547
+ if absent:
548
+ raise KeyError(
549
+ f"all-missing score override needs raw feature columns "
550
+ f"{absent[:5]} which are absent from the evaluation frame."
551
+ )
552
+ return (pd.isnull(data[features]).sum(axis=1) == len(features)).to_numpy()
553
+
554
+
535
555
  def resolve_missing_oot(
536
556
  oos: pd.DataFrame,
537
557
  synthesize: bool,
@@ -10,6 +10,7 @@ import warnings
10
10
 
11
11
  from ._common import (
12
12
  add_dataset_with_optional_weight,
13
+ all_missing_mask,
13
14
  apply_woe_fit_query,
14
15
  as_list,
15
16
  copy_column_length_checked,
@@ -121,6 +122,28 @@ class CreditModelPipelineConfig:
121
122
  perf_pct_bins: int = 10
122
123
  perf_min_bin_prop: float = 0.03
123
124
 
125
+ # --- Evaluation governance (G13/G14/G15/G16) -------------------------
126
+ # eval_target_cols: extra labels evaluated against the SAME frozen model
127
+ # scores; effective label set = [target_col] + eval_target_cols (deduped).
128
+ # all_missing_score_value: rows whose raw model features are ALL missing
129
+ # get this score (e.g. -1); same test as Core.scoring's
130
+ # all_missing_spec_value, persisted in the model artifact metadata.
131
+ # special_score_values: sentinel scores (e.g. [-1]) get their own
132
+ # evaluation bin, excluded from quantile edges and ranking metrics.
133
+ # gains_ascending: None keeps each underlying path's legacy default;
134
+ # an explicit bool is threaded through summary/gains/weighted uniformly.
135
+ # NOTE (announced flip): the next minor release flips the resolved
136
+ # default of gains_ascending to True (score ascending, bin 1 = low risk).
137
+ # eval_weight_col: "inherit" (default) reuses weight_col for evaluation
138
+ # and search/backward eval weights; None evaluates unweighted even when
139
+ # training is weighted; any other string names the evaluation weight
140
+ # column explicitly.
141
+ eval_target_cols: list[str] | None = None
142
+ all_missing_score_value: float | None = None
143
+ special_score_values: list[float] | None = None
144
+ gains_ascending: bool | None = None
145
+ eval_weight_col: str | None = "inherit"
146
+
124
147
  screening_artifact: Any | None = None
125
148
  feature_validation_result: Any | None = None
126
149
  feature_selection_mode: Literal["run", "from_artifact", "skip"] = "run"
@@ -373,6 +396,20 @@ class CreditModelPipeline:
373
396
  if cfg.woe_fit_query:
374
397
  validate_woe_fit_query_columns(cfg.woe_fit_query, data.columns, context="input data")
375
398
  validate_woe_fit_query_syntax(data, cfg.woe_fit_query)
399
+ eval_targets = self._effective_eval_targets()
400
+ missing_targets = [t for t in eval_targets if t not in data.columns]
401
+ if missing_targets:
402
+ raise KeyError(
403
+ f"Missing evaluation target column(s) {missing_targets} "
404
+ f"(target_col + eval_target_cols must all exist in the input data)"
405
+ )
406
+ eval_weight = self._resolve_eval_weight_col()
407
+ if eval_weight and eval_weight != cfg.weight_col and eval_weight not in data.columns:
408
+ raise KeyError(f"Missing eval_weight_col {eval_weight!r}")
409
+ if cfg.all_missing_score_value is not None:
410
+ float(cfg.all_missing_score_value)
411
+ if cfg.special_score_values is not None:
412
+ [float(v) for v in cfg.special_score_values]
376
413
  if cfg.extra_eval_datasets:
377
414
  reserved = {"ins", "oos", "oot"}
378
415
  conflicts = sorted(set(cfg.extra_eval_datasets) & reserved)
@@ -381,8 +418,11 @@ class CreditModelPipeline:
381
418
  f"extra_eval_datasets names cannot conflict with split names {sorted(reserved)}: {conflicts}"
382
419
  )
383
420
  for name, extra_df in cfg.extra_eval_datasets.items():
384
- if cfg.target_col not in extra_df.columns:
385
- raise KeyError(f"extra_eval_datasets[{name!r}] missing target_col {cfg.target_col!r}")
421
+ extra_missing = [t for t in eval_targets if t not in extra_df.columns]
422
+ if extra_missing:
423
+ raise KeyError(
424
+ f"extra_eval_datasets[{name!r}] missing evaluation target column(s) {extra_missing}"
425
+ )
386
426
  if cfg.warm_start_enabled and cfg.warm_start_score_col and cfg.warm_start_score_col not in extra_df.columns:
387
427
  raise KeyError(
388
428
  f"extra_eval_datasets[{name!r}] missing warm_start_score_col {cfg.warm_start_score_col!r}"
@@ -475,6 +515,28 @@ class CreditModelPipeline:
475
515
  "oot_withheld": False,
476
516
  }
477
517
 
518
+ def _resolve_eval_weight_col(self) -> str | None:
519
+ """Resolve the evaluation weight column (G16).
520
+
521
+ "inherit" (default) reuses the training weight_col — the legacy
522
+ behavior; None evaluates unweighted even when training is weighted;
523
+ any other string names the evaluation weight column explicitly.
524
+ """
525
+ value = self.config.eval_weight_col
526
+ if value == "inherit":
527
+ return self.config.weight_col
528
+ return value
529
+
530
+ def _effective_eval_targets(self) -> list[str]:
531
+ """[target_col] + eval_target_cols, deduplicated, order preserved."""
532
+ cfg = self.config
533
+ targets = [cfg.target_col]
534
+ for name in as_list(cfg.eval_target_cols):
535
+ label = str(name)
536
+ if label not in targets:
537
+ targets.append(label)
538
+ return targets
539
+
478
540
  def _apply_split_governance(self, splits: dict[str, pd.DataFrame]) -> dict[str, pd.DataFrame]:
479
541
  """Remove forbidden splits from the working splits dict entirely, so
480
542
  no downstream stage can consume them even by accident."""
@@ -925,7 +987,16 @@ class CreditModelPipeline:
925
987
  features = list(selected_raw_features if source == "raw" else selected_woe_features)
926
988
  else:
927
989
  continue
928
- inputs[name] = {"source": source, "splits": splits, "features": features}
990
+ inputs[name] = {
991
+ "source": source,
992
+ "splits": splits,
993
+ "features": features,
994
+ # Raw-column view of the model's inputs, used by the
995
+ # all-missing score override (G14): the business rule is
996
+ # defined on raw features regardless of what representation
997
+ # the model consumed.
998
+ "raw_features": list(selected_raw_features if source == "woe" else features),
999
+ }
929
1000
  return inputs
930
1001
 
931
1002
  @staticmethod
@@ -1051,7 +1122,7 @@ class CreditModelPipeline:
1051
1122
  "validation_data": splits[validation_split],
1052
1123
  "test_data_dict": test_data_dict,
1053
1124
  "weight_col": cfg.weight_col,
1054
- "validation_weight_col": cfg.weight_col,
1125
+ "validation_weight_col": self._resolve_eval_weight_col(),
1055
1126
  },
1056
1127
  user_init,
1057
1128
  )
@@ -1196,7 +1267,7 @@ class CreditModelPipeline:
1196
1267
  eval_sets=eval_sets,
1197
1268
  param_grid=cfg.lr_search_param_grid,
1198
1269
  weight_col=cfg.weight_col,
1199
- eval_weight_col=cfg.weight_col,
1270
+ eval_weight_col=self._resolve_eval_weight_col(),
1200
1271
  **params,
1201
1272
  )
1202
1273
  self._lr_best_params = dict(getattr(lr, "best_params_", {}) or {})
@@ -1332,7 +1403,7 @@ class CreditModelPipeline:
1332
1403
  search_space=search_spaces[name],
1333
1404
  fit_kwargs=fit_kwargs or None,
1334
1405
  weight_col=cfg.weight_col,
1335
- eval_weight_col=cfg.weight_col,
1406
+ eval_weight_col=self._resolve_eval_weight_col(),
1336
1407
  **common,
1337
1408
  )
1338
1409
  except Exception as exc:
@@ -1357,12 +1428,16 @@ class CreditModelPipeline:
1357
1428
  model_extra = {key: df.copy() for key, df in cfg.extra_eval_datasets.items()}
1358
1429
  else:
1359
1430
  model_extra = extra_eval_splits
1431
+ eval_targets = self._effective_eval_targets()
1432
+ eval_weight = self._resolve_eval_weight_col()
1360
1433
  evaluator = PerformanceEvaluator(
1361
- tgt_name=cfg.target_col,
1434
+ tgt_name=eval_targets if len(eval_targets) > 1 else cfg.target_col,
1362
1435
  scr_name=f"pred_{name}",
1363
1436
  pct_bins=cfg.perf_pct_bins,
1364
1437
  min_bin_prop=cfg.perf_min_bin_prop,
1365
1438
  equal_freq=True,
1439
+ spec_values=cfg.special_score_values,
1440
+ ascending=cfg.gains_ascending,
1366
1441
  )
1367
1442
  allowed = self._governance.get("evaluation_splits")
1368
1443
  if allowed is not None:
@@ -1372,8 +1447,12 @@ class CreditModelPipeline:
1372
1447
  for ds_name, df in eval_splits.items():
1373
1448
  scored = df.copy()
1374
1449
  scored[f"pred_{name}"] = self._predict_model_positive(name, wrapper, scored, feature_cols)
1450
+ if cfg.all_missing_score_value is not None:
1451
+ override = all_missing_mask(scored, model_inputs[name].get("raw_features") or [])
1452
+ if override.any():
1453
+ scored.loc[override, f"pred_{name}"] = float(cfg.all_missing_score_value)
1375
1454
  nan_stats[str(ds_name)] = int((~np.isfinite(scored[f"pred_{name}"].to_numpy(dtype=float))).sum())
1376
- add_dataset_with_optional_weight(evaluator, ds_name, scored, weight_col=cfg.weight_col)
1455
+ add_dataset_with_optional_weight(evaluator, ds_name, scored, weight_col=eval_weight)
1377
1456
  self.predict_positive_nan_stats[str(name)] = nan_stats
1378
1457
  total_bad = sum(nan_stats.values())
1379
1458
  if total_bad:
@@ -1391,6 +1470,21 @@ class CreditModelPipeline:
1391
1470
  results[name] = evaluator.evaluate(to_show=False, display=False, fig_save_path=fig_save_path)
1392
1471
  return results
1393
1472
 
1473
+ def _raw_features_for_saved_model(
1474
+ self,
1475
+ model_name: str,
1476
+ model_feature_sources: dict[str, str],
1477
+ model_feature_sets: dict[str, list[str]],
1478
+ woe_artifacts: dict[str, Any],
1479
+ ) -> list[str]:
1480
+ """Raw-column view of a saved model's inputs, for the all-missing
1481
+ override metadata (the business rule is defined on raw features)."""
1482
+ features = list(model_feature_sets.get(model_name, []))
1483
+ if str(model_feature_sources.get(model_name, "woe")).lower() == "raw":
1484
+ return features
1485
+ suffix = woe_artifacts.get("woe_suffix", self.config.woe_params.get("woe_suffix", "_woe"))
1486
+ return self._woe_to_raw_features(features, suffix)
1487
+
1394
1488
  def _predict_model_positive(
1395
1489
  self,
1396
1490
  model_name: str,
@@ -1605,6 +1699,15 @@ class CreditModelPipeline:
1605
1699
  "oot_withheld": self._governance["oot_withheld"],
1606
1700
  "evaluation_splits": self._governance["evaluation_splits"],
1607
1701
  "forbidden_splits": self._governance["forbidden_splits"],
1702
+ "eval_target_cols": list(cfg.eval_target_cols) if cfg.eval_target_cols else None,
1703
+ "all_missing_score_value": cfg.all_missing_score_value,
1704
+ "all_missing_raw_features": (
1705
+ self._raw_features_for_saved_model(name, model_feature_sources, model_feature_sets, woe_artifacts)
1706
+ if cfg.all_missing_score_value is not None
1707
+ else None
1708
+ ),
1709
+ "special_score_values": list(cfg.special_score_values) if cfg.special_score_values else None,
1710
+ "gains_ascending": cfg.gains_ascending,
1608
1711
  }
1609
1712
  metrics = None
1610
1713
  if isinstance(perf_results.get(name), pd.DataFrame):
@@ -178,6 +178,11 @@ _FIELD_LABELS = {
178
178
  "backward_params": "逐步回归参数",
179
179
  "use_backward_features": "使用逐步回归特征",
180
180
  "candidate_mode": "候选模式(禁用 OOT)",
181
+ "eval_target_cols": "额外评估标签",
182
+ "all_missing_score_value": "全缺失分数覆写值",
183
+ "special_score_values": "特殊分数独立箱",
184
+ "gains_ascending": "Gains 分数升序",
185
+ "eval_weight_col": "评估权重列",
181
186
  "synthesize_missing_oot": "缺 OOT 时用 OOS 副本代替",
182
187
  "evaluation_splits": "评估 split 白名单",
183
188
  "forbidden_splits": "禁用 split(硬闸)",
@@ -364,6 +369,7 @@ _FIELD_OPTIONS = {
364
369
  "feature_selection_mode": ["run", "from_artifact", "skip"],
365
370
  "search_objective_when_no_oot": ["max_primary", "oot_gap_penalized"],
366
371
  "backward_validation_split": ["ins", "oos", "oot"],
372
+ "gains_ascending": [True, False],
367
373
  "prescore_model_type": ["lgb", "xgb", "cat", "lr"],
368
374
  "ri_model_type": ["lgb", "xgb", "cat", "lr"],
369
375
  "lr_nan_handling": ["fillna_median", "fillna_mean", "fillna_0", "raise"],
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  __author__ = "Jingkai Sun"
4
- __version__ = "0.6.5"
4
+ __version__ = "0.6.6"
5
5
 
6
6
  from ._utils import SMF_MISSING_BIN
7
7
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.6.5
3
+ Version: 0.6.6
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.5
268
+ - **Version**: 0.6.6
269
269
  - **Author**: Jingkai Sun
270
270
 
271
271
  ## 许可证
@@ -208,7 +208,7 @@ em.close_workbook()
208
208
 
209
209
  ## 版本
210
210
 
211
- - **Version**: 0.6.5
211
+ - **Version**: 0.6.6
212
212
  - **Author**: Jingkai Sun
213
213
 
214
214
  ## 许可证
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.6.5
3
+ Version: 0.6.6
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.5
268
+ - **Version**: 0.6.6
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.5"
15
+ version = "0.6.6"
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.5"),
18
+ version=os.environ.get("SMF_VERSION", "0.6.6"),
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",