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,1094 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WOE转换与单调性分析工具包
|
|
3
|
+
提供WOE分箱、转换、映射及单调性检验功能
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from Modeling_Tool.Core.Binning_Tool import run_binning, super_binning, get_bin_range_list
|
|
11
|
+
from Modeling_Tool.Core.Slope_Tool import calculate_slope_manual
|
|
12
|
+
from Modeling_Tool.Core.utils import calc_iv, calc_woe
|
|
13
|
+
|
|
14
|
+
def is_monotonic(data, column, direction='auto', strict=False, handle_nan='drop'):
|
|
15
|
+
"""检查Pandas Series或DataFrame列是否单调(递增或递减)。
|
|
16
|
+
|
|
17
|
+
参数:
|
|
18
|
+
-----------
|
|
19
|
+
data : pd.DataFrame
|
|
20
|
+
包含数据的数据框
|
|
21
|
+
column : str
|
|
22
|
+
要检查的列名
|
|
23
|
+
direction : str, optional
|
|
24
|
+
检查方向,'auto'(自动检测)、'increasing'(递增)或'decreasing'(递减),
|
|
25
|
+
默认为'auto'
|
|
26
|
+
strict : bool, optional
|
|
27
|
+
是否要求严格单调(不允许相等值),默认为False
|
|
28
|
+
handle_nan : str, optional
|
|
29
|
+
处理NaN值的方法,可选'drop'(忽略)、'forward'(向前填充)、
|
|
30
|
+
'backward'(向后填充)或'error'(报错),默认为'drop'
|
|
31
|
+
|
|
32
|
+
返回:
|
|
33
|
+
--------
|
|
34
|
+
tuple
|
|
35
|
+
(是否单调, 单调方向) 的元组。
|
|
36
|
+
是否单调为bool值,方向为1(递增)、-1(递减)或0(非单调)
|
|
37
|
+
"""
|
|
38
|
+
series = data[column]
|
|
39
|
+
|
|
40
|
+
# 处理NaN值
|
|
41
|
+
if series.isna().any():
|
|
42
|
+
if handle_nan == 'drop':
|
|
43
|
+
series = series.dropna()
|
|
44
|
+
elif handle_nan == 'forward':
|
|
45
|
+
series = series.ffill()
|
|
46
|
+
elif handle_nan == 'backward':
|
|
47
|
+
series = series.bfill()
|
|
48
|
+
elif handle_nan == 'error':
|
|
49
|
+
raise ValueError("序列包含NaN值")
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError("handle_nan参数必须是'drop'、'forward'、'backward'或'error'")
|
|
52
|
+
|
|
53
|
+
# 如果序列为空或只有一个元素,则认为是单调的
|
|
54
|
+
if len(series) <= 1:
|
|
55
|
+
return True, 0 if len(series) == 0 else 0
|
|
56
|
+
|
|
57
|
+
# 计算差值
|
|
58
|
+
diffs = series.diff().iloc[1:]
|
|
59
|
+
|
|
60
|
+
# 检查单调性
|
|
61
|
+
if direction == 'auto':
|
|
62
|
+
if strict:
|
|
63
|
+
if (diffs > 0).all(): # 所有差值都为正
|
|
64
|
+
return True, 1
|
|
65
|
+
elif (diffs < 0).all(): # 所有差值都为负
|
|
66
|
+
return True, -1
|
|
67
|
+
else:
|
|
68
|
+
return False, 0
|
|
69
|
+
else:
|
|
70
|
+
if (diffs >= 0).all(): # 所有差值都非负
|
|
71
|
+
return True, 1
|
|
72
|
+
elif (diffs <= 0).all(): # 所有差值都非正
|
|
73
|
+
return True, -1
|
|
74
|
+
else:
|
|
75
|
+
return False, 0
|
|
76
|
+
elif direction == 'increasing':
|
|
77
|
+
if strict:
|
|
78
|
+
is_mono = (diffs > 0).all()
|
|
79
|
+
else:
|
|
80
|
+
is_mono = (diffs >= 0).all()
|
|
81
|
+
return is_mono, 1 if is_mono else 0
|
|
82
|
+
elif direction == 'decreasing':
|
|
83
|
+
if strict:
|
|
84
|
+
is_mono = (diffs < 0).all()
|
|
85
|
+
else:
|
|
86
|
+
is_mono = (diffs <= 0).all()
|
|
87
|
+
return is_mono, -1 if is_mono else 0
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError("direction参数必须是'auto'、'increasing'或'decreasing'")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def check_monotonicity(data, var):
|
|
93
|
+
"""检查WOE值的单调性。
|
|
94
|
+
|
|
95
|
+
对指定变量验证其WOE值是否满足单调性要求,
|
|
96
|
+
用于评估分箱效果是否符合业务逻辑。
|
|
97
|
+
|
|
98
|
+
参数:
|
|
99
|
+
-----------
|
|
100
|
+
data : pd.DataFrame
|
|
101
|
+
包含分箱信息和WOE值的数据框
|
|
102
|
+
var : str
|
|
103
|
+
待检查的变量名
|
|
104
|
+
|
|
105
|
+
返回:
|
|
106
|
+
--------
|
|
107
|
+
tuple
|
|
108
|
+
(是否单调, 单调方向) 的元组,格式同 is_monotonic 函数
|
|
109
|
+
|
|
110
|
+
示例:
|
|
111
|
+
--------
|
|
112
|
+
>>> result = check_monotonicity(woe_df, 'age')
|
|
113
|
+
"""
|
|
114
|
+
df_grp = data.groupby([f"_bin_num_{var}"]).agg(
|
|
115
|
+
{f"_bin_num_{var}": "count", var: [min, max], f"{var}_woe": [min, max, "mean"]}
|
|
116
|
+
)
|
|
117
|
+
assert (df_grp[(f"{var}_woe", "min")] == df_grp[(f"{var}_woe", "max")]).all()
|
|
118
|
+
woe = pd.DataFrame(df_grp.loc[df_grp[(var, "min")] != df_grp[(var, "max")], (f"{var}_woe", "min")])
|
|
119
|
+
res = is_monotonic(woe, (f"{var}_woe", "min"))
|
|
120
|
+
return res
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class WOETransformer:
|
|
124
|
+
"""WOE转换器。
|
|
125
|
+
|
|
126
|
+
提供WOE分箱、转换和单调性检验的完整功能,
|
|
127
|
+
支持单变量和多变量批量处理,支持训练集和验证集的WOE映射。
|
|
128
|
+
|
|
129
|
+
参数:
|
|
130
|
+
-----------
|
|
131
|
+
nbins : int, optional
|
|
132
|
+
分箱数量,默认为10
|
|
133
|
+
precision : int, optional
|
|
134
|
+
WOE和IV计算精度,默认为5
|
|
135
|
+
min_bin_prop : float, optional
|
|
136
|
+
每个分箱的最小样本比例,默认为0.05
|
|
137
|
+
include_missing : bool, optional
|
|
138
|
+
是否将缺失值作为单独分箱,默认为False
|
|
139
|
+
equal_freq : bool, optional
|
|
140
|
+
是否使用等频分箱,默认为True
|
|
141
|
+
fillna : int/float, optional
|
|
142
|
+
缺失值填充值,默认为-999999
|
|
143
|
+
chi2_config : tuple, optional
|
|
144
|
+
卡方分箱配置,(init_bins, p_value)元组,默认为None
|
|
145
|
+
tree_binning_seed : int, optional
|
|
146
|
+
决策树分箱随机种子,默认为None
|
|
147
|
+
spec_values : list, optional
|
|
148
|
+
特殊值列表,默认为空列表
|
|
149
|
+
drop_bin_info : bool, optional
|
|
150
|
+
是否删除中间分箱信息列,默认为True
|
|
151
|
+
ret_woe_table : bool, optional
|
|
152
|
+
是否返回WOE映射表,默认为True
|
|
153
|
+
|
|
154
|
+
示例:
|
|
155
|
+
--------
|
|
156
|
+
>>> transformer = WOETransformer(nbins=10)
|
|
157
|
+
>>> result = transformer.transform(df, ['var1', 'var2'], 'target')
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(self, nbins=10, precision=5, min_bin_prop=0.05, include_missing=False,
|
|
161
|
+
equal_freq=True, fillna=-999999, chi2_config=None, tree_binning_seed=None,
|
|
162
|
+
spec_values=None, drop_bin_info=True, ret_woe_table=True):
|
|
163
|
+
"""初始化WOE转换器。
|
|
164
|
+
|
|
165
|
+
参数:
|
|
166
|
+
-----------
|
|
167
|
+
nbins : int, optional
|
|
168
|
+
分箱数量
|
|
169
|
+
precision : int, optional
|
|
170
|
+
WOE和IV计算精度
|
|
171
|
+
min_bin_prop : float, optional
|
|
172
|
+
每个分箱的最小样本比例
|
|
173
|
+
include_missing : bool, optional
|
|
174
|
+
是否将缺失值作为单独分箱
|
|
175
|
+
equal_freq : bool, optional
|
|
176
|
+
是否使用等频分箱
|
|
177
|
+
fillna : int/float, optional
|
|
178
|
+
缺失值填充值
|
|
179
|
+
chi2_config : tuple, optional
|
|
180
|
+
卡方分箱配置
|
|
181
|
+
tree_binning_seed : int, optional
|
|
182
|
+
决策树分箱随机种子
|
|
183
|
+
spec_values : list, optional
|
|
184
|
+
特殊值列表
|
|
185
|
+
drop_bin_info : bool, optional
|
|
186
|
+
是否删除中间分箱信息列
|
|
187
|
+
ret_woe_table : bool, optional
|
|
188
|
+
是否返回WOE映射表
|
|
189
|
+
"""
|
|
190
|
+
self.nbins = nbins
|
|
191
|
+
self.precision = precision
|
|
192
|
+
self.min_bin_prop = min_bin_prop
|
|
193
|
+
self.include_missing = include_missing
|
|
194
|
+
self.equal_freq = equal_freq
|
|
195
|
+
self.fillna = fillna
|
|
196
|
+
self.chi2_config = chi2_config
|
|
197
|
+
self.tree_binning_seed = tree_binning_seed
|
|
198
|
+
self.spec_values = spec_values if spec_values is not None else []
|
|
199
|
+
self.drop_bin_info = drop_bin_info
|
|
200
|
+
self.ret_woe_table = ret_woe_table
|
|
201
|
+
|
|
202
|
+
def _get_woe_table(self, binning_res, var, dep):
|
|
203
|
+
"""根据分箱结果计算WOE表。
|
|
204
|
+
|
|
205
|
+
参数:
|
|
206
|
+
-----------
|
|
207
|
+
binning_res : pd.DataFrame
|
|
208
|
+
分箱结果数据框
|
|
209
|
+
var : str
|
|
210
|
+
变量名
|
|
211
|
+
dep : str
|
|
212
|
+
目标变量名
|
|
213
|
+
|
|
214
|
+
返回:
|
|
215
|
+
--------
|
|
216
|
+
tuple
|
|
217
|
+
(woe_table, woe_mapping_dict) 元组
|
|
218
|
+
"""
|
|
219
|
+
woe_table = binning_res.groupby([f"_bin_num_{var}", f"_bin_range_{var}"], dropna=False)\
|
|
220
|
+
.agg(
|
|
221
|
+
MIN=(var, "min"),
|
|
222
|
+
MAX=(var, "max"),
|
|
223
|
+
N=(f"_bin_num_{var}", "count"),
|
|
224
|
+
AVG_SCORE=(var, "mean"),
|
|
225
|
+
AVG_BAD=(dep, lambda x: ((x == 1).sum() / x.count())),
|
|
226
|
+
AVG_GOOD=(dep, lambda x: ((x == 0).sum() / x.count())),
|
|
227
|
+
N_BAD=(dep, "sum"),
|
|
228
|
+
N_GOOD=(dep, lambda x: (x == 0).sum())
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# IV/WOE Calculation
|
|
232
|
+
woe_table["BAD_PCT_PER_BIN"] = woe_table["N_BAD"] / woe_table["N_BAD"].sum()
|
|
233
|
+
woe_table["GOOD_PCT_PER_BIN"] = woe_table["N_GOOD"] / woe_table["N_GOOD"].sum()
|
|
234
|
+
woe_table["LIFT"] = woe_table['AVG_BAD'] / woe_table['AVG_BAD'].mean()
|
|
235
|
+
woe_table["WOE"] = calc_woe(data=woe_table, bad_pct="BAD_PCT_PER_BIN", good_pct="GOOD_PCT_PER_BIN")
|
|
236
|
+
woe_table["IV"] = calc_iv(data=woe_table, bad_pct="BAD_PCT_PER_BIN", good_pct="GOOD_PCT_PER_BIN")
|
|
237
|
+
|
|
238
|
+
# WOE Mapping Dictionary
|
|
239
|
+
woe_table = woe_table.reset_index(drop=False)
|
|
240
|
+
woe_mapping_dict = dict(zip(woe_table[f"_bin_range_{var}"], woe_table["WOE"]))
|
|
241
|
+
|
|
242
|
+
return woe_table, woe_mapping_dict
|
|
243
|
+
|
|
244
|
+
def transform_single(self, train_df, var, dep, oot_df=None, check_monotonicity_flag=False):
|
|
245
|
+
"""对单个变量进行WOE转换。
|
|
246
|
+
|
|
247
|
+
参数:
|
|
248
|
+
-----------
|
|
249
|
+
train_df : pd.DataFrame
|
|
250
|
+
训练数据集
|
|
251
|
+
var : str
|
|
252
|
+
待转换的变量名
|
|
253
|
+
dep : str
|
|
254
|
+
目标变量(因变量)名
|
|
255
|
+
oot_df : pd.DataFrame, optional
|
|
256
|
+
验证/测试数据集,默认为None
|
|
257
|
+
check_monotonicity_flag : bool, optional
|
|
258
|
+
是否检查单调性,默认为False
|
|
259
|
+
|
|
260
|
+
返回:
|
|
261
|
+
--------
|
|
262
|
+
tuple/list
|
|
263
|
+
根据参数返回训练结果、验证结果和WOE映射表
|
|
264
|
+
"""
|
|
265
|
+
chi2_method = False
|
|
266
|
+
if self.chi2_config:
|
|
267
|
+
chi2_method = True
|
|
268
|
+
else:
|
|
269
|
+
self.chi2_config = (100, 0.99)
|
|
270
|
+
|
|
271
|
+
tree_binning = False
|
|
272
|
+
if self.tree_binning_seed:
|
|
273
|
+
tree_binning = True
|
|
274
|
+
|
|
275
|
+
train_res, train_edges = super_binning(
|
|
276
|
+
data=train_df,
|
|
277
|
+
score=var,
|
|
278
|
+
dep=dep,
|
|
279
|
+
nbins=self.nbins,
|
|
280
|
+
precision=self.precision,
|
|
281
|
+
min_bin_prop=self.min_bin_prop,
|
|
282
|
+
include_missing=self.include_missing,
|
|
283
|
+
equal_freq=self.equal_freq,
|
|
284
|
+
chi2_method=chi2_method,
|
|
285
|
+
chi2_p=self.chi2_config[1],
|
|
286
|
+
init_equi_bins=self.chi2_config[0],
|
|
287
|
+
fillna=self.fillna,
|
|
288
|
+
spec_values=self.spec_values,
|
|
289
|
+
tree_binning=tree_binning,
|
|
290
|
+
random_state=self.tree_binning_seed,
|
|
291
|
+
return_edges=True,
|
|
292
|
+
bin_colnames=(f"_bin_num_{var}", f"_bin_range_{var}"),
|
|
293
|
+
ascending=True
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
train_woe_table, train_woe_mapping_dict = self._get_woe_table(train_res, var, dep)
|
|
297
|
+
|
|
298
|
+
if check_monotonicity_flag:
|
|
299
|
+
if not is_monotonic(train_woe_table.query("MIN != MAX"), "WOE")[0]:
|
|
300
|
+
logging.warning(f"WARNING: {var} WOE values are NOT monotonic in Train Dataset!")
|
|
301
|
+
|
|
302
|
+
# WOE Mapping to DataFrame
|
|
303
|
+
train_res[f"{var}_woe"] = train_res[f"_bin_range_{var}"].map(train_woe_mapping_dict)
|
|
304
|
+
|
|
305
|
+
# Drop Bin Info
|
|
306
|
+
if self.drop_bin_info:
|
|
307
|
+
train_res = train_res.drop(columns=[f"_bin_num_{var}", f"_bin_range_{var}"])
|
|
308
|
+
|
|
309
|
+
if oot_df is not None:
|
|
310
|
+
woe_mapping_table = train_woe_table.copy()
|
|
311
|
+
woe_mapping_table["BIN_RANGE"] = woe_mapping_table[f"_bin_range_{var}"]
|
|
312
|
+
woe_mapping_table["BIN_NUM"] = woe_mapping_table[f"_bin_num_{var}"]
|
|
313
|
+
woe_mapping_table["VAR"] = var
|
|
314
|
+
oot_res = _mapping_woe_single_var(
|
|
315
|
+
data=oot_df, var=var, woe_mapping_table=woe_mapping_table
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if self.ret_woe_table and oot_df is not None:
|
|
319
|
+
return train_res, oot_res, train_woe_table
|
|
320
|
+
|
|
321
|
+
if self.ret_woe_table and oot_df is None:
|
|
322
|
+
return train_res, train_woe_table
|
|
323
|
+
|
|
324
|
+
if oot_df is not None:
|
|
325
|
+
return train_res, oot_res
|
|
326
|
+
|
|
327
|
+
return train_res
|
|
328
|
+
|
|
329
|
+
def transform(self, train_df, varlist, dep, oot_df=None, check_monotonicity_flag=False):
|
|
330
|
+
"""对多个变量进行WOE转换。
|
|
331
|
+
|
|
332
|
+
参数:
|
|
333
|
+
-----------
|
|
334
|
+
train_df : pd.DataFrame
|
|
335
|
+
训练数据集
|
|
336
|
+
varlist : list
|
|
337
|
+
待转换的变量名列表
|
|
338
|
+
dep : str
|
|
339
|
+
目标变量(因变量)名
|
|
340
|
+
oot_df : pd.DataFrame, optional
|
|
341
|
+
验证/测试数据集,默认为None
|
|
342
|
+
check_monotonicity_flag : bool, optional
|
|
343
|
+
是否检查单调性,默认为False
|
|
344
|
+
|
|
345
|
+
返回:
|
|
346
|
+
--------
|
|
347
|
+
tuple/list
|
|
348
|
+
返回结果的字典和WOE映射表。
|
|
349
|
+
字典键为'TRAIN'和'OOT'(当oot_df不为None时)
|
|
350
|
+
|
|
351
|
+
示例:
|
|
352
|
+
--------
|
|
353
|
+
>>> transformer = WOETransformer(nbins=10)
|
|
354
|
+
>>> result = transformer.transform(df, ['var1', 'var2'], 'target')
|
|
355
|
+
"""
|
|
356
|
+
fnl_res = {}
|
|
357
|
+
for i, var in enumerate(varlist):
|
|
358
|
+
woe_res = self.transform_single(
|
|
359
|
+
train_df=train_df,
|
|
360
|
+
var=var,
|
|
361
|
+
dep=dep,
|
|
362
|
+
oot_df=oot_df,
|
|
363
|
+
check_monotonicity_flag=check_monotonicity_flag
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
train_df = woe_res[0]
|
|
367
|
+
if oot_df is not None:
|
|
368
|
+
oot_df = woe_res[1]
|
|
369
|
+
|
|
370
|
+
if i == 0:
|
|
371
|
+
train_woe_table = woe_res[-1]
|
|
372
|
+
train_woe_table["VAR"] = var
|
|
373
|
+
train_woe_table = train_woe_table.rename(columns={
|
|
374
|
+
f"_bin_num_{var}": "BIN_NUM",
|
|
375
|
+
f"_bin_range_{var}": "BIN_RANGE"
|
|
376
|
+
})
|
|
377
|
+
else:
|
|
378
|
+
train_woe_table_append = woe_res[-1]
|
|
379
|
+
train_woe_table_append["VAR"] = var
|
|
380
|
+
train_woe_table_append = train_woe_table_append.rename(columns={
|
|
381
|
+
f"_bin_num_{var}": "BIN_NUM",
|
|
382
|
+
f"_bin_range_{var}": "BIN_RANGE"
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
if i > 0:
|
|
386
|
+
train_woe_table = pd.concat([train_woe_table, train_woe_table_append])
|
|
387
|
+
|
|
388
|
+
fnl_res["TRAIN"] = train_df
|
|
389
|
+
if oot_df is not None:
|
|
390
|
+
fnl_res["OOT"] = oot_df
|
|
391
|
+
|
|
392
|
+
if self.ret_woe_table:
|
|
393
|
+
return fnl_res, train_woe_table
|
|
394
|
+
|
|
395
|
+
return fnl_res
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def convert_single_var_woe(data, var, woe_mapping_table, missing_ref=None, ret_bin_no=False):
|
|
399
|
+
"""将原始变量值转换为WOE值。
|
|
400
|
+
|
|
401
|
+
根据预计算的WOE映射表,对指定变量进行WOE转换。
|
|
402
|
+
支持缺失值处理和分箱编号返回。
|
|
403
|
+
|
|
404
|
+
参数:
|
|
405
|
+
-----------
|
|
406
|
+
data : pd.DataFrame
|
|
407
|
+
输入的数据框
|
|
408
|
+
var : str
|
|
409
|
+
待转换的变量名
|
|
410
|
+
woe_mapping_table : pd.DataFrame
|
|
411
|
+
WOE映射表,包含bin_no、bin_value、woe和n列
|
|
412
|
+
missing_ref : any, optional
|
|
413
|
+
缺失值参考值,默认为None
|
|
414
|
+
ret_bin_no : bool, optional
|
|
415
|
+
是否返回分箱编号而非WOE值,默认为False
|
|
416
|
+
|
|
417
|
+
返回:
|
|
418
|
+
--------
|
|
419
|
+
pd.Series/pd.Categorical
|
|
420
|
+
转换后的WOE值或分箱编号
|
|
421
|
+
|
|
422
|
+
示例:
|
|
423
|
+
--------
|
|
424
|
+
>>> woe_values = convert_single_var_woe(df, 'age', woe_table)
|
|
425
|
+
"""
|
|
426
|
+
var_woe_mapping = woe_mapping_table.query(f"var_name == '{var}_woe'")
|
|
427
|
+
var_woe_mapping = var_woe_mapping[["bin_no", "bin_value", "woe", "n"]]
|
|
428
|
+
|
|
429
|
+
var_woe_mapping["bin_value_list"] = var_woe_mapping["bin_value"].apply(
|
|
430
|
+
lambda x: x.replace("[", "").replace(")", "").split(",")
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
woe_mapping_dict = dict(zip(var_woe_mapping['bin_no'], var_woe_mapping['woe']))
|
|
434
|
+
bin_range = [
|
|
435
|
+
float(v.strip()) if v.strip() != 'inf' else np.inf
|
|
436
|
+
for x in var_woe_mapping["bin_value_list"].tolist()
|
|
437
|
+
for v in x
|
|
438
|
+
]
|
|
439
|
+
|
|
440
|
+
unique_range = []
|
|
441
|
+
for x in bin_range:
|
|
442
|
+
if x not in unique_range:
|
|
443
|
+
unique_range.append(x)
|
|
444
|
+
|
|
445
|
+
if missing_ref:
|
|
446
|
+
var_serires = data[var].fillna(missing_ref)
|
|
447
|
+
|
|
448
|
+
bin_no_transform = pd.cut(
|
|
449
|
+
var_serires,
|
|
450
|
+
bins=unique_range,
|
|
451
|
+
right=False,
|
|
452
|
+
labels=[x for x in range(0, len(unique_range) - 1)]
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
if ret_bin_no:
|
|
456
|
+
return bin_no_transform
|
|
457
|
+
|
|
458
|
+
woe_res = bin_no_transform.map(woe_mapping_dict)
|
|
459
|
+
|
|
460
|
+
return woe_res
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class WOEMappingTransformer:
|
|
464
|
+
"""基于WOE映射表的转换器。
|
|
465
|
+
|
|
466
|
+
使用预计算的WOE映射表对新数据进行WOE转换,
|
|
467
|
+
支持单变量和多变量批量处理。
|
|
468
|
+
|
|
469
|
+
参数:
|
|
470
|
+
-----------
|
|
471
|
+
woe_mapping_table : pd.DataFrame
|
|
472
|
+
WOE映射表
|
|
473
|
+
missing_ref : any, optional
|
|
474
|
+
缺失值参考值,默认为None
|
|
475
|
+
ret_bin_no : bool, optional
|
|
476
|
+
是否返回分箱编号,默认为False
|
|
477
|
+
ret_category : bool, optional
|
|
478
|
+
是否返回分类类型,默认为False
|
|
479
|
+
rename_orig_var : bool, optional
|
|
480
|
+
是否重命名原始变量,默认为False
|
|
481
|
+
suffix : str, optional
|
|
482
|
+
变量名后缀,默认为''
|
|
483
|
+
|
|
484
|
+
示例:
|
|
485
|
+
--------
|
|
486
|
+
>>> transformer = WOEMappingTransformer(woe_mapping_table)
|
|
487
|
+
>>> result = transformer.transform(df, ['var1', 'var2'])
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
def __init__(self, woe_mapping_table, missing_ref=None, ret_bin_no=False,
|
|
491
|
+
ret_category=False, rename_orig_var=False, suffix=''):
|
|
492
|
+
"""初始化WOE映射转换器。
|
|
493
|
+
|
|
494
|
+
参数:
|
|
495
|
+
-----------
|
|
496
|
+
woe_mapping_table : pd.DataFrame
|
|
497
|
+
WOE映射表
|
|
498
|
+
missing_ref : any, optional
|
|
499
|
+
缺失值参考值
|
|
500
|
+
ret_bin_no : bool, optional
|
|
501
|
+
是否返回分箱编号
|
|
502
|
+
ret_category : bool, optional
|
|
503
|
+
是否返回分类类型
|
|
504
|
+
rename_orig_var : bool, optional
|
|
505
|
+
是否重命名原始变量
|
|
506
|
+
suffix : str, optional
|
|
507
|
+
变量名后缀
|
|
508
|
+
"""
|
|
509
|
+
self.woe_mapping_table = woe_mapping_table
|
|
510
|
+
self.missing_ref = missing_ref
|
|
511
|
+
self.ret_bin_no = ret_bin_no
|
|
512
|
+
self.ret_category = ret_category
|
|
513
|
+
self.rename_orig_var = rename_orig_var
|
|
514
|
+
self.suffix = suffix
|
|
515
|
+
|
|
516
|
+
def transform_single(self, data, var):
|
|
517
|
+
"""对单个变量进行WOE转换。
|
|
518
|
+
|
|
519
|
+
参数:
|
|
520
|
+
-----------
|
|
521
|
+
data : pd.DataFrame
|
|
522
|
+
输入的数据框
|
|
523
|
+
var : str
|
|
524
|
+
待转换的变量名
|
|
525
|
+
|
|
526
|
+
返回:
|
|
527
|
+
--------
|
|
528
|
+
pd.DataFrame
|
|
529
|
+
转换后的数据框
|
|
530
|
+
"""
|
|
531
|
+
if self.rename_orig_var:
|
|
532
|
+
data = data.rename(columns={var: (var + self.suffix)})
|
|
533
|
+
data[var] = data[(var + self.suffix)].copy()
|
|
534
|
+
data[var] = convert_single_var_woe(
|
|
535
|
+
data, var, self.woe_mapping_table,
|
|
536
|
+
self.missing_ref, self.ret_bin_no
|
|
537
|
+
)
|
|
538
|
+
if not self.ret_category:
|
|
539
|
+
data[var] = data[var].astype(float)
|
|
540
|
+
else:
|
|
541
|
+
data[var + self.suffix] = convert_single_var_woe(
|
|
542
|
+
data, var, self.woe_mapping_table,
|
|
543
|
+
self.missing_ref, self.ret_bin_no
|
|
544
|
+
)
|
|
545
|
+
if not self.ret_category:
|
|
546
|
+
data[var + self.suffix] = data[var + self.suffix].astype(float)
|
|
547
|
+
|
|
548
|
+
return data
|
|
549
|
+
|
|
550
|
+
def transform(self, data, varlist):
|
|
551
|
+
"""对多个变量进行WOE转换。
|
|
552
|
+
|
|
553
|
+
参数:
|
|
554
|
+
-----------
|
|
555
|
+
data : pd.DataFrame
|
|
556
|
+
输入的数据框
|
|
557
|
+
varlist : list
|
|
558
|
+
待转换的变量名列表
|
|
559
|
+
|
|
560
|
+
返回:
|
|
561
|
+
--------
|
|
562
|
+
pd.DataFrame
|
|
563
|
+
转换后的数据框
|
|
564
|
+
|
|
565
|
+
示例:
|
|
566
|
+
--------
|
|
567
|
+
>>> transformer = WOEMappingTransformer(woe_mapping_table)
|
|
568
|
+
>>> result = transformer.transform(df, ['var1', 'var2'])
|
|
569
|
+
"""
|
|
570
|
+
res = data.copy()
|
|
571
|
+
for var in varlist:
|
|
572
|
+
res = self.transform_single(res, var)
|
|
573
|
+
return res
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def woe_transform_cdaml(data, varlist, woe_mapping_path, missing_ref=None,
|
|
577
|
+
ret_bin_no=False, ret_category=False, rename_orig_var=False, suffix=''):
|
|
578
|
+
"""使用cdaml包进行WOE转换。
|
|
579
|
+
|
|
580
|
+
从文件路径读取WOE映射表或直接使用映射表数据框,
|
|
581
|
+
对指定变量列表进行WOE转换。
|
|
582
|
+
|
|
583
|
+
参数:
|
|
584
|
+
-----------
|
|
585
|
+
data : pd.DataFrame
|
|
586
|
+
输入的数据框
|
|
587
|
+
varlist : list
|
|
588
|
+
待转换的变量名列表
|
|
589
|
+
woe_mapping_path : str/pd.DataFrame
|
|
590
|
+
WOE映射表文件路径或数据框
|
|
591
|
+
missing_ref : any, optional
|
|
592
|
+
缺失值参考值,默认为None
|
|
593
|
+
ret_bin_no : bool, optional
|
|
594
|
+
是否返回分箱编号,默认为False
|
|
595
|
+
ret_category : bool, optional
|
|
596
|
+
是否返回分类类型,默认为False
|
|
597
|
+
rename_orig_var : bool, optional
|
|
598
|
+
是否重命名原始变量,默认为False
|
|
599
|
+
suffix : str, optional
|
|
600
|
+
变量名后缀,默认为''
|
|
601
|
+
|
|
602
|
+
返回:
|
|
603
|
+
--------
|
|
604
|
+
pd.DataFrame
|
|
605
|
+
转换后的数据框
|
|
606
|
+
|
|
607
|
+
示例:
|
|
608
|
+
--------
|
|
609
|
+
>>> result = woe_transform_cdaml(df, ['var1', 'var2'], 'woe_mapping.csv')
|
|
610
|
+
"""
|
|
611
|
+
woe_mapping_table = pd.read_csv(woe_mapping_path) if isinstance(woe_mapping_path, str) else woe_mapping_path
|
|
612
|
+
woe_mapping_table.columns = [x.lower() for x in woe_mapping_table.columns]
|
|
613
|
+
|
|
614
|
+
transformer = WOEMappingTransformer(
|
|
615
|
+
woe_mapping_table=woe_mapping_table,
|
|
616
|
+
missing_ref=missing_ref,
|
|
617
|
+
ret_bin_no=ret_bin_no,
|
|
618
|
+
ret_category=ret_category,
|
|
619
|
+
rename_orig_var=rename_orig_var,
|
|
620
|
+
suffix=suffix
|
|
621
|
+
)
|
|
622
|
+
return transformer.transform(data, varlist)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def get_woe_table(data, var, dep, grp_name=None, nbins=10, precision=5,
|
|
626
|
+
min_bin_prop=0.05, include_missing=True, equal_freq=True,
|
|
627
|
+
fillna=-999999, chi2_config=None, tree_binning_seed=None, spec_values=None):
|
|
628
|
+
"""获取WOE分箱表。
|
|
629
|
+
|
|
630
|
+
对指定变量进行分箱处理,计算各分箱的WOE值、IV值等统计量。
|
|
631
|
+
支持分组分析和单调性检验。
|
|
632
|
+
|
|
633
|
+
参数:
|
|
634
|
+
-----------
|
|
635
|
+
data : pd.DataFrame
|
|
636
|
+
输入的数据框
|
|
637
|
+
var : str
|
|
638
|
+
待分析的变量名
|
|
639
|
+
dep : str
|
|
640
|
+
目标变量(因变量)名
|
|
641
|
+
grp_name : str, optional
|
|
642
|
+
分组变量名,默认为None
|
|
643
|
+
nbins : int, optional
|
|
644
|
+
分箱数量,默认为10
|
|
645
|
+
precision : int, optional
|
|
646
|
+
计算精度,默认为5
|
|
647
|
+
min_bin_prop : float, optional
|
|
648
|
+
每个分箱的最小样本比例,默认为0.05
|
|
649
|
+
include_missing : bool, optional
|
|
650
|
+
是否将缺失值作为单独分箱,默认为True
|
|
651
|
+
equal_freq : bool, optional
|
|
652
|
+
是否使用等频分箱,默认为True
|
|
653
|
+
fillna : int/float, optional
|
|
654
|
+
缺失值填充值,默认为-999999
|
|
655
|
+
chi2_config : tuple, optional
|
|
656
|
+
卡方分箱配置,(init_bins, p_value)元组,默认为None
|
|
657
|
+
tree_binning_seed : int, optional
|
|
658
|
+
决策树分箱随机种子,默认为None
|
|
659
|
+
spec_values : list, optional
|
|
660
|
+
特殊值列表,默认为None
|
|
661
|
+
|
|
662
|
+
返回:
|
|
663
|
+
--------
|
|
664
|
+
tuple/pd.DataFrame
|
|
665
|
+
当grp_name不为None时返回(woe_table, grp_summary, grp_woe_pvt)元组;
|
|
666
|
+
当grp_name为None时返回(woe_table, is_monotonic, direction, slope)元组
|
|
667
|
+
|
|
668
|
+
示例:
|
|
669
|
+
--------
|
|
670
|
+
>>> woe_table, is_mono, direction, slope = get_woe_table(df, 'age', 'target')
|
|
671
|
+
"""
|
|
672
|
+
from pandas.api.types import is_numeric_dtype, is_string_dtype
|
|
673
|
+
|
|
674
|
+
if spec_values is None:
|
|
675
|
+
spec_values = []
|
|
676
|
+
|
|
677
|
+
chi2_method = False
|
|
678
|
+
if chi2_config:
|
|
679
|
+
chi2_method = True
|
|
680
|
+
else:
|
|
681
|
+
chi2_config = (100, 0.99)
|
|
682
|
+
|
|
683
|
+
tree_binning = False
|
|
684
|
+
if tree_binning_seed:
|
|
685
|
+
tree_binning = True
|
|
686
|
+
|
|
687
|
+
from Modeling_Tool.Eval.Model_Eval_Tool import get_gains_table
|
|
688
|
+
|
|
689
|
+
gains_table = get_gains_table(
|
|
690
|
+
data=data,
|
|
691
|
+
score=var,
|
|
692
|
+
dep=dep,
|
|
693
|
+
nbins=nbins,
|
|
694
|
+
precision=precision,
|
|
695
|
+
min_bin_prop=min_bin_prop,
|
|
696
|
+
include_missing=include_missing,
|
|
697
|
+
equal_freq=equal_freq,
|
|
698
|
+
chi2_method=chi2_method,
|
|
699
|
+
chi2_p=chi2_config[1],
|
|
700
|
+
tree_binning=tree_binning,
|
|
701
|
+
init_equi_bins=chi2_config[0],
|
|
702
|
+
fillna=fillna,
|
|
703
|
+
spec_values=spec_values,
|
|
704
|
+
sync_range=True,
|
|
705
|
+
grp_name=grp_name,
|
|
706
|
+
retSummary=False,
|
|
707
|
+
random_state=tree_binning_seed,
|
|
708
|
+
ascending=True,
|
|
709
|
+
withSummary=False
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
gains_table = gains_table.reset_index(drop=False).rename(columns={
|
|
713
|
+
"_bin_num": "BIN_NUM", "_bin_range": "BIN_RANGE"
|
|
714
|
+
})
|
|
715
|
+
woe_cols = ["BIN_NUM", "BIN_RANGE", "MIN", "MAX", "N", "RANK_ORDER_BUMP", "WOE", "IV", "AVG_BAD"]
|
|
716
|
+
if grp_name:
|
|
717
|
+
woe_cols += [grp_name]
|
|
718
|
+
woe_table = gains_table[woe_cols]
|
|
719
|
+
avg_bad_df = woe_table.loc[woe_table["BIN_NUM"] != "Grand Summary", :]
|
|
720
|
+
|
|
721
|
+
if include_missing:
|
|
722
|
+
monoto_info = is_monotonic(avg_bad_df.iloc[1:, :], "AVG_BAD")
|
|
723
|
+
else:
|
|
724
|
+
monoto_info = is_monotonic(avg_bad_df, "AVG_BAD")
|
|
725
|
+
|
|
726
|
+
dep_slope = calculate_slope_manual(avg_bad_df, "AVG_BAD")
|
|
727
|
+
direction = 1 if dep_slope > 0 else -1 if dep_slope < 0 else 0
|
|
728
|
+
|
|
729
|
+
if grp_name:
|
|
730
|
+
slope_grp = pd.DataFrame(
|
|
731
|
+
gains_table.groupby([grp_name]).apply(calculate_slope_manual, "AVG_BAD"),
|
|
732
|
+
columns=["SLOPE"]
|
|
733
|
+
)
|
|
734
|
+
grp_summary = gains_table.groupby([grp_name]).agg(
|
|
735
|
+
N=("N", sum), IV=("IV", sum), KS=("KS_PER_BIN", max),
|
|
736
|
+
BTM_LIFT=("LIFT", min), TOP_LIFT=("LIFT", max)
|
|
737
|
+
).merge(slope_grp, left_index=True, right_index=True)
|
|
738
|
+
grp_summary['direction'] = grp_summary['SLOPE'].apply(
|
|
739
|
+
lambda x: 1 if x > 0 else -1 if x < 0 else 0
|
|
740
|
+
)
|
|
741
|
+
grp_woe_pvt = gains_table.pivot_table(
|
|
742
|
+
index=["BIN_NUM", "BIN_RANGE"],
|
|
743
|
+
columns=[grp_name],
|
|
744
|
+
values=["WOE", 'AVG_BAD']
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
return woe_table, grp_summary, grp_woe_pvt
|
|
748
|
+
|
|
749
|
+
return (woe_table, monoto_info[0], direction, dep_slope)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def plot_monotonicity_check(data, column, title=None, include_missing=True):
|
|
753
|
+
"""绘制序列并标注其单调性。
|
|
754
|
+
|
|
755
|
+
创建折线图可视化指定列的值分布,
|
|
756
|
+
并在图上标注单调性检验结果。
|
|
757
|
+
|
|
758
|
+
参数:
|
|
759
|
+
-----------
|
|
760
|
+
data : pd.DataFrame
|
|
761
|
+
包含数据的数据框
|
|
762
|
+
column : str
|
|
763
|
+
要绑制的列名
|
|
764
|
+
title : str, optional
|
|
765
|
+
图表标题,默认为None(自动生成)
|
|
766
|
+
include_missing : bool, optional
|
|
767
|
+
是否包含缺失值处理,默认为True
|
|
768
|
+
|
|
769
|
+
返回:
|
|
770
|
+
--------
|
|
771
|
+
None
|
|
772
|
+
直接显示图表
|
|
773
|
+
|
|
774
|
+
示例:
|
|
775
|
+
--------
|
|
776
|
+
>>> plot_monotonicity_check(df, 'woe_values')
|
|
777
|
+
"""
|
|
778
|
+
import matplotlib.pyplot as plt
|
|
779
|
+
|
|
780
|
+
series = data[column]
|
|
781
|
+
|
|
782
|
+
# 检查单调性
|
|
783
|
+
if include_missing:
|
|
784
|
+
is_mono, direction = is_monotonic(
|
|
785
|
+
data.iloc[1:, :], "AVG_BAD", strict=True, handle_nan='drop'
|
|
786
|
+
)
|
|
787
|
+
else:
|
|
788
|
+
is_mono, direction = is_monotonic(data, "AVG_BAD", strict=True, handle_nan='drop')
|
|
789
|
+
|
|
790
|
+
# 创建图表
|
|
791
|
+
plt.figure(figsize=(10, 6))
|
|
792
|
+
plt.plot(series.index, series.values, 'bo-', linewidth=2, markersize=6)
|
|
793
|
+
|
|
794
|
+
# 添加标题和标签
|
|
795
|
+
if title is None:
|
|
796
|
+
title = f"Monotonicity Check: {'Strict' if is_mono else 'Not'} Monotonic {direction if is_mono else ''}"
|
|
797
|
+
plt.title(title, fontsize=14)
|
|
798
|
+
plt.xlabel('Index')
|
|
799
|
+
plt.ylabel('Value')
|
|
800
|
+
|
|
801
|
+
# 添加网格
|
|
802
|
+
plt.grid(True, alpha=0.3)
|
|
803
|
+
|
|
804
|
+
# 显示单调性信息
|
|
805
|
+
plt.text(
|
|
806
|
+
0.02, 0.98,
|
|
807
|
+
f"Strict Monotonic: {is_mono}\n Direction: {direction}",
|
|
808
|
+
transform=plt.gca().transAxes,
|
|
809
|
+
verticalalignment='top',
|
|
810
|
+
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8)
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
plt.tight_layout()
|
|
814
|
+
plt.show()
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def woe_transform(train_df, var, dep, nbins, oot_df=None, chi2_config=None, tree_binning_seed=None,
|
|
818
|
+
precision=5, min_bin_prop=0.05, include_missing=False, equal_freq=True,
|
|
819
|
+
ascending=True, fillna=-999999, spec_values=None, drop_bin_info=True,
|
|
820
|
+
ret_woe_table=True, check_monotonicity=False):
|
|
821
|
+
"""将变量转换为WOE值。
|
|
822
|
+
|
|
823
|
+
对单个变量进行分箱并计算WOE值,支持训练集和验证集的转换。
|
|
824
|
+
基于WOETransformer类实现。
|
|
825
|
+
|
|
826
|
+
参数:
|
|
827
|
+
-----------
|
|
828
|
+
train_df : pd.DataFrame
|
|
829
|
+
训练数据集
|
|
830
|
+
var : str
|
|
831
|
+
待转换的变量名
|
|
832
|
+
dep : str
|
|
833
|
+
目标变量(因变量)名
|
|
834
|
+
nbins : int
|
|
835
|
+
分箱数量
|
|
836
|
+
oot_df : pd.DataFrame, optional
|
|
837
|
+
验证/测试数据集,默认为None
|
|
838
|
+
chi2_config : tuple, optional
|
|
839
|
+
卡方分箱配置,(init_bins, p_value)元组,默认为None
|
|
840
|
+
tree_binning_seed : int, optional
|
|
841
|
+
决策树分箱随机种子,默认为None
|
|
842
|
+
precision : int, optional
|
|
843
|
+
计算精度,默认为5
|
|
844
|
+
min_bin_prop : float, optional
|
|
845
|
+
每个分箱的最小样本比例,默认为0.05
|
|
846
|
+
include_missing : bool, optional
|
|
847
|
+
是否将缺失值作为单独分箱,默认为False
|
|
848
|
+
equal_freq : bool, optional
|
|
849
|
+
是否使用等频分箱,默认为True
|
|
850
|
+
ascending : bool, optional
|
|
851
|
+
是否升序排列,默认为True
|
|
852
|
+
fillna : int/float, optional
|
|
853
|
+
缺失值填充值,默认为-999999
|
|
854
|
+
spec_values : list, optional
|
|
855
|
+
特殊值列表,默认为None
|
|
856
|
+
drop_bin_info : bool, optional
|
|
857
|
+
是否删除中间分箱信息列,默认为True
|
|
858
|
+
ret_woe_table : bool, optional
|
|
859
|
+
是否返回WOE映射表,默认为True
|
|
860
|
+
check_monotonicity : bool, optional
|
|
861
|
+
是否检查单调性,默认为False
|
|
862
|
+
|
|
863
|
+
返回:
|
|
864
|
+
--------
|
|
865
|
+
tuple/list/pd.DataFrame
|
|
866
|
+
根据参数返回不同的组合:
|
|
867
|
+
- ret_woe_table=True, oot_df=None: (train_res, train_woe_table)
|
|
868
|
+
- ret_woe_table=True, oot_df不为None: (train_res, oot_res, train_woe_table)
|
|
869
|
+
- ret_woe_table=False, oot_df不为None: (train_res, oot_res)
|
|
870
|
+
- ret_woe_table=False, oot_df=None: train_res
|
|
871
|
+
|
|
872
|
+
示例:
|
|
873
|
+
--------
|
|
874
|
+
>>> train_res, woe_table = woe_transform(df, 'age', 'target', nbins=10)
|
|
875
|
+
"""
|
|
876
|
+
if spec_values is None:
|
|
877
|
+
spec_values = []
|
|
878
|
+
|
|
879
|
+
transformer = WOETransformer(
|
|
880
|
+
nbins=nbins,
|
|
881
|
+
precision=precision,
|
|
882
|
+
min_bin_prop=min_bin_prop,
|
|
883
|
+
include_missing=include_missing,
|
|
884
|
+
equal_freq=equal_freq,
|
|
885
|
+
fillna=fillna,
|
|
886
|
+
chi2_config=chi2_config,
|
|
887
|
+
tree_binning_seed=tree_binning_seed,
|
|
888
|
+
spec_values=spec_values,
|
|
889
|
+
drop_bin_info=drop_bin_info,
|
|
890
|
+
ret_woe_table=ret_woe_table
|
|
891
|
+
)
|
|
892
|
+
return transformer.transform_single(
|
|
893
|
+
train_df=train_df,
|
|
894
|
+
var=var,
|
|
895
|
+
dep=dep,
|
|
896
|
+
oot_df=oot_df,
|
|
897
|
+
check_monotonicity_flag=check_monotonicity
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def woe_transformation(train_df, varlist, dep, oot_df=None, nbins=10, chi2_config=None,
|
|
902
|
+
tree_binning_seed=None, precision=5, min_bin_prop=0.05,
|
|
903
|
+
include_missing=False, equal_freq=True, fillna=-999999,
|
|
904
|
+
spec_values=None, drop_bin_info=True, ret_woe_table=True):
|
|
905
|
+
"""对变量列表进行WOE转换。
|
|
906
|
+
|
|
907
|
+
批量对多个变量进行WOE分箱和转换,支持训练集和验证集。
|
|
908
|
+
基于WOETransformer类实现。
|
|
909
|
+
|
|
910
|
+
参数:
|
|
911
|
+
-----------
|
|
912
|
+
train_df : pd.DataFrame
|
|
913
|
+
训练数据集
|
|
914
|
+
varlist : list
|
|
915
|
+
待转换的变量名列表
|
|
916
|
+
dep : str
|
|
917
|
+
目标变量(因变量)名
|
|
918
|
+
oot_df : pd.DataFrame, optional
|
|
919
|
+
验证/测试数据集,默认为None
|
|
920
|
+
nbins : int, optional
|
|
921
|
+
分箱数量,默认为10
|
|
922
|
+
chi2_config : tuple, optional
|
|
923
|
+
卡方分箱配置,(init_bins, p_value)元组,默认为None
|
|
924
|
+
tree_binning_seed : int, optional
|
|
925
|
+
决策树分箱随机种子,默认为None
|
|
926
|
+
precision : int, optional
|
|
927
|
+
计算精度,默认为5
|
|
928
|
+
min_bin_prop : float, optional
|
|
929
|
+
每个分箱的最小样本比例,默认为0.05
|
|
930
|
+
include_missing : bool, optional
|
|
931
|
+
是否将缺失值作为单独分箱,默认为False
|
|
932
|
+
equal_freq : bool, optional
|
|
933
|
+
是否使用等频分箱,默认为True
|
|
934
|
+
fillna : int/float, optional
|
|
935
|
+
缺失值填充值,默认为-999999
|
|
936
|
+
spec_values : list, optional
|
|
937
|
+
特殊值列表,默认为None
|
|
938
|
+
drop_bin_info : bool, optional
|
|
939
|
+
是否删除中间分箱信息列,默认为True
|
|
940
|
+
ret_woe_table : bool, optional
|
|
941
|
+
是否返回WOE映射表,默认为True
|
|
942
|
+
|
|
943
|
+
返回:
|
|
944
|
+
--------
|
|
945
|
+
tuple
|
|
946
|
+
(结果字典, train_woe_table) 元组。
|
|
947
|
+
结果字典包含'TRAIN'键,验证集通过'OOT'键(当oot_df不为None时)
|
|
948
|
+
|
|
949
|
+
示例:
|
|
950
|
+
--------
|
|
951
|
+
>>> result, woe_table = woe_transformation(df, ['var1', 'var2'], 'target')
|
|
952
|
+
"""
|
|
953
|
+
if spec_values is None:
|
|
954
|
+
spec_values = []
|
|
955
|
+
|
|
956
|
+
transformer = WOETransformer(
|
|
957
|
+
nbins=nbins,
|
|
958
|
+
precision=precision,
|
|
959
|
+
min_bin_prop=min_bin_prop,
|
|
960
|
+
include_missing=include_missing,
|
|
961
|
+
equal_freq=equal_freq,
|
|
962
|
+
fillna=fillna,
|
|
963
|
+
chi2_config=chi2_config,
|
|
964
|
+
tree_binning_seed=tree_binning_seed,
|
|
965
|
+
spec_values=spec_values,
|
|
966
|
+
drop_bin_info=drop_bin_info,
|
|
967
|
+
ret_woe_table=ret_woe_table
|
|
968
|
+
)
|
|
969
|
+
return transformer.transform(train_df, varlist, dep, oot_df)
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def _mapping_woe_single_var(data, var, woe_mapping_table, suffix="_woe", drop_bin_info=True):
|
|
973
|
+
"""基于WOE映射表为单个变量映射WOE值。
|
|
974
|
+
|
|
975
|
+
使用预计算的WOE映射表对新数据进行转换,
|
|
976
|
+
验证单调性并检查映射结果。
|
|
977
|
+
|
|
978
|
+
参数:
|
|
979
|
+
-----------
|
|
980
|
+
data : pd.DataFrame
|
|
981
|
+
输入的数据框
|
|
982
|
+
var : str
|
|
983
|
+
待映射的变量名
|
|
984
|
+
woe_mapping_table : pd.DataFrame
|
|
985
|
+
WOE映射表
|
|
986
|
+
suffix : str, optional
|
|
987
|
+
变量名后缀,默认为'_woe'
|
|
988
|
+
drop_bin_info : bool, optional
|
|
989
|
+
是否删除中间分箱信息列,默认为True
|
|
990
|
+
|
|
991
|
+
返回:
|
|
992
|
+
--------
|
|
993
|
+
pd.DataFrame
|
|
994
|
+
映射后的数据框
|
|
995
|
+
|
|
996
|
+
注意:
|
|
997
|
+
--------
|
|
998
|
+
当映射失败时会输出警告信息
|
|
999
|
+
"""
|
|
1000
|
+
# from Modeling_Tool.Modeling_Tool.Model_Eval_Tool import run_binning
|
|
1001
|
+
# from 代码优化.Model_Eval_Tool import get_bin_range_list
|
|
1002
|
+
|
|
1003
|
+
left = woe_mapping_table.loc[woe_mapping_table["VAR"] == var, "BIN_RANGE"].str[0].unique()[0]
|
|
1004
|
+
right = woe_mapping_table.loc[woe_mapping_table["VAR"] == var, "BIN_RANGE"].str[-1].unique()[0]
|
|
1005
|
+
|
|
1006
|
+
include_lowest = False if left == '(' else True
|
|
1007
|
+
right = False if right == ')' else True
|
|
1008
|
+
|
|
1009
|
+
var_woe_mapping = woe_mapping_table.query(f"VAR == '{var}'")
|
|
1010
|
+
var_woe_mapping = var_woe_mapping[["BIN_NUM", "BIN_RANGE", "WOE", "N"]]
|
|
1011
|
+
bin_range = get_bin_range_list(var_woe_mapping, col="BIN_RANGE")
|
|
1012
|
+
woe_mapping_dict = dict(zip(var_woe_mapping['BIN_RANGE'], var_woe_mapping['WOE']))
|
|
1013
|
+
|
|
1014
|
+
res, edges = run_binning(
|
|
1015
|
+
data=data,
|
|
1016
|
+
column=var,
|
|
1017
|
+
nbins=sorted(set(list(bin_range) + [-np.inf, np.inf])),
|
|
1018
|
+
include_missing=True,
|
|
1019
|
+
equal_freq=True,
|
|
1020
|
+
bin_colnames=(f"_BIN_NUM_", f"_BIN_RANGE_"),
|
|
1021
|
+
ascending=True,
|
|
1022
|
+
include_lowest=include_lowest,
|
|
1023
|
+
right=right
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
res[f"{var}{suffix}"] = res["_BIN_RANGE_"].map(woe_mapping_dict)
|
|
1027
|
+
|
|
1028
|
+
res_woe_table = res.groupby(["_BIN_RANGE_"]).agg(
|
|
1029
|
+
{f"_BIN_RANGE_": "count", var: [min, max], f"{var}{suffix}": [min, max, "mean"]}
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
check_res = res_woe_table.dropna()
|
|
1033
|
+
assert (check_res[(f"{var}{suffix}", "min")] == check_res[(f"{var}{suffix}", "max")]).all(), \
|
|
1034
|
+
f"Error occurred when mapping var: {var}{suffix}"
|
|
1035
|
+
assert data.shape[0] == res.shape[0]
|
|
1036
|
+
|
|
1037
|
+
missing_woe = pd.isnull(res[var + suffix]).sum() > 0
|
|
1038
|
+
if missing_woe:
|
|
1039
|
+
logging.warning(f"WARNING: Failed to Map WOE values for {pd.isnull(res[var + suffix]).sum()} Records!")
|
|
1040
|
+
|
|
1041
|
+
woe = pd.DataFrame(
|
|
1042
|
+
res_woe_table.loc[
|
|
1043
|
+
res_woe_table[(var, "min")] != res_woe_table[(var, "max")],
|
|
1044
|
+
(f"{var}_woe", "mean")
|
|
1045
|
+
]
|
|
1046
|
+
)
|
|
1047
|
+
monotonicity = is_monotonic(woe, (f"{var}_woe", "mean"))
|
|
1048
|
+
|
|
1049
|
+
if monotonicity[0]:
|
|
1050
|
+
logging.warning(f"WARNING: {var} WOE values are NOT monotonic in the given Dataset!")
|
|
1051
|
+
|
|
1052
|
+
if drop_bin_info:
|
|
1053
|
+
res = res.drop(columns=["_BIN_NUM_", "_BIN_RANGE_"])
|
|
1054
|
+
|
|
1055
|
+
return res
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def mapping_woe(data, varlist, woe_mapping_table, suffix="_woe", drop_bin_info = True):
|
|
1059
|
+
"""基于WOE映射表批量映射WOE值。
|
|
1060
|
+
|
|
1061
|
+
使用预计算的WOE映射表对多个变量进行WOE转换。
|
|
1062
|
+
|
|
1063
|
+
参数:
|
|
1064
|
+
-----------
|
|
1065
|
+
data : pd.DataFrame
|
|
1066
|
+
输入的数据框
|
|
1067
|
+
varlist : list
|
|
1068
|
+
待映射的变量名列表
|
|
1069
|
+
woe_mapping_table : pd.DataFrame
|
|
1070
|
+
WOE映射表
|
|
1071
|
+
suffix : str, optional
|
|
1072
|
+
变量名后缀,默认为'_woe'
|
|
1073
|
+
drop_bin_info : bool, optional
|
|
1074
|
+
是否删除中间分箱信息列,默认为True
|
|
1075
|
+
|
|
1076
|
+
返回:
|
|
1077
|
+
--------
|
|
1078
|
+
pd.DataFrame
|
|
1079
|
+
映射后的数据框
|
|
1080
|
+
|
|
1081
|
+
示例:
|
|
1082
|
+
--------
|
|
1083
|
+
>>> result = mapping_woe(df, ['var1', 'var2'], woe_table)
|
|
1084
|
+
"""
|
|
1085
|
+
transformer = WOEMappingTransformer(
|
|
1086
|
+
woe_mapping_table=woe_mapping_table,
|
|
1087
|
+
suffix=suffix
|
|
1088
|
+
# drop_bin_info=drop_bin_info
|
|
1089
|
+
)
|
|
1090
|
+
res = data.copy()
|
|
1091
|
+
for var in varlist:
|
|
1092
|
+
res = _mapping_woe_single_var(data=res, var=var, woe_mapping_table=woe_mapping_table,
|
|
1093
|
+
suffix=suffix, drop_bin_info=drop_bin_info)
|
|
1094
|
+
return res
|