SuperModelingFactory 0.2.0__py3-none-any.whl
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.
- ExcelMaster/ExcelFormatTool.py +487 -0
- ExcelMaster/ExcelMaster.py +917 -0
- ExcelMaster/Template.py +525 -0
- ExcelMaster/Utility.py +233 -0
- ExcelMaster/__init__.py +3 -0
- Modeling_Tool/Core/Binning_Tool.py +1608 -0
- Modeling_Tool/Core/Binning_Tool.pyi +48 -0
- Modeling_Tool/Core/Check_DuckDB_Compatibility.py +835 -0
- Modeling_Tool/Core/Json_Data_Converter.py +621 -0
- Modeling_Tool/Core/Model_Registry_Tool.py +199 -0
- Modeling_Tool/Core/ODPS_Tool.py +284 -0
- Modeling_Tool/Core/Slope_Tool.py +356 -0
- Modeling_Tool/Core/Slope_Tool.pyi +31 -0
- Modeling_Tool/Core/XOR_Encryptor.py +207 -0
- Modeling_Tool/Core/XOR_Encryptor.pyi +26 -0
- Modeling_Tool/Core/__init__.py +99 -0
- Modeling_Tool/Core/kDataFrame.py +228 -0
- Modeling_Tool/Core/kDataFrame.pyi +43 -0
- Modeling_Tool/Core/sample_weight_utils.py +77 -0
- Modeling_Tool/Core/utils.py +2672 -0
- Modeling_Tool/Eval/Evaluation_Tool.py +1452 -0
- Modeling_Tool/Eval/Evaluation_Tool.pyi +47 -0
- Modeling_Tool/Eval/Model_Eval_Tool.py +2548 -0
- Modeling_Tool/Eval/Model_Eval_Tool.pyi +37 -0
- Modeling_Tool/Eval/__init__.py +54 -0
- Modeling_Tool/Eval/evaluate_model.py +2008 -0
- Modeling_Tool/Eval/evaluate_model.pyi +50 -0
- Modeling_Tool/Eval/weighted_eval_utils.py +326 -0
- Modeling_Tool/Explainability/Coalition_Structure.py +305 -0
- Modeling_Tool/Explainability/Model_Explainer.py +743 -0
- Modeling_Tool/Explainability/__init__.py +24 -0
- Modeling_Tool/Feature/Distribution_Tool.py +509 -0
- Modeling_Tool/Feature/Distribution_Tool.pyi +42 -0
- Modeling_Tool/Feature/Feature_Insights.py +762 -0
- Modeling_Tool/Feature/Feature_Insights.pyi +33 -0
- Modeling_Tool/Feature/PSI_Tool.py +1195 -0
- Modeling_Tool/Feature/PSI_Tool.pyi +29 -0
- Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +355 -0
- Modeling_Tool/Feature/__init__.py +40 -0
- Modeling_Tool/Model/Backward_Tool.py +778 -0
- Modeling_Tool/Model/Backward_Tool.pyi +45 -0
- Modeling_Tool/Model/GBM_Search_Tool.py +251 -0
- Modeling_Tool/Model/GBM_Tool.py +1610 -0
- Modeling_Tool/Model/GBM_Tool.pyi +90 -0
- Modeling_Tool/Model/LRM_Tool.py +1198 -0
- Modeling_Tool/Model/LRM_Tool.pyi +47 -0
- Modeling_Tool/Model/__init__.py +61 -0
- Modeling_Tool/Sample/Distribution_Adaptation.py +131 -0
- Modeling_Tool/Sample/Distribution_Adaptation.pyi +30 -0
- Modeling_Tool/Sample/Reject_Infer.py +413 -0
- Modeling_Tool/Sample/Reject_Infer.pyi +43 -0
- Modeling_Tool/Sample/Sample_Split.py +520 -0
- Modeling_Tool/Sample/Sample_Split.pyi +43 -0
- Modeling_Tool/Sample/__init__.py +31 -0
- Modeling_Tool/UAT/UAT_Consistency_Checker.py +1180 -0
- Modeling_Tool/UAT/__init__.py +19 -0
- Modeling_Tool/WOE/WOE_Adapter.py +204 -0
- Modeling_Tool/WOE/WOE_Adapter.pyi +21 -0
- Modeling_Tool/WOE/WOE_Master.py +491 -0
- Modeling_Tool/WOE/WOE_Master.pyi +40 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.py +3324 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +71 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.py +919 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.pyi +46 -0
- Modeling_Tool/WOE/WOE_Report_Builder.py +214 -0
- Modeling_Tool/WOE/WOE_Report_Builder.pyi +24 -0
- Modeling_Tool/WOE/WOE_Tool.py +1094 -0
- Modeling_Tool/WOE/WOE_Tool.pyi +41 -0
- Modeling_Tool/WOE/__init__.py +86 -0
- Modeling_Tool/WOE/plot_woe_tool.py +290 -0
- Modeling_Tool/WOE/plot_woe_tool.pyi +25 -0
- Modeling_Tool/__init__.py +176 -0
- Report/Report_Tool.py +380 -0
- Report/__init__.py +3 -0
- supermodelingfactory-0.2.0.dist-info/METADATA +268 -0
- supermodelingfactory-0.2.0.dist-info/RECORD +79 -0
- supermodelingfactory-0.2.0.dist-info/WHEEL +5 -0
- supermodelingfactory-0.2.0.dist-info/licenses/LICENSE +102 -0
- supermodelingfactory-0.2.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Modeling_Tool.Feature.PSI_Tool
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2026 Kyle Sun <github.com/Kyle-J-Sun>. All rights reserved.
|
|
5
|
+
# SuperModelingFactory — Licensed under the Business Source License 1.1.
|
|
6
|
+
#
|
|
7
|
+
# This stub describes the public API of a closed-source module compiled to a
|
|
8
|
+
# native extension (.so / .pyd). The original source is not distributed.
|
|
9
|
+
# Production / commercial use requires a separate commercial license.
|
|
10
|
+
#
|
|
11
|
+
# FINGERPRINT: SMF-PSITOOL-5299b9cc
|
|
12
|
+
# (Unique trace marker. Do not remove or alter — used for plagiarism
|
|
13
|
+
# detection across the public internet.)
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
import pandas as pd
|
|
18
|
+
from typing import Union, List, Dict, Optional, Tuple, Callable, Any
|
|
19
|
+
from tqdm import tqdm
|
|
20
|
+
from Modeling_Tool.Core.Binning_Tool import quick_binning
|
|
21
|
+
|
|
22
|
+
class PSICalculator:
|
|
23
|
+
def __init__(self, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, binning_engine: Any = None): ...
|
|
24
|
+
def calculate(self, expected_df: pd.DataFrame, current_data: pd.DataFrame, varlist: List[str], group_by: Optional[str] = None, group_name: Optional[str] = None, return_details = False) -> pd.DataFrame: ...
|
|
25
|
+
def calculate_psi(expected: Union[pd.DataFrame, pd.Series], actual: Union[pd.DataFrame, pd.Series], target_col: str, buckets: int = 10, equal_freq: bool = True, group_by: Optional[Union[str, List[str]]] = None, return_details: bool = False, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5) -> Union[float, pd.DataFrame, Tuple[Dict, Dict]]: ...
|
|
26
|
+
def calculate_within_psi(data: pd.DataFrame, grp_name: str, target_col: str, benchmark: Optional[Any] = None, equal_freq: bool = True, buckets: int = 10, return_details: bool = False, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, benchmark_display_name: Optional[str] = None) -> Union[pd.DataFrame, Dict]: ...
|
|
27
|
+
def calculate_psi_within_dataset(data: pd.DataFrame, grp_name: str, varlist: List[str], benchmark: Optional[Any] = None, equal_freq: bool = True, buckets: int = 10, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5) -> pd.DataFrame: ...
|
|
28
|
+
def calculate_multivar_psi_two_sets(expected_df: pd.DataFrame, actual_df: pd.DataFrame, varlist: List[str], group_by: Optional[Union[str, List[str]]] = None, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5) -> pd.DataFrame: ...
|
|
29
|
+
def calculate_multigroup_psi_two_sets(expected_df: pd.DataFrame, actual_df: pd.DataFrame, varlist: List[str], group_by: Optional[Union[str, List[str]]] = None, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, group_name: Optional[str] = None, return_details: bool = False) -> Union[pd.DataFrame, Dict[str, pd.DataFrame]]: ...
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""WOE-engine-aware wrappers for feature screening tools.
|
|
2
|
+
|
|
3
|
+
The original Feature modules are compiled in release builds, so mutating their
|
|
4
|
+
class objects at import time is fragile. These wrappers delegate to the original
|
|
5
|
+
classes for legacy behavior and only take over when a fitted WOE engine is
|
|
6
|
+
explicitly supplied.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
|
|
16
|
+
from .PSI_Tool import PSICalculator as _BasePSICalculator
|
|
17
|
+
from .Feature_Insights import (
|
|
18
|
+
CorrelationFilter as _BaseCorrelationFilter,
|
|
19
|
+
VarExtractionInsights as _BaseVarExtractionInsights,
|
|
20
|
+
var_corr_filter,
|
|
21
|
+
)
|
|
22
|
+
from .Distribution_Tool import proc_means_by_grp
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _psi_from_bins(expected_bins: pd.Series, current_bins: pd.Series, content: float) -> float:
|
|
26
|
+
expected_pct = expected_bins.value_counts(normalize=True, dropna=False)
|
|
27
|
+
current_pct = current_bins.value_counts(normalize=True, dropna=False)
|
|
28
|
+
all_bins = expected_pct.index.union(current_pct.index)
|
|
29
|
+
e = expected_pct.reindex(all_bins, fill_value=0).astype(float) + content
|
|
30
|
+
c = current_pct.reindex(all_bins, fill_value=0).astype(float) + content
|
|
31
|
+
return float(((c - e) * np.log(c / e)).sum())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _make_monotone_adapter(owner, data: pd.DataFrame, varlist: list[str]):
|
|
35
|
+
if getattr(owner, "woe_binner", None) is not None:
|
|
36
|
+
return as_woe_engine(owner.woe_binner)
|
|
37
|
+
if getattr(owner, "woe_engine", "master") != "monotone":
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
from Modeling_Tool.WOE.WOE_Monotone_Binner import MonotoneWOEBinner
|
|
41
|
+
|
|
42
|
+
params = dict(getattr(owner, "woe_engine_params", {}) or {})
|
|
43
|
+
fit_params = params.pop("fit_params", {}) if "fit_params" in params else {}
|
|
44
|
+
binner = MonotoneWOEBinner(
|
|
45
|
+
feature_cols=varlist,
|
|
46
|
+
target_col=owner.dep,
|
|
47
|
+
special_values=owner.spec_values,
|
|
48
|
+
**params,
|
|
49
|
+
)
|
|
50
|
+
binner.fit(
|
|
51
|
+
data,
|
|
52
|
+
chi2_binning=owner.chi2_method,
|
|
53
|
+
chi2_p=owner.chi2_p,
|
|
54
|
+
chi2_init_size=owner.init_equi_bins,
|
|
55
|
+
**fit_params,
|
|
56
|
+
)
|
|
57
|
+
owner.woe_binner = binner
|
|
58
|
+
return as_woe_engine(binner)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _screening_summary_from_engine(
|
|
62
|
+
data: pd.DataFrame,
|
|
63
|
+
varlist: list[str],
|
|
64
|
+
dep: str,
|
|
65
|
+
adapter,
|
|
66
|
+
iv_cut: float,
|
|
67
|
+
missing_rate_ref,
|
|
68
|
+
) -> pd.DataFrame:
|
|
69
|
+
rows = []
|
|
70
|
+
target = data[dep]
|
|
71
|
+
total_bad = max(float((target == 1).sum()), 1.0)
|
|
72
|
+
total_good = max(float((target == 0).sum()), 1.0)
|
|
73
|
+
|
|
74
|
+
for var in tqdm(varlist):
|
|
75
|
+
if var not in data.columns or data[var].nunique(dropna=False) <= 1:
|
|
76
|
+
continue
|
|
77
|
+
try:
|
|
78
|
+
bins = adapter.assign_bins(data, var)
|
|
79
|
+
tmp = pd.DataFrame({"bin": bins, dep: target})
|
|
80
|
+
grouped = tmp.groupby("bin", dropna=False)[dep].agg(["count", "sum"]).reset_index()
|
|
81
|
+
grouped = grouped.rename(columns={"count": "n", "sum": "n_bad"})
|
|
82
|
+
grouped["n_good"] = grouped["n"] - grouped["n_bad"]
|
|
83
|
+
grouped["bad_pct"] = grouped["n_bad"] / total_bad
|
|
84
|
+
grouped["good_pct"] = grouped["n_good"] / total_good
|
|
85
|
+
grouped["woe"] = np.log((grouped["bad_pct"] + 1e-6) / (grouped["good_pct"] + 1e-6))
|
|
86
|
+
grouped["iv"] = (grouped["bad_pct"] - grouped["good_pct"]) * grouped["woe"]
|
|
87
|
+
grouped["avg_bad"] = grouped["n_bad"] / grouped["n"].replace(0, np.nan)
|
|
88
|
+
grouped = grouped.sort_values("avg_bad", ascending=False).reset_index(drop=True)
|
|
89
|
+
ks = (grouped["bad_pct"].cumsum() - grouped["good_pct"].cumsum()).abs().max()
|
|
90
|
+
overall_bad = max(float((target == 1).mean()), 1e-6)
|
|
91
|
+
lift = float((grouped["avg_bad"] / overall_bad).replace([np.inf, -np.inf], np.nan).max())
|
|
92
|
+
rows.append({
|
|
93
|
+
"var": var,
|
|
94
|
+
"ks_in_gains": float(ks),
|
|
95
|
+
"lift_in_gains": lift,
|
|
96
|
+
"iv": float(grouped["iv"].sum()),
|
|
97
|
+
"n_bump": int(grouped.shape[0]),
|
|
98
|
+
"n_bins": int(grouped.shape[0]),
|
|
99
|
+
})
|
|
100
|
+
except Exception:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
columns = [
|
|
104
|
+
"var", "n_all", "n", "ks_in_gains", "lift_in_gains", "iv",
|
|
105
|
+
"n_bump", "missing_rate", "min", "mean", "max", "n_bins",
|
|
106
|
+
]
|
|
107
|
+
if not rows:
|
|
108
|
+
return pd.DataFrame(columns=columns)
|
|
109
|
+
|
|
110
|
+
high_iv_summary = pd.DataFrame(rows).query(f"iv >= {iv_cut}").round(4)
|
|
111
|
+
high_iv_varlist = high_iv_summary["var"].tolist()
|
|
112
|
+
if high_iv_varlist:
|
|
113
|
+
means = proc_means_by_grp(data, high_iv_varlist, spec_missing_value=missing_rate_ref)
|
|
114
|
+
summary = high_iv_summary.merge(
|
|
115
|
+
means[["attribute", "N_ALL", "N", "MISSING_RATE", "MIN", "MEAN", "MAX"]],
|
|
116
|
+
left_on="var",
|
|
117
|
+
right_on="attribute",
|
|
118
|
+
how="left",
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
summary = high_iv_summary.copy()
|
|
122
|
+
for col in ["N_ALL", "N", "MISSING_RATE", "MIN", "MEAN", "MAX"]:
|
|
123
|
+
summary[col] = np.nan
|
|
124
|
+
|
|
125
|
+
summary.columns = [str(c).lower() for c in summary.columns]
|
|
126
|
+
return summary[columns]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class PSICalculator:
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
buckets: int = 10,
|
|
133
|
+
equal_freq: bool = True,
|
|
134
|
+
min_bin_prop: float = 0.05,
|
|
135
|
+
content: float = 1e-6,
|
|
136
|
+
precision: int = 5,
|
|
137
|
+
binning_engine=None,
|
|
138
|
+
):
|
|
139
|
+
self._base = _BasePSICalculator(buckets, equal_freq, min_bin_prop, content, precision)
|
|
140
|
+
self.buckets = buckets
|
|
141
|
+
self.equal_freq = equal_freq
|
|
142
|
+
self.min_bin_prop = min_bin_prop
|
|
143
|
+
self.content = content
|
|
144
|
+
self.precision = precision
|
|
145
|
+
self.binning_engine = binning_engine
|
|
146
|
+
self._woe_engine_adapter = as_woe_engine(binning_engine) if binning_engine is not None else None
|
|
147
|
+
|
|
148
|
+
def __getattr__(self, name):
|
|
149
|
+
return getattr(self._base, name)
|
|
150
|
+
|
|
151
|
+
def calculate(self, expected_df, current_data, varlist, group_by=None, group_name=None, return_details=False):
|
|
152
|
+
adapter = self._woe_engine_adapter
|
|
153
|
+
if adapter is None:
|
|
154
|
+
return self._base.calculate(expected_df, current_data, varlist, group_by, group_name, return_details)
|
|
155
|
+
|
|
156
|
+
detail = {}
|
|
157
|
+
rows = []
|
|
158
|
+
groups = [(None, current_data)] if group_by is None else list(current_data.groupby(group_by))
|
|
159
|
+
for var in varlist:
|
|
160
|
+
expected_bins = adapter.assign_bins(expected_df, var)
|
|
161
|
+
for grp_value, grp_df in groups:
|
|
162
|
+
current_bins = adapter.assign_bins(grp_df, var)
|
|
163
|
+
row = {"var": var, "psi": round(_psi_from_bins(expected_bins, current_bins, self.content), self.precision)}
|
|
164
|
+
if group_by is not None:
|
|
165
|
+
row[group_name or group_by] = grp_value
|
|
166
|
+
rows.append(row)
|
|
167
|
+
if return_details:
|
|
168
|
+
detail[var if grp_value is None else (var, grp_value)] = {
|
|
169
|
+
"expected_bins": expected_bins.value_counts(normalize=True, dropna=False),
|
|
170
|
+
"current_bins": current_bins.value_counts(normalize=True, dropna=False),
|
|
171
|
+
}
|
|
172
|
+
result = pd.DataFrame(rows)
|
|
173
|
+
return (result, detail) if return_details else result
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class VarExtractionInsights:
|
|
177
|
+
def __init__(
|
|
178
|
+
self,
|
|
179
|
+
data,
|
|
180
|
+
dep,
|
|
181
|
+
plot_path,
|
|
182
|
+
nbins=10,
|
|
183
|
+
equal_freq=True,
|
|
184
|
+
min_bin_prop=0.05,
|
|
185
|
+
precision=5,
|
|
186
|
+
chi2_method=False,
|
|
187
|
+
chi2_p=0.9,
|
|
188
|
+
init_equi_bins=5000,
|
|
189
|
+
tree_binning=True,
|
|
190
|
+
include_missing=True,
|
|
191
|
+
seed=3407,
|
|
192
|
+
missing_rate_ref=-999999,
|
|
193
|
+
spec_values=None,
|
|
194
|
+
woe_engine="master",
|
|
195
|
+
woe_binner=None,
|
|
196
|
+
woe_engine_params=None,
|
|
197
|
+
):
|
|
198
|
+
self._base = _BaseVarExtractionInsights(
|
|
199
|
+
data, dep, plot_path, nbins, equal_freq, min_bin_prop, precision,
|
|
200
|
+
chi2_method, chi2_p, init_equi_bins, tree_binning, include_missing,
|
|
201
|
+
seed, missing_rate_ref, spec_values,
|
|
202
|
+
)
|
|
203
|
+
self.data = data
|
|
204
|
+
self.dep = dep
|
|
205
|
+
self.plot_path = plot_path
|
|
206
|
+
self.nbins = nbins
|
|
207
|
+
self.equal_freq = equal_freq
|
|
208
|
+
self.min_bin_prop = min_bin_prop
|
|
209
|
+
self.precision = precision
|
|
210
|
+
self.chi2_method = chi2_method
|
|
211
|
+
self.chi2_p = chi2_p
|
|
212
|
+
self.init_equi_bins = init_equi_bins
|
|
213
|
+
self.tree_binning = tree_binning
|
|
214
|
+
self.include_missing = include_missing
|
|
215
|
+
self.seed = seed
|
|
216
|
+
self.missing_rate_ref = missing_rate_ref
|
|
217
|
+
self.spec_values = spec_values if spec_values is not None else []
|
|
218
|
+
self.woe_engine = woe_engine
|
|
219
|
+
self.woe_binner = woe_binner
|
|
220
|
+
self.woe_engine_params = woe_engine_params or {}
|
|
221
|
+
|
|
222
|
+
def __getattr__(self, name):
|
|
223
|
+
return getattr(self._base, name)
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def remove_folder(file_path):
|
|
227
|
+
return _BaseVarExtractionInsights.remove_folder(file_path)
|
|
228
|
+
|
|
229
|
+
def get_var_analysis_report(self, data, varlist, dep=None, iv_cut=0.01):
|
|
230
|
+
if dep is None:
|
|
231
|
+
dep = self.dep
|
|
232
|
+
adapter = as_woe_engine(self.woe_binner) if self.woe_binner is not None else _make_monotone_adapter(self, data, varlist)
|
|
233
|
+
if adapter is None:
|
|
234
|
+
return self._base.get_var_analysis_report(data, varlist, dep, iv_cut)
|
|
235
|
+
return _screening_summary_from_engine(data, varlist, dep, adapter, iv_cut, self.missing_rate_ref)
|
|
236
|
+
|
|
237
|
+
def plot_woe(self, data, varlist, plot_group=None, plot_dirname="var_analysis_plot", plot_path=None):
|
|
238
|
+
adapter = as_woe_engine(self.woe_binner) if self.woe_binner is not None else _make_monotone_adapter(self, data, varlist)
|
|
239
|
+
if adapter is None:
|
|
240
|
+
return self._base.plot_woe(data, varlist, plot_group, plot_dirname, plot_path)
|
|
241
|
+
if plot_path is None:
|
|
242
|
+
plot_path = self.plot_path
|
|
243
|
+
if plot_path is None:
|
|
244
|
+
return None
|
|
245
|
+
if adapter.get_engine_name() == "monotone" and hasattr(adapter.engine, "plot_woe_graph"):
|
|
246
|
+
import os
|
|
247
|
+
graph_path = os.path.join(plot_path, plot_dirname)
|
|
248
|
+
adapter.engine.plot_woe_graph(graph_path, group_name=plot_group, _df_for_group=data if plot_group else None)
|
|
249
|
+
return None
|
|
250
|
+
return self._base.plot_woe(data, varlist, plot_group, plot_dirname, plot_path)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class CorrelationFilter:
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
data,
|
|
257
|
+
dep,
|
|
258
|
+
corr_cutpoint=0.8,
|
|
259
|
+
method="pearson",
|
|
260
|
+
tree_binning=False,
|
|
261
|
+
chi2_method=False,
|
|
262
|
+
seed=42,
|
|
263
|
+
chi2_p=0.999,
|
|
264
|
+
init_equi_bins=1000,
|
|
265
|
+
missing_rate_ref=-9999999,
|
|
266
|
+
spec_values=[],
|
|
267
|
+
base_metric="iv",
|
|
268
|
+
woe_engine="master",
|
|
269
|
+
woe_binner=None,
|
|
270
|
+
woe_engine_params=None,
|
|
271
|
+
):
|
|
272
|
+
self._base = _BaseCorrelationFilter(
|
|
273
|
+
data, dep, corr_cutpoint, method, tree_binning, chi2_method, seed,
|
|
274
|
+
chi2_p, init_equi_bins, missing_rate_ref, spec_values, base_metric,
|
|
275
|
+
)
|
|
276
|
+
self.data = data
|
|
277
|
+
self.dep = dep
|
|
278
|
+
self.corr_cutpoint = corr_cutpoint
|
|
279
|
+
self.method = method
|
|
280
|
+
self.tree_binning = tree_binning
|
|
281
|
+
self.chi2_method = chi2_method
|
|
282
|
+
self.seed = seed
|
|
283
|
+
self.chi2_p = chi2_p
|
|
284
|
+
self.init_equi_bins = init_equi_bins
|
|
285
|
+
self.missing_rate_ref = missing_rate_ref
|
|
286
|
+
self.spec_values = spec_values
|
|
287
|
+
self.base_metric = base_metric
|
|
288
|
+
self.woe_engine = woe_engine
|
|
289
|
+
self.woe_binner = woe_binner
|
|
290
|
+
self.woe_engine_params = woe_engine_params or {}
|
|
291
|
+
self.correlated_dict = {}
|
|
292
|
+
self.filtered_varlist = []
|
|
293
|
+
|
|
294
|
+
def __getattr__(self, name):
|
|
295
|
+
return getattr(self._base, name)
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def calculate_vif(df):
|
|
299
|
+
return _BaseCorrelationFilter.calculate_vif(df)
|
|
300
|
+
|
|
301
|
+
def filter_single_iteration(self, varlist):
|
|
302
|
+
if self.woe_binner is None and self.woe_engine == "master":
|
|
303
|
+
return self._base.filter_single_iteration(varlist)
|
|
304
|
+
|
|
305
|
+
name_mapping = {"iv": "iv", "ks": "ks_in_gains"}
|
|
306
|
+
high_corr_var = var_corr_filter(self.data, varlist, corr_cutpoint=self.corr_cutpoint, method=self.method)
|
|
307
|
+
if len(high_corr_var) == 0:
|
|
308
|
+
return varlist
|
|
309
|
+
|
|
310
|
+
selected_varlist = []
|
|
311
|
+
removed_varlist = []
|
|
312
|
+
for var in tqdm(high_corr_var["VAR1"].drop_duplicates().tolist()):
|
|
313
|
+
if var in set(removed_varlist + selected_varlist):
|
|
314
|
+
continue
|
|
315
|
+
single_var_corr = high_corr_var.query(f"VAR1 == '{var}'")
|
|
316
|
+
correlated_list = [var] + single_var_corr["VAR2"].drop_duplicates().tolist()
|
|
317
|
+
insights = VarExtractionInsights(
|
|
318
|
+
self.data, self.dep, None, chi2_method=self.chi2_method,
|
|
319
|
+
chi2_p=self.chi2_p, init_equi_bins=self.init_equi_bins,
|
|
320
|
+
tree_binning=self.tree_binning, seed=self.seed,
|
|
321
|
+
missing_rate_ref=self.missing_rate_ref, spec_values=self.spec_values,
|
|
322
|
+
woe_engine=self.woe_engine, woe_binner=self.woe_binner,
|
|
323
|
+
woe_engine_params=self.woe_engine_params,
|
|
324
|
+
)
|
|
325
|
+
summary = insights.get_var_analysis_report(self.data, correlated_list, dep=self.dep, iv_cut=0)
|
|
326
|
+
if summary.empty:
|
|
327
|
+
continue
|
|
328
|
+
selected = summary.sort_values([name_mapping[self.base_metric.lower()]], ascending=False)["var"].iloc[0]
|
|
329
|
+
if selected not in selected_varlist:
|
|
330
|
+
selected_varlist.append(selected)
|
|
331
|
+
removed_varlist += [x for x in correlated_list if x != selected and x not in removed_varlist]
|
|
332
|
+
self.correlated_dict[var] = {"corr": single_var_corr, "gains": summary}
|
|
333
|
+
|
|
334
|
+
return selected_varlist + [x for x in varlist if x not in (selected_varlist + removed_varlist)]
|
|
335
|
+
|
|
336
|
+
def remove_highly_correlated(self, varlist, max_iterations=10):
|
|
337
|
+
if self.woe_binner is None and self.woe_engine == "master":
|
|
338
|
+
result = self._base.remove_highly_correlated(varlist, max_iterations)
|
|
339
|
+
self.correlated_dict = getattr(self._base, "correlated_dict", {})
|
|
340
|
+
self.filtered_varlist = getattr(self._base, "filtered_varlist", [])
|
|
341
|
+
return result
|
|
342
|
+
|
|
343
|
+
last_keep_list = self.filter_single_iteration(varlist)
|
|
344
|
+
for _ in range(1, max_iterations):
|
|
345
|
+
keep_list = self.filter_single_iteration(last_keep_list)
|
|
346
|
+
removed_vars = [x for x in last_keep_list if x not in keep_list]
|
|
347
|
+
self.filtered_varlist.append(removed_vars)
|
|
348
|
+
if len(removed_vars) == 0:
|
|
349
|
+
break
|
|
350
|
+
last_keep_list = keep_list
|
|
351
|
+
self.filtered_varlist = [x for x in varlist if x not in last_keep_list]
|
|
352
|
+
return last_keep_list
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
__all__ = ["PSICalculator", "VarExtractionInsights", "CorrelationFilter"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .Distribution_Tool import (
|
|
2
|
+
proc_means,
|
|
3
|
+
proc_means_by_grp,
|
|
4
|
+
get_distribution_shift,
|
|
5
|
+
get_distribution_shift_single_var,
|
|
6
|
+
plot_distribution,
|
|
7
|
+
DistributionShiftAnalyzer,
|
|
8
|
+
DistributionPlotter,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .Feature_Insights import (
|
|
12
|
+
var_corr_filter,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .PSI_Tool import (
|
|
16
|
+
calculate_psi,
|
|
17
|
+
calculate_within_psi,
|
|
18
|
+
calculate_psi_within_dataset,
|
|
19
|
+
calculate_multivar_psi_two_sets,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .WOE_Engine_Feature_Patch import (
|
|
23
|
+
PSICalculator,
|
|
24
|
+
VarExtractionInsights,
|
|
25
|
+
CorrelationFilter,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# Distribution_Tool
|
|
30
|
+
'proc_means', 'proc_means_by_grp', 'get_distribution_shift',
|
|
31
|
+
'get_distribution_shift_single_var', 'plot_distribution',
|
|
32
|
+
'DistributionShiftAnalyzer', 'DistributionPlotter',
|
|
33
|
+
|
|
34
|
+
# Feature_Insights
|
|
35
|
+
'VarExtractionInsights', 'var_corr_filter', 'CorrelationFilter',
|
|
36
|
+
|
|
37
|
+
# PSI_Tool
|
|
38
|
+
'PSICalculator', 'calculate_psi', 'calculate_within_psi',
|
|
39
|
+
'calculate_psi_within_dataset', 'calculate_multivar_psi_two_sets',
|
|
40
|
+
]
|