SuperModelingFactory 0.3.7__tar.gz → 0.3.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. supermodelingfactory-0.3.8/Modeling_Tool/Feature/Weighted_Screen.py +548 -0
  2. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/__init__.py +8 -0
  3. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/credit_model.py +68 -67
  4. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/__init__.py +5 -1
  5. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/PKG-INFO +1 -1
  6. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/SuperModelingFactory.egg-info/PKG-INFO +1 -1
  7. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/SuperModelingFactory.egg-info/SOURCES.txt +1 -0
  8. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/pyproject.toml +1 -1
  9. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/setup.py +1 -1
  10. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/ExcelMaster/ExcelFormatTool.py +0 -0
  11. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/ExcelMaster/ExcelMaster.py +0 -0
  12. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/ExcelMaster/Template.py +0 -0
  13. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/ExcelMaster/Utility.py +0 -0
  14. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/ExcelMaster/__init__.py +0 -0
  15. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/LICENSE +0 -0
  16. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/MANIFEST.in +0 -0
  17. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  18. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  19. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  20. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  21. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  22. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  23. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  24. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
  25. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  26. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  27. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  28. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  29. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  30. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/__init__.py +0 -0
  31. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/kDataFrame.py +0 -0
  32. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  33. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  34. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Core/utils.py +0 -0
  35. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  36. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  37. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
  38. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
  39. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/__init__.py +0 -0
  40. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  41. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  42. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
  43. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  44. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  45. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Explainability/__init__.py +0 -0
  46. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
  47. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
  48. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
  49. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  50. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
  51. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
  52. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
  53. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  54. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  55. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  56. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  57. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  58. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/LRM_Tool.py +0 -0
  59. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
  60. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Model/__init__.py +0 -0
  61. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/__init__.py +0 -0
  62. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/_common.py +0 -0
  63. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/feature_validation.py +0 -0
  64. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  65. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
  66. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
  67. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
  68. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  69. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  70. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  71. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  72. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  73. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  74. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  75. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/Sample/__init__.py +0 -0
  76. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  77. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/UAT/__init__.py +0 -0
  78. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  79. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  80. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Master.py +0 -0
  81. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
  82. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
  83. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
  84. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  85. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  86. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  87. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  88. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  89. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  90. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/__init__.py +0 -0
  91. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  92. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  93. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  94. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  95. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/ref_font/__init__.py +0 -0
  96. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  97. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/README.md +0 -0
  98. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Report/Report_Tool.py +0 -0
  99. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/Report/__init__.py +0 -0
  100. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  101. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  102. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/SuperModelingFactory.egg-info/requires.txt +0 -0
  103. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  104. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/requirements.txt +0 -0
  105. {supermodelingfactory-0.3.7 → supermodelingfactory-0.3.8}/setup.cfg +0 -0
@@ -0,0 +1,548 @@
1
+ """Weighted feature screening: PSI -> IV -> correlation dedup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight, weighted_rate
13
+ from Modeling_Tool.Core.utils import calc_iv
14
+
15
+ _MISSING_BIN = "__MISSING__"
16
+
17
+
18
+ @dataclass
19
+ class WeightedScreenResult:
20
+ selected_features: list[str]
21
+ iv_table: pd.DataFrame
22
+ psi_table: pd.DataFrame
23
+ corr_dropped: pd.DataFrame
24
+ summary: pd.DataFrame
25
+
26
+
27
+ def _summary_row(stage: str, n_in: int, n_out: int, threshold: Any, weight_col: str | None) -> dict:
28
+ return {
29
+ "stage": stage,
30
+ "n_in": n_in,
31
+ "n_out": n_out,
32
+ "threshold": threshold,
33
+ "weight_col": weight_col,
34
+ }
35
+
36
+
37
+ def _resolve_splits(data: pd.DataFrame, split_col: str) -> dict[str, pd.DataFrame]:
38
+ if split_col not in data.columns:
39
+ raise KeyError(f"Missing split_col {split_col!r}")
40
+ ins = data[data[split_col] == "ins"].copy()
41
+ oos = data[data[split_col] == "oos"].copy()
42
+ oot = data[data[split_col] == "oot"].copy()
43
+ if len(ins) == 0:
44
+ raise ValueError("No rows with split_col == 'ins'.")
45
+ return {"ins": ins, "oos": oos, "oot": oot}
46
+
47
+
48
+ def _max_nbins(n_obs: int, nbins: int, min_bin_prop: float) -> int:
49
+ if min_bin_prop <= 0 or n_obs <= 0:
50
+ return max(1, nbins)
51
+ return max(1, min(nbins, int(1 / min_bin_prop)))
52
+
53
+
54
+ def _weighted_equal_freq_edges(
55
+ x: np.ndarray,
56
+ w: np.ndarray,
57
+ n_bins: int,
58
+ min_bin_prop: float,
59
+ precision: int = 5,
60
+ ) -> list[float] | None:
61
+ mask = np.isfinite(x)
62
+ xv = x[mask].astype(float)
63
+ wv = w[mask].astype(float)
64
+ if xv.size == 0 or wv.sum() <= 0:
65
+ return None
66
+
67
+ n_bins = _max_nbins(xv.size, n_bins, min_bin_prop)
68
+ order = np.argsort(xv)
69
+ xv = xv[order]
70
+ wv = wv[order]
71
+ cumw = np.cumsum(wv)
72
+ total_w = float(cumw[-1])
73
+ targets = [total_w * i / n_bins for i in range(1, n_bins)]
74
+
75
+ edges: list[float] = []
76
+ for target in targets:
77
+ idx = int(np.searchsorted(cumw, target, side="right"))
78
+ idx = min(max(idx, 1), xv.size) - 1
79
+ edges.append(round(float(xv[idx]), precision))
80
+
81
+ uniq = sorted(set(edges))
82
+ if not uniq:
83
+ return [-np.inf, np.inf]
84
+ return [-np.inf, *uniq, np.inf]
85
+
86
+
87
+ def _assign_bins(x: np.ndarray, edges: list[float]) -> np.ndarray:
88
+ out = np.empty(len(x), dtype=object)
89
+ missing = ~np.isfinite(x)
90
+ out[missing] = _MISSING_BIN
91
+ if edges is None:
92
+ out[~missing] = "all"
93
+ return out
94
+ valid = x[~missing].astype(float)
95
+ # pd.cut for consistent interval labels
96
+ cats = pd.cut(valid, bins=edges, include_lowest=True, duplicates="drop")
97
+ out[~missing] = np.asarray(cats.astype(str))
98
+ return out
99
+
100
+
101
+ def _weighted_bin_distribution(
102
+ bins: np.ndarray,
103
+ w: np.ndarray,
104
+ content: float,
105
+ ) -> pd.Series:
106
+ df = pd.DataFrame({"bin": bins, "w": w})
107
+ dist = df.groupby("bin", dropna=False)["w"].sum()
108
+ total = float(dist.sum()) or 1.0
109
+ dist = dist / total
110
+ return dist.reindex(dist.index.union([_MISSING_BIN]), fill_value=0.0).clip(lower=content)
111
+
112
+
113
+ def _psi_from_distributions(expected: pd.Series, actual: pd.Series, content: float) -> float:
114
+ all_bins = expected.index.union(actual.index)
115
+ e = expected.reindex(all_bins, fill_value=content).astype(float)
116
+ a = actual.reindex(all_bins, fill_value=content).astype(float)
117
+ e = e.clip(lower=content)
118
+ a = a.clip(lower=content)
119
+ return float(((a - e) * np.log(a / e)).sum())
120
+
121
+
122
+ def _weighted_iv_for_var(
123
+ x: np.ndarray,
124
+ y: np.ndarray,
125
+ w: np.ndarray,
126
+ n_bins: int,
127
+ min_bin_prop: float,
128
+ precision: int,
129
+ ) -> tuple[float, int, float]:
130
+ edges = _weighted_equal_freq_edges(x, w, n_bins, min_bin_prop, precision=precision)
131
+ bins = _assign_bins(x, edges)
132
+ total_bad = float(np.sum(w * y))
133
+ total_good = float(np.sum(w * (1.0 - y)))
134
+ if total_bad <= 0 or total_good <= 0:
135
+ missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
136
+ return 0.0, 0, missing_rate
137
+
138
+ rows = []
139
+ for b in pd.unique(bins):
140
+ m = bins == b
141
+ bad_w = float(np.sum(w[m] * y[m]))
142
+ good_w = float(np.sum(w[m] * (1.0 - y[m])))
143
+ rows.append({
144
+ "bad_pct": bad_w / total_bad,
145
+ "good_pct": good_w / total_good,
146
+ })
147
+ if not rows:
148
+ missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
149
+ return 0.0, 0, missing_rate
150
+
151
+ stats = pd.DataFrame(rows)
152
+ iv = float(calc_iv(stats, "bad_pct", "good_pct").sum())
153
+ missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
154
+ return iv, len(rows), missing_rate
155
+
156
+
157
+ def _weighted_pearson_corr_matrix(X: np.ndarray, w: np.ndarray) -> np.ndarray:
158
+ if X.shape[1] == 0:
159
+ return np.empty((0, 0))
160
+ w = np.asarray(w, dtype=float)
161
+ w_sum = w.sum()
162
+ if w_sum <= 0:
163
+ return np.corrcoef(X, rowvar=False)
164
+ w_norm = w / w_sum
165
+ mean = np.average(X, axis=0, weights=w_norm)
166
+ Xc = X - mean
167
+ cov = np.cov(Xc, rowvar=False, aweights=w, ddof=0)
168
+ std = np.sqrt(np.diag(cov))
169
+ std[std == 0] = np.nan
170
+ corr = cov / np.outer(std, std)
171
+ return np.nan_to_num(corr, nan=0.0)
172
+
173
+
174
+ def _high_corr_pairs(varlist: list[str], corr: np.ndarray, threshold: float) -> pd.DataFrame:
175
+ rows = []
176
+ for i in range(len(varlist)):
177
+ for j in range(i + 1, len(varlist)):
178
+ c = corr[i, j]
179
+ if abs(c) > threshold:
180
+ rows.append({"var_a": varlist[i], "var_b": varlist[j], "corr": float(c)})
181
+ return pd.DataFrame(rows)
182
+
183
+
184
+ def _corr_dedup_weighted(
185
+ varlist: list[str],
186
+ corr: np.ndarray,
187
+ iv_map: dict[str, float],
188
+ threshold: float,
189
+ max_iterations: int,
190
+ ) -> tuple[list[str], pd.DataFrame]:
191
+ dropped_rows: list[dict] = []
192
+ current = list(varlist)
193
+ idx = {v: i for i, v in enumerate(varlist)}
194
+
195
+ for _ in range(max_iterations):
196
+ if len(current) <= 1:
197
+ break
198
+ sub_idx = [idx[v] for v in current]
199
+ sub_corr = corr[np.ix_(sub_idx, sub_idx)]
200
+ pairs = _high_corr_pairs(current, sub_corr, threshold)
201
+ if pairs.empty:
202
+ break
203
+
204
+ remove: set[str] = set()
205
+ for _, row in pairs.iterrows():
206
+ v1, v2 = row["var_a"], row["var_b"]
207
+ if v1 in remove or v2 in remove:
208
+ continue
209
+ iv1 = iv_map.get(v1, 0.0)
210
+ iv2 = iv_map.get(v2, 0.0)
211
+ if iv1 >= iv2:
212
+ kept, dropped = v1, v2
213
+ else:
214
+ kept, dropped = v2, v1
215
+ remove.add(dropped)
216
+ dropped_rows.append({
217
+ "var_a": v1,
218
+ "var_b": v2,
219
+ "corr": row["corr"],
220
+ "iv_a": iv1,
221
+ "iv_b": iv2,
222
+ "kept": kept,
223
+ "dropped": dropped,
224
+ })
225
+
226
+ if not remove:
227
+ break
228
+ current = [v for v in current if v not in remove]
229
+
230
+ return current, pd.DataFrame(dropped_rows)
231
+
232
+
233
+ def _legacy_unweighted_screen(
234
+ splits: dict[str, pd.DataFrame],
235
+ feature_cols: list[str],
236
+ target_col: str,
237
+ *,
238
+ psi_enabled: bool,
239
+ psi_threshold: float,
240
+ psi_compare_splits: list[str],
241
+ iv_enabled: bool,
242
+ iv_threshold: float,
243
+ iv_bins: int,
244
+ iv_min_bin_prop: float,
245
+ corr_enabled: bool,
246
+ corr_threshold: float,
247
+ corr_max_iterations: int,
248
+ psi_buckets: int,
249
+ plot_path: str | None,
250
+ plot_outputs: bool,
251
+ iv_equal_freq: bool = True,
252
+ ) -> WeightedScreenResult:
253
+ from Modeling_Tool import CorrelationFilter, PSICalculator, VarExtractionInsights
254
+
255
+ ins, oos, oot = splits["ins"], splits["oos"], splits["oot"]
256
+ current = list(feature_cols)
257
+ summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, None)]
258
+
259
+ psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
260
+ if psi_enabled:
261
+ psi_frames = []
262
+ if "oos" in psi_compare_splits and len(oos) > 0:
263
+ psi_oos = PSICalculator(buckets=psi_buckets).calculate(
264
+ expected_df=ins, current_data=oos, varlist=current,
265
+ )
266
+ psi_oos = psi_oos.rename(columns={"psi": "psi_ins_oos"})[["var", "psi_ins_oos"]]
267
+ psi_frames.append(psi_oos)
268
+ if "oot" in psi_compare_splits and len(oot) > 0:
269
+ psi_oot = PSICalculator(buckets=psi_buckets).calculate(
270
+ expected_df=ins, current_data=oot, varlist=current,
271
+ )
272
+ psi_oot = psi_oot.rename(columns={"psi": "psi_ins_oot"})[["var", "psi_ins_oot"]]
273
+ psi_frames.append(psi_oot)
274
+
275
+ if psi_frames:
276
+ psi_table = psi_frames[0]
277
+ for frame in psi_frames[1:]:
278
+ psi_table = psi_table.merge(frame, on="var", how="outer")
279
+ for col in ("psi_ins_oos", "psi_ins_oot"):
280
+ if col not in psi_table.columns:
281
+ psi_table[col] = np.nan
282
+ compare_cols = [c for c in ("psi_ins_oos", "psi_ins_oot") if c in psi_table.columns]
283
+ psi_table["psi_max"] = psi_table[compare_cols].max(axis=1, skipna=True)
284
+ keep = psi_table.loc[psi_table["psi_max"] < psi_threshold, "var"].tolist()
285
+ n_before = len(current)
286
+ current = keep or current
287
+ summary_rows.append(_summary_row("psi", n_before, len(current), psi_threshold, None))
288
+
289
+ iv_table = pd.DataFrame(columns=["var", "iv_weighted", "n_bins", "missing_rate"])
290
+ if iv_enabled:
291
+ vi = VarExtractionInsights(
292
+ data=ins,
293
+ dep=target_col,
294
+ plot_path=plot_path or "",
295
+ nbins=iv_bins,
296
+ equal_freq=iv_equal_freq,
297
+ min_bin_prop=iv_min_bin_prop,
298
+ )
299
+ iv = vi.get_var_analysis_report(data=ins, varlist=current, dep=target_col, iv_cut=0.0)
300
+ if plot_path and plot_outputs and current:
301
+ Path(plot_path, "overall").mkdir(parents=True, exist_ok=True)
302
+ plot_data = ins.copy()
303
+ plot_data["_smf_plot_group"] = "overall"
304
+ vi.plot_woe(
305
+ data=plot_data,
306
+ varlist=current,
307
+ plot_group="_smf_plot_group",
308
+ plot_dirname="overall",
309
+ plot_path=plot_path,
310
+ )
311
+ iv_table = iv.rename(columns={"iv": "iv_weighted"})[["var", "iv_weighted", "n_bins", "missing_rate"]]
312
+ keep = iv.loc[iv["iv"] >= iv_threshold, "var"].tolist()
313
+ n_before = len(current)
314
+ current = keep or current
315
+ summary_rows.append(_summary_row("iv", n_before, len(current), iv_threshold, None))
316
+
317
+ corr_dropped = pd.DataFrame(columns=["var_a", "var_b", "corr", "iv_a", "iv_b", "kept", "dropped"])
318
+ if corr_enabled and len(current) > 1:
319
+ cf = CorrelationFilter(
320
+ data=ins[current + [target_col]],
321
+ dep=target_col,
322
+ corr_cutpoint=corr_threshold,
323
+ )
324
+ n_before = len(current)
325
+ current = cf.remove_highly_correlated(current, max_iterations=corr_max_iterations)
326
+ summary_rows.append(_summary_row("corr", n_before, len(current), corr_threshold, None))
327
+
328
+ summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, None))
329
+ return WeightedScreenResult(
330
+ selected_features=list(current),
331
+ iv_table=iv_table,
332
+ psi_table=psi_table,
333
+ corr_dropped=corr_dropped,
334
+ summary=pd.DataFrame(summary_rows),
335
+ )
336
+
337
+
338
+ def _weighted_screen_impl(
339
+ splits: dict[str, pd.DataFrame],
340
+ feature_cols: list[str],
341
+ target_col: str,
342
+ weight_col: str,
343
+ *,
344
+ psi_enabled: bool,
345
+ psi_threshold: float,
346
+ psi_compare_splits: list[str],
347
+ iv_enabled: bool,
348
+ iv_threshold: float,
349
+ iv_bins: int,
350
+ min_bin_prop: float,
351
+ corr_enabled: bool,
352
+ corr_threshold: float,
353
+ corr_max_iterations: int,
354
+ content: float,
355
+ precision: int,
356
+ ) -> WeightedScreenResult:
357
+ ins = splits["ins"]
358
+ oos = splits["oos"]
359
+ oot = splits["oot"]
360
+ w_ins = resolve_sample_weight(data=ins, weight_col=weight_col, expected_len=len(ins))
361
+
362
+ current = list(feature_cols)
363
+ summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, weight_col)]
364
+
365
+ # Precompute bin edges on ins for PSI / IV
366
+ edges_cache: dict[str, list[float] | None] = {}
367
+ y_ins = ins[target_col].astype(float).to_numpy()
368
+
369
+ psi_records: list[dict] = []
370
+ if psi_enabled:
371
+ for var in feature_cols:
372
+ if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
373
+ continue
374
+ x_ins = ins[var].to_numpy(dtype=float)
375
+ if var not in edges_cache:
376
+ edges_cache[var] = _weighted_equal_freq_edges(
377
+ x_ins, w_ins, iv_bins, min_bin_prop, precision=precision,
378
+ )
379
+ edges = edges_cache[var]
380
+ bins_ins = _assign_bins(x_ins, edges) if edges else _assign_bins(x_ins, None)
381
+ exp_dist = _weighted_bin_distribution(bins_ins, w_ins, content)
382
+
383
+ row: dict[str, Any] = {"var": var, "psi_ins_oos": np.nan, "psi_ins_oot": np.nan}
384
+ if "oos" in psi_compare_splits and len(oos) > 0:
385
+ w_oos = resolve_sample_weight(data=oos, weight_col=weight_col, expected_len=len(oos))
386
+ bins_oos = _assign_bins(oos[var].to_numpy(dtype=float), edges)
387
+ act = _weighted_bin_distribution(bins_oos, w_oos, content)
388
+ row["psi_ins_oos"] = _psi_from_distributions(exp_dist, act, content)
389
+ if "oot" in psi_compare_splits and len(oot) > 0:
390
+ w_oot = resolve_sample_weight(data=oot, weight_col=weight_col, expected_len=len(oot))
391
+ bins_oot = _assign_bins(oot[var].to_numpy(dtype=float), edges)
392
+ act = _weighted_bin_distribution(bins_oot, w_oot, content)
393
+ row["psi_ins_oot"] = _psi_from_distributions(exp_dist, act, content)
394
+ psi_records.append(row)
395
+
396
+ psi_table = pd.DataFrame(psi_records) if psi_records else pd.DataFrame(
397
+ columns=["var", "psi_ins_oos", "psi_ins_oot"],
398
+ )
399
+ if not psi_table.empty:
400
+ compare_cols = [c for c in ("psi_ins_oos", "psi_ins_oot") if c in psi_table.columns]
401
+ psi_table["psi_max"] = psi_table[compare_cols].max(axis=1, skipna=True)
402
+ keep = psi_table.loc[psi_table["psi_max"] < psi_threshold, "var"].tolist()
403
+ n_before = len(current)
404
+ current = [v for v in current if v in keep] or current
405
+ summary_rows.append(_summary_row("psi", n_before, len(current), psi_threshold, weight_col))
406
+ else:
407
+ psi_table["psi_max"] = pd.Series(dtype=float)
408
+ else:
409
+ psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
410
+
411
+ iv_records: list[dict] = []
412
+ if iv_enabled:
413
+ for var in current:
414
+ if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
415
+ continue
416
+ iv_val, n_b, miss = _weighted_iv_for_var(
417
+ ins[var].to_numpy(dtype=float),
418
+ y_ins,
419
+ w_ins,
420
+ iv_bins,
421
+ min_bin_prop,
422
+ precision,
423
+ )
424
+ iv_records.append({
425
+ "var": var,
426
+ "iv_weighted": iv_val,
427
+ "n_bins": n_b,
428
+ "missing_rate": miss,
429
+ })
430
+ iv_table = pd.DataFrame(iv_records) if iv_records else pd.DataFrame(
431
+ columns=["var", "iv_weighted", "n_bins", "missing_rate"],
432
+ )
433
+ if not iv_table.empty:
434
+ keep = iv_table.loc[iv_table["iv_weighted"] >= iv_threshold, "var"].tolist()
435
+ n_before = len(current)
436
+ current = [v for v in current if v in keep] or current
437
+ summary_rows.append(_summary_row("iv", n_before, len(current), iv_threshold, weight_col))
438
+ else:
439
+ iv_table = pd.DataFrame(columns=["var", "iv_weighted", "n_bins", "missing_rate"])
440
+
441
+ corr_dropped = pd.DataFrame(columns=["var_a", "var_b", "corr", "iv_a", "iv_b", "kept", "dropped"])
442
+ if corr_enabled and len(current) > 1:
443
+ X = ins[current].astype(float).to_numpy()
444
+ corr = _weighted_pearson_corr_matrix(X, w_ins)
445
+ iv_map = dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
446
+ n_before = len(current)
447
+ current, corr_dropped = _corr_dedup_weighted(
448
+ current, corr, iv_map, corr_threshold, corr_max_iterations,
449
+ )
450
+ summary_rows.append(_summary_row("corr", n_before, len(current), corr_threshold, weight_col))
451
+
452
+ summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, weight_col))
453
+ return WeightedScreenResult(
454
+ selected_features=list(current),
455
+ iv_table=iv_table,
456
+ psi_table=psi_table,
457
+ corr_dropped=corr_dropped,
458
+ summary=pd.DataFrame(summary_rows),
459
+ )
460
+
461
+
462
+ def weighted_feature_screen(
463
+ data: pd.DataFrame,
464
+ feature_cols: list[str],
465
+ target_col: str,
466
+ split_col: str,
467
+ weight_col: str | None = None,
468
+ psi_enabled: bool = True,
469
+ psi_threshold: float = 0.2,
470
+ psi_compare_splits: list[str] | None = None,
471
+ iv_enabled: bool = True,
472
+ iv_threshold: float = 0.02,
473
+ corr_enabled: bool = True,
474
+ corr_threshold: float = 0.75,
475
+ iv_bins: int = 10,
476
+ min_bin_prop: float = 0.05,
477
+ iv_min_bin_prop: float | None = None,
478
+ corr_max_iterations: int = 10,
479
+ content: float = 1e-6,
480
+ precision: int = 5,
481
+ psi_buckets: int | None = None,
482
+ plot_path: str | None = None,
483
+ plot_outputs: bool = False,
484
+ iv_equal_freq: bool = True,
485
+ ) -> WeightedScreenResult:
486
+ """Run PSI -> IV -> correlation screening with optional sample weights.
487
+
488
+ Bin edges are fit on ``ins`` only. When ``weight_col`` is None the legacy
489
+ unweighted tools are used for backward-compatible results with
490
+ ``CreditModelPipeline`` (IV uses tree binning via ``VarExtractionInsights``).
491
+ Weighted screening uses equal-frequency bins on weighted quantiles.
492
+ """
493
+ if iv_min_bin_prop is None:
494
+ iv_min_bin_prop = min_bin_prop
495
+ if psi_buckets is None:
496
+ psi_buckets = iv_bins
497
+ if psi_compare_splits is None:
498
+ psi_compare_splits = ["oos", "oot"]
499
+
500
+ exclude = {target_col, split_col}
501
+ if weight_col:
502
+ exclude.add(weight_col)
503
+ features = [c for c in feature_cols if c not in exclude and c in data.columns]
504
+
505
+ splits = _resolve_splits(data, split_col)
506
+
507
+ if weight_col is None:
508
+ return _legacy_unweighted_screen(
509
+ splits,
510
+ features,
511
+ target_col,
512
+ psi_enabled=psi_enabled,
513
+ psi_threshold=psi_threshold,
514
+ psi_compare_splits=list(psi_compare_splits),
515
+ iv_enabled=iv_enabled,
516
+ iv_threshold=iv_threshold,
517
+ iv_bins=iv_bins,
518
+ iv_min_bin_prop=iv_min_bin_prop,
519
+ corr_enabled=corr_enabled,
520
+ corr_threshold=corr_threshold,
521
+ corr_max_iterations=corr_max_iterations,
522
+ psi_buckets=psi_buckets,
523
+ plot_path=plot_path,
524
+ plot_outputs=plot_outputs,
525
+ iv_equal_freq=iv_equal_freq,
526
+ )
527
+
528
+ if weight_col not in data.columns:
529
+ raise KeyError(f"Missing weight_col {weight_col!r}")
530
+
531
+ return _weighted_screen_impl(
532
+ splits,
533
+ features,
534
+ target_col,
535
+ weight_col,
536
+ psi_enabled=psi_enabled,
537
+ psi_threshold=psi_threshold,
538
+ psi_compare_splits=list(psi_compare_splits),
539
+ iv_enabled=iv_enabled,
540
+ iv_threshold=iv_threshold,
541
+ iv_bins=iv_bins,
542
+ min_bin_prop=min_bin_prop,
543
+ corr_enabled=corr_enabled,
544
+ corr_threshold=corr_threshold,
545
+ corr_max_iterations=corr_max_iterations,
546
+ content=content,
547
+ precision=precision,
548
+ )
@@ -25,6 +25,11 @@ from .WOE_Engine_Feature_Patch import (
25
25
  CorrelationFilter,
26
26
  )
27
27
 
28
+ from .Weighted_Screen import (
29
+ WeightedScreenResult,
30
+ weighted_feature_screen,
31
+ )
32
+
28
33
  __all__ = [
29
34
  # Distribution_Tool
30
35
  'proc_means', 'proc_means_by_grp', 'get_distribution_shift',
@@ -37,4 +42,7 @@ __all__ = [
37
42
  # PSI_Tool
38
43
  'PSICalculator', 'calculate_psi', 'calculate_within_psi',
39
44
  'calculate_psi_within_dataset', 'calculate_multivar_psi_two_sets',
45
+
46
+ # Weighted_Screen
47
+ 'WeightedScreenResult', 'weighted_feature_screen',
40
48
  ]
@@ -45,10 +45,12 @@ class CreditModelPipelineConfig:
45
45
  default_factory=lambda: {
46
46
  "psi_enabled": True,
47
47
  "psi_threshold": 0.2,
48
+ "psi_compare_splits": ["oos"],
48
49
  "iv_enabled": True,
49
50
  "iv_threshold": 0.02,
50
51
  "corr_enabled": True,
51
52
  "corr_threshold": 0.75,
53
+ "corr_max_iterations": 10,
52
54
  }
53
55
  )
54
56
  woe_engine: str = "equal_freq"
@@ -427,79 +429,78 @@ class CreditModelPipeline:
427
429
  splits: dict[str, pd.DataFrame],
428
430
  feature_cols: list[str],
429
431
  ) -> tuple[dict[str, Any], list[str]]:
430
- from Modeling_Tool import CorrelationFilter, PSICalculator, VarExtractionInsights
432
+ from Modeling_Tool.Feature.Weighted_Screen import weighted_feature_screen
431
433
 
432
434
  cfg = self.config
433
435
  fs_cfg = cfg.feature_selection
434
- ins, oos = splits["ins"], splits["oos"]
435
- current = list(feature_cols)
436
- summary: dict[str, Any] = {"initial_features": list(feature_cols)}
437
-
438
- if fs_cfg.get("psi_enabled", True):
439
- try:
440
- psi = PSICalculator(buckets=int(fs_cfg.get("psi_buckets", 10))).calculate(
441
- expected_df=ins,
442
- current_data=oos,
443
- varlist=current,
444
- )
445
- keep = psi.loc[psi["psi"] < float(fs_cfg.get("psi_threshold", 0.2)), "var"].tolist()
446
- current = keep or current
447
- summary["psi"] = psi
448
- except Exception as exc:
449
- summary["psi_error"] = repr(exc)
450
-
436
+ split_col = "_smf_split"
437
+ screen_frames = []
438
+ for name, df in splits.items():
439
+ part = df.copy()
440
+ part[split_col] = name
441
+ screen_frames.append(part)
442
+ screen_data = pd.concat(screen_frames, ignore_index=True)
443
+
444
+ plot_path = None
451
445
  if fs_cfg.get("iv_enabled", True):
452
- try:
453
- plot_path = str(Path(cfg.output_dir) / "figs" / "var_analysis")
454
- make_dirs(plot_path)
455
- vi = VarExtractionInsights(
456
- data=ins,
457
- dep=cfg.target_col,
458
- plot_path=plot_path,
459
- nbins=int(fs_cfg.get("iv_nbins", 10)),
460
- equal_freq=bool(fs_cfg.get("iv_equal_freq", True)),
461
- min_bin_prop=float(fs_cfg.get("iv_min_bin_prop", 0.05)),
462
- )
463
- iv = vi.get_var_analysis_report(
464
- data=ins,
465
- varlist=current,
466
- dep=cfg.target_col,
467
- iv_cut=0.0,
468
- )
469
- if cfg.write_outputs and cfg.plot_outputs and current:
470
- make_dirs(Path(plot_path) / "overall")
471
- plot_data = ins.copy()
472
- plot_data["_smf_plot_group"] = "overall"
473
- vi.plot_woe(
474
- data=plot_data,
475
- varlist=current,
476
- plot_group="_smf_plot_group",
477
- plot_dirname="overall",
478
- plot_path=plot_path,
479
- )
480
- keep = iv.loc[iv["iv"] >= float(fs_cfg.get("iv_threshold", 0.02)), "var"].tolist()
481
- current = keep or current
482
- summary["iv"] = iv
483
- except Exception as exc:
484
- summary["iv_error"] = repr(exc)
446
+ plot_path = str(Path(cfg.output_dir) / "figs" / "var_analysis")
447
+ if cfg.write_outputs and cfg.plot_outputs:
448
+ make_dirs(plot_path, Path(plot_path) / "overall")
485
449
 
486
- if fs_cfg.get("corr_enabled", True) and len(current) > 1:
487
- try:
488
- corr = CorrelationFilter(
489
- data=ins[current + [cfg.target_col]],
490
- dep=cfg.target_col,
491
- corr_cutpoint=float(fs_cfg.get("corr_threshold", 0.75)),
492
- )
493
- current = corr.remove_highly_correlated(
494
- current,
495
- max_iterations=int(fs_cfg.get("corr_max_iterations", 10)),
496
- )
497
- summary["corr_features"] = list(current)
498
- except Exception as exc:
499
- summary["corr_error"] = repr(exc)
450
+ summary: dict[str, Any] = {"initial_features": list(feature_cols)}
451
+ try:
452
+ result = weighted_feature_screen(
453
+ data=screen_data,
454
+ feature_cols=feature_cols,
455
+ target_col=cfg.target_col,
456
+ split_col=split_col,
457
+ weight_col=cfg.weight_col,
458
+ psi_enabled=bool(fs_cfg.get("psi_enabled", True)),
459
+ psi_threshold=float(fs_cfg.get("psi_threshold", 0.2)),
460
+ psi_compare_splits=list(fs_cfg.get("psi_compare_splits", ["oos"])),
461
+ iv_enabled=bool(fs_cfg.get("iv_enabled", True)),
462
+ iv_threshold=float(fs_cfg.get("iv_threshold", 0.02)),
463
+ corr_enabled=bool(fs_cfg.get("corr_enabled", True)),
464
+ corr_threshold=float(fs_cfg.get("corr_threshold", 0.75)),
465
+ iv_bins=int(fs_cfg.get("iv_nbins", 10)),
466
+ iv_min_bin_prop=float(fs_cfg.get("iv_min_bin_prop", 0.05)),
467
+ corr_max_iterations=int(fs_cfg.get("corr_max_iterations", 10)),
468
+ psi_buckets=int(fs_cfg.get("psi_buckets", fs_cfg.get("iv_nbins", 10))),
469
+ plot_path=plot_path,
470
+ plot_outputs=bool(cfg.write_outputs and cfg.plot_outputs),
471
+ iv_equal_freq=bool(fs_cfg.get("iv_equal_freq", True)),
472
+ )
473
+ summary = self._screen_result_to_summary(result, feature_cols)
474
+ return summary, list(result.selected_features)
475
+ except Exception as exc:
476
+ summary["error"] = repr(exc)
477
+ summary["final_features"] = list(feature_cols)
478
+ return summary, list(feature_cols)
500
479
 
501
- summary["final_features"] = list(current)
502
- return summary, list(current)
480
+ def _screen_result_to_summary(
481
+ self,
482
+ result: Any,
483
+ initial_features: list[str],
484
+ ) -> dict[str, Any]:
485
+ summary: dict[str, Any] = {"initial_features": list(initial_features)}
486
+ if not result.psi_table.empty:
487
+ psi = result.psi_table.copy()
488
+ if "psi_ins_oos" in psi.columns:
489
+ psi["psi"] = psi["psi_ins_oos"]
490
+ elif "psi_max" in psi.columns:
491
+ psi["psi"] = psi["psi_max"]
492
+ summary["psi"] = psi
493
+ if not result.iv_table.empty:
494
+ iv = result.iv_table.copy()
495
+ if "iv_weighted" in iv.columns:
496
+ iv = iv.rename(columns={"iv_weighted": "iv"})
497
+ summary["iv"] = iv
498
+ if not result.corr_dropped.empty:
499
+ summary["corr_dropped"] = result.corr_dropped
500
+ summary["corr_features"] = list(result.selected_features)
501
+ summary["screen_summary"] = result.summary
502
+ summary["final_features"] = list(result.selected_features)
503
+ return summary
503
504
 
504
505
  def _transform_extra_eval_datasets(
505
506
  self,
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  __author__ = "Jingkai Sun"
4
- __version__ = "0.3.7"
4
+ __version__ = "0.3.8"
5
5
 
6
6
  # Core module - base data structures
7
7
  from .Core import (
@@ -87,6 +87,8 @@ from .Feature import (
87
87
  CorrelationFilter,
88
88
  PSICalculator,
89
89
  calculate_psi_within_dataset,
90
+ WeightedScreenResult,
91
+ weighted_feature_screen,
90
92
  )
91
93
 
92
94
  _MODEL_EXPORTS = frozenset({
@@ -228,4 +230,6 @@ __all__ = [
228
230
  'CorrelationFilter',
229
231
  'PSICalculator',
230
232
  'calculate_psi_within_dataset',
233
+ 'WeightedScreenResult',
234
+ 'weighted_feature_screen',
231
235
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.
5
5
  Home-page: https://github.com/Kyle-J-Sun/SuperModelingFactory
6
6
  Author: Kyle Sun
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.
5
5
  Home-page: https://github.com/Kyle-J-Sun/SuperModelingFactory
6
6
  Author: Kyle Sun
@@ -46,6 +46,7 @@ Modeling_Tool/Feature/Feature_Insights.pyi
46
46
  Modeling_Tool/Feature/PSI_Tool.py
47
47
  Modeling_Tool/Feature/PSI_Tool.pyi
48
48
  Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py
49
+ Modeling_Tool/Feature/Weighted_Screen.py
49
50
  Modeling_Tool/Feature/__init__.py
50
51
  Modeling_Tool/Model/Backward_Tool.py
51
52
  Modeling_Tool/Model/Backward_Tool.pyi
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
12
12
 
13
13
  [project]
14
14
  name = "SuperModelingFactory"
15
- version = "0.3.7"
15
+ version = "0.3.8"
16
16
  description = "Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting."
17
17
  readme = "README.md"
18
18
  requires-python = ">=3.10"
@@ -15,7 +15,7 @@ def _read(path: str) -> str:
15
15
 
16
16
  setup(
17
17
  name="SuperModelingFactory",
18
- version=os.environ.get("SMF_VERSION", "0.3.7"),
18
+ version=os.environ.get("SMF_VERSION", "0.3.8"),
19
19
  description="Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.",
20
20
  long_description=_read("README.md"),
21
21
  long_description_content_type="text/markdown",