SuperModelingFactory 0.3.14__tar.gz → 0.3.15__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 (108) hide show
  1. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/Distribution_Tool.py +34 -0
  2. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/Distribution_Tool.pyi +1 -0
  3. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/Feature_Insights.py +6 -5
  4. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/Feature_Screen.py +30 -0
  5. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +3 -3
  6. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/Weighted_Screen.py +112 -1
  7. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/feature_validation.py +17 -3
  8. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/screening_artifact.py +4 -0
  9. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/__init__.py +1 -1
  10. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/PKG-INFO +1 -1
  11. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/SuperModelingFactory.egg-info/PKG-INFO +1 -1
  12. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/pyproject.toml +1 -1
  13. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/setup.py +1 -1
  14. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/ExcelMaster/ExcelFormatTool.py +0 -0
  15. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/ExcelMaster/ExcelMaster.py +0 -0
  16. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/ExcelMaster/Template.py +0 -0
  17. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/ExcelMaster/Utility.py +0 -0
  18. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/ExcelMaster/__init__.py +0 -0
  19. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/LICENSE +0 -0
  20. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/MANIFEST.in +0 -0
  21. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  22. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  23. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  24. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  25. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  26. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  27. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  28. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
  29. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  30. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  31. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  32. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  33. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  34. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/__init__.py +0 -0
  35. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/kDataFrame.py +0 -0
  36. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  37. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  38. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Core/utils.py +0 -0
  39. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  40. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  41. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
  42. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
  43. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/__init__.py +0 -0
  44. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  45. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  46. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
  47. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  48. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  49. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Explainability/__init__.py +0 -0
  50. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  51. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
  52. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
  53. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Feature/__init__.py +0 -0
  54. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  55. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  56. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  57. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  58. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  59. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/LRM_Tool.py +0 -0
  60. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
  61. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Model/__init__.py +0 -0
  62. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/__init__.py +0 -0
  63. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/_common.py +0 -0
  64. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/credit_model.py +0 -0
  65. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  66. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
  67. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
  68. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
  69. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
  70. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  71. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  72. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  73. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  74. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  75. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  76. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  77. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/Sample/__init__.py +0 -0
  78. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  79. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/UAT/__init__.py +0 -0
  80. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  81. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  82. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Master.py +0 -0
  83. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
  84. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
  85. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
  86. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  87. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  88. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  89. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  90. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  91. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  92. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/__init__.py +0 -0
  93. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  94. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  95. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  96. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  97. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/ref_font/__init__.py +0 -0
  98. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  99. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/README.md +0 -0
  100. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Report/Report_Tool.py +0 -0
  101. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/Report/__init__.py +0 -0
  102. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
  103. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  104. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  105. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/SuperModelingFactory.egg-info/requires.txt +0 -0
  106. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  107. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/requirements.txt +0 -0
  108. {supermodelingfactory-0.3.14 → supermodelingfactory-0.3.15}/setup.cfg +0 -0
@@ -186,6 +186,40 @@ def proc_means_by_grp(data, varlist, groupby=None, spec_missing_value=None, q=No
186
186
  return means_rpt
187
187
 
188
188
 
189
+ _SCREENING_MEANS_COLS = ["attribute", "N_ALL", "N", "MISSING_RATE", "MIN", "MEAN", "MAX"]
190
+
191
+
192
+ def proc_means_for_screening(data, varlist, spec_missing_value=None, q=None):
193
+ """Build a uniform means table for IV screening reports.
194
+
195
+ ``proc_means_by_grp`` returns object stats (unique/top/freq) when numeric and
196
+ categorical columns are melted together. This helper splits by dtype so MIN,
197
+ MEAN, and MAX are always present (NaN for non-numeric features).
198
+ """
199
+ if not varlist:
200
+ return pd.DataFrame(columns=_SCREENING_MEANS_COLS)
201
+
202
+ numeric = [v for v in varlist if v in data.columns and pd.api.types.is_numeric_dtype(data[v])]
203
+ other = [v for v in varlist if v not in numeric and v in data.columns]
204
+ frames = []
205
+
206
+ if numeric:
207
+ frames.append(proc_means_by_grp(data, numeric, spec_missing_value=spec_missing_value, q=q))
208
+
209
+ for var in other:
210
+ part = proc_means_by_grp(data, [var], spec_missing_value=spec_missing_value, q=q)
211
+ for col in ("MIN", "MEAN", "MAX"):
212
+ if col not in part.columns:
213
+ part[col] = np.nan
214
+ frames.append(part)
215
+
216
+ if not frames:
217
+ return pd.DataFrame(columns=_SCREENING_MEANS_COLS)
218
+
219
+ out = pd.concat(frames, ignore_index=True)
220
+ return out[_SCREENING_MEANS_COLS]
221
+
222
+
189
223
  class DistributionShiftAnalyzer:
190
224
  """分布偏移分析器。
191
225
 
@@ -25,6 +25,7 @@ class proc_means:
25
25
  def group_sum(self): ...
26
26
  def __call__(self, q = None): ...
27
27
  def proc_means_by_grp(data, varlist, groupby = None, spec_missing_value = None, q = None): ...
28
+ def proc_means_for_screening(data, varlist, spec_missing_value = None, q = None): ...
28
29
 
29
30
  class DistributionShiftAnalyzer:
30
31
  def __init__(self, data, grp_name, benchmark_value): ...
@@ -6,7 +6,7 @@
6
6
  import pandas as pd
7
7
  from tqdm import tqdm
8
8
 
9
- from .Distribution_Tool import proc_means_by_grp
9
+ from .Distribution_Tool import proc_means_by_grp, proc_means_for_screening
10
10
  import logging
11
11
  logger = logging.getLogger(__name__)
12
12
 
@@ -208,14 +208,15 @@ class VarExtractionInsights:
208
208
  high_iv_summary = iv_info_res.query(f"IV >= {iv_cut}").round(4)
209
209
  high_iv_varlist = high_iv_summary['var'].tolist()
210
210
 
211
- means = proc_means_by_grp(data, high_iv_varlist, spec_missing_value=self.missing_rate_ref)
212
-
213
211
  if len(high_iv_varlist) == 0:
214
212
  logger.info(f"WARNING: No variable with IV >= {iv_cut}")
215
- means = means.rename(columns={"index": "attribute"})
213
+
214
+ means = proc_means_for_screening(
215
+ data, high_iv_varlist, spec_missing_value=self.missing_rate_ref,
216
+ )
216
217
 
217
218
  fnl_summary = high_iv_summary.merge(
218
- means[['attribute', 'N_ALL', 'N', 'MISSING_RATE', 'MIN', 'MEAN', 'MAX']],
219
+ means,
219
220
  left_on=['var'],
220
221
  right_on=['attribute'],
221
222
  how='left'
@@ -13,6 +13,7 @@ from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
13
13
 
14
14
  from .Weighted_Screen import (
15
15
  WeightedScreenResult,
16
+ _apply_missing_rate_stage,
16
17
  _apply_stage_keep,
17
18
  _corr_dedup_weighted,
18
19
  _legacy_unweighted_screen,
@@ -58,6 +59,8 @@ class FeatureScreenConfig:
58
59
  corr_use_woe_bins: bool = False
59
60
  corr_nan_policy: Literal["pairwise", "median_fill", "raise"] = "pairwise"
60
61
  on_empty_stage: Literal["keep_all_warn", "raise"] = "keep_all_warn"
62
+ missing_rate_threshold: float | None = None
63
+ missing_rate_ref: float | int = -999999
61
64
  woe_engine: str = "equal_freq"
62
65
  woe_fit_query: str | None = None
63
66
  woe_params: dict[str, Any] = field(
@@ -105,6 +108,8 @@ def screen_config_from_mapping(
105
108
  corr_use_woe_bins=bool(cfg.get("corr_use_woe_bins", False)),
106
109
  corr_nan_policy=str(cfg.get("corr_nan_policy", "pairwise")), # type: ignore[arg-type]
107
110
  on_empty_stage=str(cfg.get("on_empty_stage", "keep_all_warn")), # type: ignore[arg-type]
111
+ missing_rate_threshold=cfg.get("missing_rate_threshold"),
112
+ missing_rate_ref=cfg.get("missing_rate_ref", -999999),
108
113
  woe_engine=str(woe_engine or cfg.get("woe_engine", "equal_freq")),
109
114
  woe_fit_query=woe_fit_query if woe_fit_query is not None else cfg.get("woe_fit_query"),
110
115
  woe_params=dict(FeatureScreenConfig().woe_params | dict(woe_params or cfg.get("woe_params") or {})),
@@ -211,6 +216,14 @@ def _woe_bins_unweighted_screen(
211
216
  ins, oos, oot = splits["ins"], splits["oos"], splits["oot"]
212
217
  current = list(feature_cols)
213
218
  summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, None)]
219
+ current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
220
+ ins,
221
+ current,
222
+ summary_rows,
223
+ missing_rate_threshold=config.missing_rate_threshold,
224
+ missing_rate_ref=config.missing_rate_ref,
225
+ on_empty_stage=config.on_empty_stage,
226
+ )
214
227
  binner = _resolve_screening_binner(splits, feature_cols, target_col, config, prefit_woe_engine)
215
228
 
216
229
  psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
@@ -303,6 +316,8 @@ def _woe_bins_unweighted_screen(
303
316
  psi_table=psi_table,
304
317
  corr_dropped=corr_dropped,
305
318
  summary=pd.DataFrame(summary_rows),
319
+ missing_rate_table=missing_rate_table,
320
+ missing_rate_dropped=missing_rate_dropped,
306
321
  )
307
322
 
308
323
 
@@ -328,6 +343,15 @@ def _weighted_woe_bins_screen(
328
343
  y_ins = ins[target_col].astype(float).to_numpy()
329
344
  current = list(feature_cols)
330
345
  summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, weight_col)]
346
+ current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
347
+ ins,
348
+ current,
349
+ summary_rows,
350
+ missing_rate_threshold=config.missing_rate_threshold,
351
+ missing_rate_ref=config.missing_rate_ref,
352
+ weight_col=weight_col,
353
+ on_empty_stage=config.on_empty_stage,
354
+ )
331
355
 
332
356
  binner = prefit_woe_engine
333
357
  if binner is None:
@@ -438,6 +462,8 @@ def _weighted_woe_bins_screen(
438
462
  psi_table=psi_table,
439
463
  corr_dropped=corr_dropped,
440
464
  summary=pd.DataFrame(summary_rows),
465
+ missing_rate_table=missing_rate_table,
466
+ missing_rate_dropped=missing_rate_dropped,
441
467
  )
442
468
 
443
469
 
@@ -485,6 +511,8 @@ def feature_screen(
485
511
  corr_nan_policy=cfg.corr_nan_policy,
486
512
  on_empty_stage=cfg.on_empty_stage,
487
513
  prefit_woe_engine=prefit_woe_engine,
514
+ missing_rate_threshold=cfg.missing_rate_threshold,
515
+ missing_rate_ref=cfg.missing_rate_ref,
488
516
  )
489
517
 
490
518
  if use_woe_bins or prefit_woe_engine is not None:
@@ -515,6 +543,8 @@ def feature_screen(
515
543
  plot_outputs=cfg.plot_outputs,
516
544
  iv_equal_freq=cfg.iv_equal_freq,
517
545
  on_empty_stage=cfg.on_empty_stage,
546
+ missing_rate_threshold=cfg.missing_rate_threshold,
547
+ missing_rate_ref=cfg.missing_rate_ref,
518
548
  )
519
549
 
520
550
 
@@ -19,7 +19,7 @@ from .Feature_Insights import (
19
19
  VarExtractionInsights as _BaseVarExtractionInsights,
20
20
  var_corr_filter,
21
21
  )
22
- from .Distribution_Tool import proc_means_by_grp
22
+ from .Distribution_Tool import proc_means_for_screening
23
23
 
24
24
 
25
25
  def _psi_from_bins(expected_bins: pd.Series, current_bins: pd.Series, content: float) -> float:
@@ -110,9 +110,9 @@ def _screening_summary_from_engine(
110
110
  high_iv_summary = pd.DataFrame(rows).query(f"iv >= {iv_cut}").round(4)
111
111
  high_iv_varlist = high_iv_summary["var"].tolist()
112
112
  if high_iv_varlist:
113
- means = proc_means_by_grp(data, high_iv_varlist, spec_missing_value=missing_rate_ref)
113
+ means = proc_means_for_screening(data, high_iv_varlist, spec_missing_value=missing_rate_ref)
114
114
  summary = high_iv_summary.merge(
115
- means[["attribute", "N_ALL", "N", "MISSING_RATE", "MIN", "MEAN", "MAX"]],
115
+ means,
116
116
  left_on="var",
117
117
  right_on="attribute",
118
118
  how="left",
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import warnings
6
- from dataclasses import dataclass
6
+ from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
8
  from typing import Any, Literal
9
9
 
@@ -16,6 +16,9 @@ from Modeling_Tool.Core.utils import calc_iv
16
16
  _MISSING_BIN = "__MISSING__"
17
17
 
18
18
 
19
+ _MISSING_RATE_COLS = ["var", "missing_rate"]
20
+
21
+
19
22
  @dataclass
20
23
  class WeightedScreenResult:
21
24
  selected_features: list[str]
@@ -23,6 +26,12 @@ class WeightedScreenResult:
23
26
  psi_table: pd.DataFrame
24
27
  corr_dropped: pd.DataFrame
25
28
  summary: pd.DataFrame
29
+ missing_rate_table: pd.DataFrame = field(
30
+ default_factory=lambda: pd.DataFrame(columns=_MISSING_RATE_COLS),
31
+ )
32
+ missing_rate_dropped: pd.DataFrame = field(
33
+ default_factory=lambda: pd.DataFrame(columns=_MISSING_RATE_COLS),
34
+ )
26
35
 
27
36
 
28
37
  def _summary_row(stage: str, n_in: int, n_out: int, threshold: Any, weight_col: str | None) -> dict:
@@ -327,6 +336,83 @@ def _weighted_corr_for_screen(
327
336
  return _weighted_pearson_corr_matrix(X, w_ins, nan_policy=corr_nan_policy)
328
337
 
329
338
 
339
+ def _missing_rate_for_series(
340
+ series: pd.Series,
341
+ *,
342
+ w: np.ndarray | None = None,
343
+ missing_rate_ref: Any = None,
344
+ ) -> float:
345
+ values = series.copy()
346
+ if missing_rate_ref is not None:
347
+ values = values.replace(missing_rate_ref, np.nan)
348
+ if w is None:
349
+ total = len(values)
350
+ if total == 0:
351
+ return 1.0
352
+ valid = int(values.notna().sum())
353
+ return 1.0 - float(valid) / float(total)
354
+ arr = values.to_numpy(dtype=float, copy=False)
355
+ finite = np.isfinite(arr) & values.notna().to_numpy()
356
+ total_w = float(np.sum(w))
357
+ if total_w <= 0:
358
+ return 1.0
359
+ return 1.0 - float(np.sum(w[finite])) / total_w
360
+
361
+
362
+ def _apply_missing_rate_stage(
363
+ ins: pd.DataFrame,
364
+ current: list[str],
365
+ summary_rows: list[dict],
366
+ *,
367
+ missing_rate_threshold: float | None,
368
+ missing_rate_ref: Any = None,
369
+ weight_col: str | None = None,
370
+ on_empty_stage: Literal["keep_all_warn", "raise"] = "keep_all_warn",
371
+ ) -> tuple[list[str], pd.DataFrame, pd.DataFrame]:
372
+ empty = pd.DataFrame(columns=_MISSING_RATE_COLS)
373
+ if missing_rate_threshold is None:
374
+ return current, empty.copy(), empty.copy()
375
+
376
+ w = (
377
+ resolve_sample_weight(data=ins, weight_col=weight_col, expected_len=len(ins))
378
+ if weight_col is not None
379
+ else None
380
+ )
381
+ records: list[dict] = []
382
+ for var in current:
383
+ if var not in ins.columns:
384
+ continue
385
+ records.append({
386
+ "var": var,
387
+ "missing_rate": _missing_rate_for_series(
388
+ ins[var], w=w, missing_rate_ref=missing_rate_ref,
389
+ ),
390
+ })
391
+ missing_rate_table = pd.DataFrame(records) if records else empty.copy()
392
+ keep = missing_rate_table.loc[
393
+ missing_rate_table["missing_rate"] <= missing_rate_threshold, "var"
394
+ ].tolist()
395
+ missing_rate_dropped = missing_rate_table.loc[
396
+ missing_rate_table["missing_rate"] > missing_rate_threshold, _MISSING_RATE_COLS
397
+ ].copy()
398
+
399
+ n_before = len(current)
400
+ current = _apply_stage_keep(
401
+ current,
402
+ keep,
403
+ "missing_rate",
404
+ summary_rows,
405
+ on_empty_stage=on_empty_stage,
406
+ weight_col=weight_col,
407
+ threshold=missing_rate_threshold,
408
+ intersect=True,
409
+ )
410
+ summary_rows.append(
411
+ _summary_row("missing_rate", n_before, len(current), missing_rate_threshold, weight_col),
412
+ )
413
+ return current, missing_rate_table, missing_rate_dropped
414
+
415
+
330
416
  def _apply_stage_keep(
331
417
  current: list[str],
332
418
  keep: list[str],
@@ -439,12 +525,22 @@ def _legacy_unweighted_screen(
439
525
  plot_outputs: bool,
440
526
  iv_equal_freq: bool = True,
441
527
  on_empty_stage: Literal["keep_all_warn", "raise"] = "keep_all_warn",
528
+ missing_rate_threshold: float | None = None,
529
+ missing_rate_ref: Any = None,
442
530
  ) -> WeightedScreenResult:
443
531
  from Modeling_Tool import CorrelationFilter, PSICalculator, VarExtractionInsights
444
532
 
445
533
  ins, oos, oot = splits["ins"], splits["oos"], splits["oot"]
446
534
  current = list(feature_cols)
447
535
  summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, None)]
536
+ current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
537
+ ins,
538
+ current,
539
+ summary_rows,
540
+ missing_rate_threshold=missing_rate_threshold,
541
+ missing_rate_ref=missing_rate_ref,
542
+ on_empty_stage=on_empty_stage,
543
+ )
448
544
 
449
545
  psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
450
546
  if psi_enabled:
@@ -528,6 +624,8 @@ def _legacy_unweighted_screen(
528
624
  psi_table=psi_table,
529
625
  corr_dropped=corr_dropped,
530
626
  summary=pd.DataFrame(summary_rows),
627
+ missing_rate_table=missing_rate_table,
628
+ missing_rate_dropped=missing_rate_dropped,
531
629
  )
532
630
 
533
631
 
@@ -553,6 +651,8 @@ def _weighted_screen_impl(
553
651
  corr_nan_policy: Literal["pairwise", "median_fill", "raise"] = "pairwise",
554
652
  on_empty_stage: Literal["keep_all_warn", "raise"] = "keep_all_warn",
555
653
  prefit_woe_engine: Any | None = None,
654
+ missing_rate_threshold: float | None = None,
655
+ missing_rate_ref: Any = None,
556
656
  ) -> WeightedScreenResult:
557
657
  ins = splits["ins"]
558
658
  oos = splits["oos"]
@@ -561,6 +661,15 @@ def _weighted_screen_impl(
561
661
 
562
662
  current = list(feature_cols)
563
663
  summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, weight_col)]
664
+ current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
665
+ ins,
666
+ current,
667
+ summary_rows,
668
+ missing_rate_threshold=missing_rate_threshold,
669
+ missing_rate_ref=missing_rate_ref,
670
+ weight_col=weight_col,
671
+ on_empty_stage=on_empty_stage,
672
+ )
564
673
 
565
674
  # Precompute bin edges on ins for PSI / IV
566
675
  edges_cache: dict[str, list[float] | None] = {}
@@ -668,6 +777,8 @@ def _weighted_screen_impl(
668
777
  psi_table=psi_table,
669
778
  corr_dropped=corr_dropped,
670
779
  summary=pd.DataFrame(summary_rows),
780
+ missing_rate_table=missing_rate_table,
781
+ missing_rate_dropped=missing_rate_dropped,
671
782
  )
672
783
 
673
784
 
@@ -97,6 +97,7 @@ class FeatureValidationPipelineConfig:
97
97
  }
98
98
  )
99
99
 
100
+ missing_rate_threshold: float | None = None
100
101
  selection_enabled: bool = False
101
102
  selection_params: dict[str, Any] = field(default_factory=dict)
102
103
  weight_col: str | None = None
@@ -1174,6 +1175,7 @@ class FeatureValidationPipeline:
1174
1175
  params = dict(cfg.selection_params or {})
1175
1176
  corr_params = dict(cfg.corr_params or {})
1176
1177
  psi_params = dict(cfg.psi_params or {})
1178
+ missing_rate_threshold = params.get("missing_rate_threshold", cfg.missing_rate_threshold)
1177
1179
  mapping = {
1178
1180
  "psi_enabled": params.get("psi_enabled", cfg.psi_enabled),
1179
1181
  "psi_threshold": params.get("psi_threshold", 0.2),
@@ -1196,6 +1198,11 @@ class FeatureValidationPipeline:
1196
1198
  corr_params.get("max_iterations", 10),
1197
1199
  ),
1198
1200
  "corr_use_woe_bins": params.get("corr_use_woe_bins", cfg.corr_use_woe_bins),
1201
+ "missing_rate_threshold": missing_rate_threshold,
1202
+ "missing_rate_ref": params.get(
1203
+ "missing_rate_ref",
1204
+ cfg.woe_params.get("missing_ref_value", -999999),
1205
+ ),
1199
1206
  }
1200
1207
  return screen_config_from_mapping(
1201
1208
  mapping,
@@ -1238,6 +1245,7 @@ class FeatureValidationPipeline:
1238
1245
  config_snapshot = {
1239
1246
  "selection_enabled": True,
1240
1247
  "selection_params": dict(cfg.selection_params or {}),
1248
+ "missing_rate_threshold": screen_cfg.missing_rate_threshold,
1241
1249
  "weight_col": cfg.weight_col,
1242
1250
  "psi_use_woe_bins": screen_cfg.psi_use_woe_bins,
1243
1251
  "iv_use_woe_bins": screen_cfg.iv_use_woe_bins,
@@ -1673,9 +1681,15 @@ class FeatureValidationPipeline:
1673
1681
  }
1674
1682
  if selected_features:
1675
1683
  tables["selected_features"] = pd.DataFrame({"feature": selected_features})
1676
- screen_summary = (selection_summary or {}).get("screen_summary")
1677
- if isinstance(screen_summary, pd.DataFrame) and not screen_summary.empty:
1678
- tables["selection_screen_summary"] = screen_summary
1684
+ if selection_summary:
1685
+ for key in ("missing_rate", "missing_rate_dropped", "screen_summary"):
1686
+ value = selection_summary.get(key)
1687
+ if isinstance(value, pd.DataFrame) and not value.empty:
1688
+ tables[f"selection_{key}"] = value
1689
+ if "final_features" in selection_summary:
1690
+ tables["selection_final_features"] = pd.DataFrame(
1691
+ {"feature": list(selection_summary["final_features"])},
1692
+ )
1679
1693
  for name, df in distribution_summary.items():
1680
1694
  tables[f"distribution_{name}"] = df
1681
1695
  return tables
@@ -31,6 +31,10 @@ def screen_result_to_summary(
31
31
  summary["iv"] = iv
32
32
  if not result.corr_dropped.empty:
33
33
  summary["corr_dropped"] = result.corr_dropped
34
+ if not result.missing_rate_table.empty:
35
+ summary["missing_rate"] = result.missing_rate_table
36
+ if not result.missing_rate_dropped.empty:
37
+ summary["missing_rate_dropped"] = result.missing_rate_dropped
34
38
  summary["corr_features"] = list(result.selected_features)
35
39
  summary["screen_summary"] = result.summary
36
40
  summary["final_features"] = list(result.selected_features)
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  __author__ = "Jingkai Sun"
4
- __version__ = "0.3.14"
4
+ __version__ = "0.3.15"
5
5
 
6
6
  # Core module - base data structures
7
7
  from .Core import (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.3.14
3
+ Version: 0.3.15
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.3.14
3
+ Version: 0.3.15
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
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
12
12
 
13
13
  [project]
14
14
  name = "SuperModelingFactory"
15
- version = "0.3.14"
15
+ version = "0.3.15"
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.3.14"),
18
+ version=os.environ.get("SMF_VERSION", "0.3.15"),
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",