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.
Files changed (79) hide show
  1. ExcelMaster/ExcelFormatTool.py +487 -0
  2. ExcelMaster/ExcelMaster.py +917 -0
  3. ExcelMaster/Template.py +525 -0
  4. ExcelMaster/Utility.py +233 -0
  5. ExcelMaster/__init__.py +3 -0
  6. Modeling_Tool/Core/Binning_Tool.py +1608 -0
  7. Modeling_Tool/Core/Binning_Tool.pyi +48 -0
  8. Modeling_Tool/Core/Check_DuckDB_Compatibility.py +835 -0
  9. Modeling_Tool/Core/Json_Data_Converter.py +621 -0
  10. Modeling_Tool/Core/Model_Registry_Tool.py +199 -0
  11. Modeling_Tool/Core/ODPS_Tool.py +284 -0
  12. Modeling_Tool/Core/Slope_Tool.py +356 -0
  13. Modeling_Tool/Core/Slope_Tool.pyi +31 -0
  14. Modeling_Tool/Core/XOR_Encryptor.py +207 -0
  15. Modeling_Tool/Core/XOR_Encryptor.pyi +26 -0
  16. Modeling_Tool/Core/__init__.py +99 -0
  17. Modeling_Tool/Core/kDataFrame.py +228 -0
  18. Modeling_Tool/Core/kDataFrame.pyi +43 -0
  19. Modeling_Tool/Core/sample_weight_utils.py +77 -0
  20. Modeling_Tool/Core/utils.py +2672 -0
  21. Modeling_Tool/Eval/Evaluation_Tool.py +1452 -0
  22. Modeling_Tool/Eval/Evaluation_Tool.pyi +47 -0
  23. Modeling_Tool/Eval/Model_Eval_Tool.py +2548 -0
  24. Modeling_Tool/Eval/Model_Eval_Tool.pyi +37 -0
  25. Modeling_Tool/Eval/__init__.py +54 -0
  26. Modeling_Tool/Eval/evaluate_model.py +2008 -0
  27. Modeling_Tool/Eval/evaluate_model.pyi +50 -0
  28. Modeling_Tool/Eval/weighted_eval_utils.py +326 -0
  29. Modeling_Tool/Explainability/Coalition_Structure.py +305 -0
  30. Modeling_Tool/Explainability/Model_Explainer.py +743 -0
  31. Modeling_Tool/Explainability/__init__.py +24 -0
  32. Modeling_Tool/Feature/Distribution_Tool.py +509 -0
  33. Modeling_Tool/Feature/Distribution_Tool.pyi +42 -0
  34. Modeling_Tool/Feature/Feature_Insights.py +762 -0
  35. Modeling_Tool/Feature/Feature_Insights.pyi +33 -0
  36. Modeling_Tool/Feature/PSI_Tool.py +1195 -0
  37. Modeling_Tool/Feature/PSI_Tool.pyi +29 -0
  38. Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +355 -0
  39. Modeling_Tool/Feature/__init__.py +40 -0
  40. Modeling_Tool/Model/Backward_Tool.py +778 -0
  41. Modeling_Tool/Model/Backward_Tool.pyi +45 -0
  42. Modeling_Tool/Model/GBM_Search_Tool.py +251 -0
  43. Modeling_Tool/Model/GBM_Tool.py +1610 -0
  44. Modeling_Tool/Model/GBM_Tool.pyi +90 -0
  45. Modeling_Tool/Model/LRM_Tool.py +1198 -0
  46. Modeling_Tool/Model/LRM_Tool.pyi +47 -0
  47. Modeling_Tool/Model/__init__.py +61 -0
  48. Modeling_Tool/Sample/Distribution_Adaptation.py +131 -0
  49. Modeling_Tool/Sample/Distribution_Adaptation.pyi +30 -0
  50. Modeling_Tool/Sample/Reject_Infer.py +413 -0
  51. Modeling_Tool/Sample/Reject_Infer.pyi +43 -0
  52. Modeling_Tool/Sample/Sample_Split.py +520 -0
  53. Modeling_Tool/Sample/Sample_Split.pyi +43 -0
  54. Modeling_Tool/Sample/__init__.py +31 -0
  55. Modeling_Tool/UAT/UAT_Consistency_Checker.py +1180 -0
  56. Modeling_Tool/UAT/__init__.py +19 -0
  57. Modeling_Tool/WOE/WOE_Adapter.py +204 -0
  58. Modeling_Tool/WOE/WOE_Adapter.pyi +21 -0
  59. Modeling_Tool/WOE/WOE_Master.py +491 -0
  60. Modeling_Tool/WOE/WOE_Master.pyi +40 -0
  61. Modeling_Tool/WOE/WOE_Monotone_Binner.py +3324 -0
  62. Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +71 -0
  63. Modeling_Tool/WOE/WOE_Plot_Tool.py +919 -0
  64. Modeling_Tool/WOE/WOE_Plot_Tool.pyi +46 -0
  65. Modeling_Tool/WOE/WOE_Report_Builder.py +214 -0
  66. Modeling_Tool/WOE/WOE_Report_Builder.pyi +24 -0
  67. Modeling_Tool/WOE/WOE_Tool.py +1094 -0
  68. Modeling_Tool/WOE/WOE_Tool.pyi +41 -0
  69. Modeling_Tool/WOE/__init__.py +86 -0
  70. Modeling_Tool/WOE/plot_woe_tool.py +290 -0
  71. Modeling_Tool/WOE/plot_woe_tool.pyi +25 -0
  72. Modeling_Tool/__init__.py +176 -0
  73. Report/Report_Tool.py +380 -0
  74. Report/__init__.py +3 -0
  75. supermodelingfactory-0.2.0.dist-info/METADATA +268 -0
  76. supermodelingfactory-0.2.0.dist-info/RECORD +79 -0
  77. supermodelingfactory-0.2.0.dist-info/WHEEL +5 -0
  78. supermodelingfactory-0.2.0.dist-info/licenses/LICENSE +102 -0
  79. supermodelingfactory-0.2.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,919 @@
1
+ from matplotlib import pyplot as plt
2
+ import numpy as np
3
+ import pandas as pd
4
+ import logging
5
+ import os
6
+ import shutil
7
+ from matplotlib.font_manager import FontProperties
8
+
9
+ from .WOE_Tool import calc_woe, calc_iv, mapping_woe
10
+ from Modeling_Tool.Core.utils import mkdir_if_not_exist
11
+
12
+ zhfont = FontProperties(fname=os.path.join(os.path.dirname(__file__), "../ref_font/KaiTi.ttf"))
13
+ palette = {
14
+ "single_01": ["#0099CC", "#FF6666", ],
15
+ "grey": "#000000", # 黑灰色
16
+ "blue": "#336699", # 蓝色
17
+ "red_list" : ["#CC0033", "#CC3333", "#FF6666", ], # 红色系由深至浅
18
+ }
19
+
20
+ def plot_woe(woe_df, var_rename=None, to_show=True, save_dir=None, fig_name='var.png'):
21
+ """
22
+ 绘制变量的WOE图。
23
+
24
+ Parameters
25
+ ----------
26
+ woe_df : pandas.DataFrame
27
+ WOE表,包含变量、分箱等信息
28
+ var_rename : str, optional
29
+ 变量重命名,用于图表标题显示,默认为None
30
+ to_show : bool, optional
31
+ 是否展示图片,默认为True
32
+ save_dir : str, optional
33
+ 结果图片存放的文件夹路径,默认为None
34
+ fig_name : str, optional
35
+ 保存图片的文件名,默认为'var.png'
36
+
37
+ Returns
38
+ -------
39
+ None
40
+ 函数直接绘制图表并可选保存或展示
41
+
42
+ Examples
43
+ --------
44
+ >>> plot_woe(woe_df, var_rename='年龄', save_dir='./output')
45
+ """
46
+ woe_df.columns = [x.lower() for x in woe_df.columns]
47
+ woe_df[['woe', 'iv']] = woe_df[['woe', 'iv']].replace([np.inf, -np.inf], 0)
48
+
49
+ woe_res = []
50
+ for _, group in woe_df.groupby('var'):
51
+ group['p'] = group['n']/group['n'].sum()
52
+ woe_res.append(group)
53
+ woe_res = pd.concat(woe_res)
54
+ woe_df = woe_res.copy()
55
+
56
+ var_name = woe_df["var"].iloc[0]
57
+ iv = round(sum(woe_df["iv"]), 5)
58
+ X = woe_df["bin_num"]
59
+ xticks_list = [str(x)[:20]+"..." if len(str(x)) > 20 else str(x) for x in woe_df["bin_range"]]
60
+
61
+ # 创建画布
62
+ plt.figure(figsize=(12, 5), dpi=200) # 8,4
63
+ grid = plt.GridSpec(1, 12, wspace=0.5, hspace=0.5)
64
+
65
+ # 1.绘制Woe图
66
+ ax1 = plt.subplot(grid[:, :5])
67
+ # 绘制主坐标轴
68
+ ax1.bar(X, woe_df["p"], color=palette["single_01"][0], label="0", align="edge", width=0.985, alpha=0.8)
69
+ ax1.bar(X, woe_df["p"] * woe_df["avg_bad"], color=palette["single_01"][1], label="1", align="edge", width=0.985)
70
+
71
+ plt.xticks(X+0.5, xticks_list, fontsize=6, rotation=45)
72
+ plt.yticks(fontsize=6)
73
+ ax1.axis(ymin=0.0, ymax=1)
74
+ ax1.set_ylabel("Proportion", fontsize=6)
75
+ ax1.legend(loc=2, fontsize=6)
76
+
77
+ # 绘制次坐标轴
78
+ ax1_2 = plt.twinx()
79
+ plt.axis(ymin=np.min([-1, woe_df["woe"].min() * 1.1]), ymax=np.max([1, woe_df["woe"].max() * 1.1])) # 设置次轴区间
80
+ plt.plot(X+0.5, woe_df["woe"], color="black", linewidth=1.5)
81
+ for x, woe, avg_bad in zip(X, woe_df["woe"], woe_df["avg_bad"]):
82
+ ax1_2.annotate(f"{woe:.3f} ({avg_bad:.2%})",
83
+ xy=(x+0.5, woe),
84
+ va="center",
85
+ ha="center",
86
+ bbox={"boxstyle": "round", "fc": "w"},
87
+ fontsize=7
88
+ )
89
+ plt.yticks(fontsize=6)
90
+ ax1_2.set_ylabel("WOE (TargetRate)", fontsize=6)
91
+
92
+ # 绘制标题
93
+ if bool(var_rename):
94
+ plt.title(f"{str(var_rename)}: IV={iv:.3f}", fontsize=12, fontproperties=zhfont)
95
+ else:
96
+ plt.title(f"{var_name}: IV={iv:.3f}", fontsize=12, fontproperties=zhfont)
97
+
98
+ # 2.绘制Woe表
99
+ ax2 = plt.subplot(grid[:, 7:])
100
+ ax2.set_axis_off()
101
+
102
+ # 调整要展示的数据
103
+ tbl = woe_df[["n", "p", "avg_bad", "lift", "woe",]].copy()
104
+ tbl.loc["total"] = [tbl["n"].sum(), tbl["p"].sum(), woe_df["n_bad"].sum()/woe_df["n"].sum(), 1, 0]
105
+ tbl["n"] = [f"{x:,.0f}" for x in tbl["n"]]
106
+ tbl["p"] = [f"{x:.2%}" for x in tbl["p"]]
107
+ tbl["avg_bad"] = [f"{x:.2%}" for x in tbl["avg_bad"]]
108
+ tbl["lift"] = [f"{x:.2}" for x in tbl["lift"]]
109
+ tbl["woe"] = [f"{x:.3f}" for x in tbl["woe"]]
110
+
111
+ # 绘制表格
112
+ rowls = xticks_list
113
+ rowls.append("total")
114
+ tbl = ax2.table(
115
+ cellText=tbl[["n", "p", "avg_bad", "lift", "woe"]].values,
116
+ colLabels=["N", "Prop", "BadRate", "Lift", "WOE"],
117
+ rowLabels=rowls,
118
+ colWidths=[0.2]*5,
119
+ loc="center",
120
+ cellLoc="right",
121
+ rowLoc="right",
122
+ colLoc="center",
123
+ colColours=["#CCCCCC"]*5,
124
+ rowColours=["#CCCCCC"]*len(rowls),
125
+ )
126
+
127
+ tbl.scale(1, 1.5)
128
+ for key, cell in tbl.get_celld().items():
129
+ row, col = key
130
+ if row == 0 or col == -1:
131
+ cell.set_text_props(font=zhfont, fontsize=7, fontstyle="oblique")
132
+ if row > 0 and col > -1:
133
+ cell.set_text_props(font=zhfont, fontsize=7)
134
+
135
+ # 保存结果
136
+ if bool(save_dir):
137
+ plt.savefig(os.path.join(save_dir, fig_name), bbox_inches="tight")
138
+
139
+ # 展示结果
140
+ if to_show:
141
+ plt.show()
142
+ plt.close()
143
+
144
+
145
+ def get_woe_table(binning_res, var, dep):
146
+ """
147
+ 根据分箱结果计算并返回WOE表和WOE映射字典。
148
+
149
+ Parameters
150
+ ----------
151
+ binning_res : pandas.DataFrame
152
+ 包含分箱结果的数据框,应包含 _bin_num_{var} 和 _bin_range_{var} 列
153
+ var : str
154
+ 变量名称,用于标识要分析的特征列
155
+ dep : str
156
+ 目标变量名称,用于计算好坏样本统计信息
157
+
158
+ Returns
159
+ -------
160
+ tuple
161
+ - woe_table (pandas.DataFrame): 包含每个分箱的WOE、IV等统计信息的表格
162
+ - woe_mapping_dict (dict): 将分箱范围映射到WOE值的字典
163
+
164
+ Examples
165
+ --------
166
+ >>> woe_table, woe_dict = get_woe_table(binning_res, 'age', 'target')
167
+ """
168
+ # 计算每个分箱的统计信息
169
+ woe_table = binning_res.groupby([f"_bin_num_{var}", f"_bin_range_{var}"], dropna = False)\
170
+ .agg(MIN = (var, "min"),
171
+ MAX = (var, "max"),
172
+ N = (f"_bin_num_{var}", "count"),
173
+ AVG_SCORE = (var, "mean"),
174
+ AVG_BAD = (dep, lambda x: ( (x==1).sum() / x.count()) ),
175
+ AVG_GOOD = (dep, lambda x: ( (x==0).sum() / x.count()) ),
176
+ N_BAD = (dep, "sum"),
177
+ N_GOOD = (dep, lambda x: (x==0).sum()))
178
+
179
+ ## IV/WOE Calculation
180
+ woe_table["BAD_PCT_PER_BIN"] = woe_table["N_BAD"] / woe_table["N_BAD"].sum()
181
+ woe_table["GOOD_PCT_PER_BIN"] = woe_table["N_GOOD"] / woe_table["N_GOOD"].sum()
182
+ woe_table["LIFT"] = woe_table['AVG_BAD'] / woe_table['AVG_BAD'].mean()
183
+ woe_table["WOE"] = calc_woe(data = woe_table, bad_pct = "BAD_PCT_PER_BIN", good_pct = "GOOD_PCT_PER_BIN")
184
+ woe_table["IV"] = calc_iv(data = woe_table, bad_pct = "BAD_PCT_PER_BIN", good_pct = "GOOD_PCT_PER_BIN")
185
+
186
+ ## WOE Mapping Dictionary
187
+ woe_table = woe_table.reset_index(drop=False)
188
+ woe_mapping_dict = dict(zip(woe_table[f"_bin_range_{var}"], woe_table["WOE"]))
189
+
190
+ return woe_table, woe_mapping_dict
191
+
192
+
193
+ def get_mapped_woe_summary_single(data, var, ref_woe_table, tgt_name):
194
+ """
195
+ 根据给定的参考WOE映射表,对单个变量生成WOE汇总表。
196
+
197
+ Parameters
198
+ ----------
199
+ data : pandas.DataFrame
200
+ 输入数据,包含原始变量值
201
+ var : str
202
+ 变量名称
203
+ ref_woe_table : pandas.DataFrame
204
+ 参考WOE映射表,包含各变量的标准WOE分箱信息
205
+ tgt_name : str
206
+ 目标变量名称,用于计算好坏样本比例
207
+
208
+ Returns
209
+ -------
210
+ pandas.DataFrame
211
+ 与参考WOE表格式一致的变量WOE汇总表
212
+
213
+ Examples
214
+ --------
215
+ >>> summary = get_mapped_woe_summary_single(data, 'income', ref_table, 'default')
216
+ """
217
+
218
+ ref_var_woe_table = ref_woe_table[ref_woe_table["VAR"] == var]
219
+
220
+ df_mapped = mapping_woe(data, [var.replace("_woe", "")], ref_woe_table, suffix = "_woe", drop_bin_info = False)
221
+ df_mapped.columns = [x.lower() for x in df_mapped.columns]
222
+
223
+ if f"_bin_num_{var}" in df_mapped.columns:
224
+ df_mapped = df_mapped.drop(columns = [f"_bin_num_{var}"])
225
+
226
+ if f"_bin_range_{var}" in df_mapped.columns:
227
+ df_mapped = df_mapped.drop(columns = [f"_bin_range_{var}"])
228
+
229
+ df_mapped = df_mapped.rename(columns = {"_bin_num_": f"_bin_num_{var}",
230
+ "_bin_range_": f"_bin_range_{var}"})
231
+
232
+ var_woe_table = get_woe_table(df_mapped, var, dep = tgt_name)[0]
233
+ var_woe_table["VAR"] = var
234
+
235
+ var_woe_table = var_woe_table.rename(columns = {f"_bin_num_{var}": "bin_num", f"_bin_range_{var}": f"bin_range"})
236
+ var_woe_table.columns = [x.upper() for x in var_woe_table.columns]
237
+ fnlcollist = ref_woe_table.columns.tolist()
238
+ return var_woe_table[fnlcollist]
239
+
240
+
241
+ def get_mapped_woe_summary_grp(data, var, ref_woe_table, tgt_name, grp_name=None):
242
+ """
243
+ 获取分组后的单个变量WOE汇总表。
244
+
245
+ Parameters
246
+ ----------
247
+ data : pandas.DataFrame
248
+ 输入数据,包含原始变量值和分组信息
249
+ var : str
250
+ 变量名称
251
+ ref_woe_table : pandas.DataFrame
252
+ 参考WOE映射表,包含各变量的标准WOE分箱信息
253
+ tgt_name : str
254
+ 目标变量名称,用于计算好坏样本比例
255
+ grp_name : str or list, optional
256
+ 分组字段名称,用于按组别分别计算WOE,默认为None
257
+
258
+ Returns
259
+ -------
260
+ pandas.DataFrame
261
+ 分组后的变量WOE汇总表
262
+
263
+ Notes
264
+ -----
265
+ 如果数据中存在缺失值,会自动删除包含缺失值的记录并记录警告日志
266
+
267
+ Examples
268
+ --------
269
+ >>> summary = get_mapped_woe_summary_grp(data, 'age', ref_table, 'default', 'city')
270
+ """
271
+
272
+ if data.query(f"{var} != {var}").shape[0] > 0:
273
+ data = data.dropna(subset=[var])
274
+ logging.info(f"WARNING: Found Missing Value in {var}, Missing Records Had Been Dropped!")
275
+
276
+ if grp_name:
277
+ fnl_res = []
278
+ for grp, group in data.groupby(grp_name):
279
+ grp_res = get_mapped_woe_summary_single(data = group, var = var, ref_woe_table = ref_woe_table, tgt_name = tgt_name)
280
+ for i, g in enumerate(grp):
281
+ grp_res[grp_name[i]] = g
282
+ fnl_res.append(grp_res)
283
+
284
+ return pd.concat(fnl_res)
285
+
286
+ return get_mapped_woe_summary_single(data = data, var = var, ref_woe_table = ref_woe_table, tgt_name=tgt_name)
287
+
288
+
289
+ def get_mapped_woe_summary(data, ref_woe_table, tgt_name, varlist=None, grp_name=None):
290
+ """
291
+ 获取多个变量的WOE汇总表。
292
+
293
+ Parameters
294
+ ----------
295
+ data : pandas.DataFrame
296
+ 输入数据,包含原始变量值
297
+ ref_woe_table : pandas.DataFrame
298
+ 参考WOE映射表,包含各变量的标准WOE分箱信息
299
+ tgt_name : str
300
+ 目标变量名称,用于计算好坏样本比例
301
+ varlist : list, optional
302
+ 要计算的变量列表,默认为None(使用参考表中的所有变量)
303
+ grp_name : str or list, optional
304
+ 分组字段名称,用于按组别分别计算WOE,默认为None
305
+
306
+ Returns
307
+ -------
308
+ pandas.DataFrame
309
+ 包含所有变量WOE信息的汇总表
310
+
311
+ Examples
312
+ --------
313
+ >>> summary = get_mapped_woe_summary(data, ref_table, 'default', varlist=['age', 'income'])
314
+ """
315
+
316
+ if varlist is None:
317
+ varlist = ref_woe_table['VAR'].unique().tolist()
318
+
319
+ fnl_res = []
320
+ for var in varlist:
321
+ var_res = get_mapped_woe_summary_grp(data, var, ref_woe_table, tgt_name, grp_name)
322
+ fnl_res.append(var_res)
323
+
324
+ return pd.concat(fnl_res)
325
+
326
+
327
+ def plot_woe_group(woe_grp_df, grp_name=None, var_rename=None, to_show=True, save_dir=None, fig_name="var_group.png"):
328
+ """
329
+ 绘制变量的分组WOE图。
330
+
331
+ Parameters
332
+ ----------
333
+ woe_grp_df : pandas.DataFrame
334
+ 分组WOE表,包含按组别分组后的WOE信息
335
+ grp_name : str, optional
336
+ 分组字段名称,用于区分不同组的WOE曲线,默认为None
337
+ var_rename : str, optional
338
+ 变量重命名,用于图表标题显示,默认为None
339
+ to_show : bool, optional
340
+ 是否展示图片,默认为True
341
+ save_dir : str, optional
342
+ 结果图片存放的文件夹路径,默认为None
343
+ fig_name : str, optional
344
+ 保存图片的文件名,默认为'var_group.png'
345
+
346
+ Returns
347
+ -------
348
+ None
349
+ 函数直接绘制图表并可选保存或展示
350
+
351
+ Examples
352
+ --------
353
+ >>> plot_woe_group(woe_grp_df, grp_name='gender', save_dir='./output')
354
+ """
355
+
356
+
357
+ woe_grp_df.columns = [x.lower() for x in woe_grp_df.columns]
358
+ woe_grp_df[['woe', 'iv']] = woe_grp_df[['woe', 'iv']].replace([np.inf, -np.inf], np.nan)
359
+
360
+ var_name = woe_grp_df["var"].iloc[0]
361
+ summary_df = woe_grp_df.groupby([grp_name]).agg({"iv": sum, "n": sum, "n_bad": sum})
362
+ summary_df["avg_bad"] = summary_df["n_bad"] / summary_df["n"]
363
+
364
+ iv_dict = {x: round(y, 5) for x, y in zip(summary_df.index, summary_df["iv"])}
365
+ tr_dict = {x: round(y, 5) for x, y in zip(summary_df.index, summary_df["avg_bad"])}
366
+ N_dict = {x: y for x, y in zip(summary_df.index, summary_df["n"])}
367
+ X = woe_grp_df["bin_num"].drop_duplicates()
368
+ Xticks = woe_grp_df.groupby(["bin_num"]).agg({"bin_range": max})["bin_range"]
369
+
370
+ gs = list(set(woe_grp_df[grp_name]))
371
+ gs.sort()
372
+ n = len(gs)
373
+
374
+ # 构建画布
375
+ plt.figure(figsize=(12, 5), dpi=200) # 8,4
376
+ grid = plt.GridSpec(1, 12, wspace=0.5, hspace=0.5)
377
+
378
+ # 1.绘制Woe图
379
+ ax1 = plt.subplot(grid[:, :5])
380
+
381
+ # 绘制主坐标轴
382
+ width = 0.9 / n
383
+ alpha = 0.5 / n
384
+ for i in range(n):
385
+ g = gs[i]
386
+ df_plot = woe_grp_df.loc[woe_grp_df[grp_name] == g, ].reset_index(drop=True)
387
+ df_plot['p'] = df_plot['n'] / df_plot['n'].sum()
388
+ ax1.bar(X + i * (0.01 + width), df_plot["p"], color=palette["single_01"][0], label=f"{g} N={N_dict[g]:,.0f} TR={tr_dict[g]:.2%}", align="edge", width=width, alpha=0.5 + alpha*(i+1))
389
+ ax1.bar(X + i * (0.01 + width), df_plot["p"] * df_plot["avg_bad"], color=palette["red_list"][2], align="edge", width=width, alpha=1)
390
+
391
+ xticks_list = [str(x)[:20]+"..." if len(str(x)) > 20 else str(x) for x in Xticks]
392
+ plt.xticks(X+0.5, xticks_list, fontsize=6, rotation=45)
393
+ plt.axis(ymin=0.0, ymax=1)
394
+ plt.yticks(fontsize=6)
395
+ plt.ylabel("Proportion", fontsize=6)
396
+ plt.legend(loc=2, fontsize=6)
397
+
398
+ # 绘制次坐标轴
399
+ alpha = 0.8 / n
400
+ ax1_2 = plt.twinx()
401
+ for i in range(n):
402
+ g = gs[i]
403
+ df_plot = woe_grp_df.loc[woe_grp_df[grp_name] == g, ].reset_index(drop=True)
404
+ plt.plot(X+0.5, df_plot["woe"], color=palette["grey"], linewidth=1, label=f"{g} IV={iv_dict[g]:.2f}", alpha=0.2 + alpha*(i+1))
405
+ plt.axis(ymin=np.min([-1, woe_grp_df["woe"].min() * 1.1]), ymax=np.max([1, woe_grp_df["woe"].max() * 1.1])) # 设置次轴区间
406
+ plt.yticks(fontsize=6)
407
+ plt.ylabel("WOE", fontsize=6)
408
+ plt.legend(loc=1, fontsize=6)
409
+
410
+ # 绘制总标题
411
+ if bool(var_rename):
412
+ plt.title(f"{str(var_rename)}: IV_range={summary_df.iv.min():.2f}-{summary_df.iv.max():.2f}", fontsize=12, fontproperties=zhfont)
413
+ else:
414
+ plt.title(f"{var_name}: IV_range={summary_df.iv.min():.2f}-{summary_df.iv.max():.2f}", fontsize=12, fontproperties=zhfont)
415
+
416
+ # 2.绘制Woe表
417
+ ax2 = plt.subplot(grid[:, 7:])
418
+ ax2.set_axis_off()
419
+
420
+ # 调整要展示的数据
421
+ tbl = pd.DataFrame()
422
+ for i in range(n):
423
+ g = gs[i]
424
+ tbl.loc[:, g] = woe_grp_df.loc[woe_grp_df[grp_name] == g, "woe"]
425
+ tbl.loc[:, g] = [f"{x:.3f}" for x in tbl[g]]
426
+
427
+ # 绘制表格
428
+ rowls = xticks_list
429
+ colls = ["_".join([x, "woe"]) for x in gs]
430
+ tbl = ax2.table(
431
+ cellText=tbl.values,
432
+ colLabels=colls,
433
+ rowLabels=rowls,
434
+ colWidths=[1.0 / n] * n,
435
+ loc="center",
436
+ cellLoc="right",
437
+ rowLoc="right",
438
+ colLoc="center",
439
+ colColours=["#CCCCCC"] * n,
440
+ rowColours=["#CCCCCC"] * len(rowls),
441
+ )
442
+ tbl.scale(1, 1.5)
443
+ for key, cell in tbl.get_celld().items():
444
+ row, col = key
445
+ if row == 0 or col == -1:
446
+ cell.set_text_props(font=zhfont, fontsize=7, fontstyle="oblique")
447
+ if row > 0 and col > -1:
448
+ cell.set_text_props(font=zhfont, fontsize=7)
449
+
450
+ # 保存结果
451
+ if bool(save_dir):
452
+ plt.savefig(os.path.join(save_dir, fig_name), bbox_inches="tight")
453
+
454
+ # 展示结果
455
+ if to_show:
456
+ plt.show()
457
+ plt.close()
458
+
459
+
460
+ def align_bin_num(woe_table, grp_woe_df, grp_name):
461
+ """
462
+ 对齐分组WOE表与参考WOE表的分箱编号。
463
+
464
+ Parameters
465
+ ----------
466
+ woe_table : pandas.DataFrame
467
+ 参考WOE表,包含标准的分箱定义
468
+ grp_woe_df : pandas.DataFrame
469
+ 分组WOE表,需要与参考表对齐
470
+ grp_name : str or list
471
+ 分组字段名称
472
+
473
+ Returns
474
+ -------
475
+ pandas.DataFrame
476
+ 对齐后的分组WOE表,BIN_NUM重新编号以避免空值
477
+
478
+ Examples
479
+ --------
480
+ >>> aligned = align_bin_num(ref_woe_table, grp_woe_df, 'city')
481
+ """
482
+
483
+ left_data = woe_table[['BIN_RANGE', 'VAR']]
484
+ left_data.columns = [x.upper() for x in left_data.columns]
485
+
486
+ fnl_res = []
487
+ for grp, group in grp_woe_df.groupby(grp_name):
488
+ grp_res = left_data.merge(group, on = ['BIN_RANGE', 'VAR'], how = 'left')
489
+ for i, g in enumerate(grp):
490
+ grp_res[grp_name[i]] = g
491
+
492
+ fnl_res.append(grp_res)
493
+ fnl_res = pd.concat(fnl_res)
494
+
495
+
496
+ ## Reset BIN_NUM to Avoid Null values in BIN_NUM column
497
+ reset_bin_num_res = []
498
+ for _, group_data in fnl_res.groupby(['VAR', *grp_name]):
499
+ group_data = group_data.drop(columns = ['BIN_NUM'])
500
+ group_data = group_data.reset_index(drop = True)
501
+ group_data['BIN_NUM'] = (group_data.index + 1).tolist()
502
+ reset_bin_num_res.append(group_data)
503
+ reset_bin_num_res = pd.concat(reset_bin_num_res)
504
+ reset_bin_num_res = reset_bin_num_res[['BIN_RANGE', 'VAR', 'BIN_NUM'] + [x for x in reset_bin_num_res.columns if x not in ['BIN_RANGE', 'VAR', 'BIN_NUM']]]
505
+
506
+ return reset_bin_num_res
507
+
508
+
509
+ def get_bivar_graph(data, varlist, sep, ref_woe_table, save_dir, group=None, woe_suffix="_woe"):
510
+ """
511
+ 生成双变量分析图表,包括参考WOE图和分组WOE图。
512
+
513
+ Parameters
514
+ ----------
515
+ data : pandas.DataFrame
516
+ 输入数据,包含原始变量值和分组信息
517
+ varlist : list
518
+ 要分析的变量列表
519
+ sep : str
520
+ 目标变量名称,用于计算好坏样本比例
521
+ ref_woe_table : pandas.DataFrame
522
+ 参考WOE映射表,包含各变量的标准WOE分箱信息
523
+ save_dir : str
524
+ 图片保存目录路径
525
+ group : str, optional
526
+ 分组字段名称,用于生成分组对比图,默认为None
527
+ woe_suffix : str, optional
528
+ WOE变量后缀,默认为'_woe'
529
+
530
+ Returns
531
+ -------
532
+ None
533
+ 函数直接保存图表到指定目录
534
+
535
+ Examples
536
+ --------
537
+ >>> get_bivar_graph(data, ['age', 'income'], 'default', ref_table, './output', group='city')
538
+ """
539
+
540
+ # try:
541
+ # shutil.rmtree(save_dir)
542
+ # except Exception as e:
543
+ # mkdir_if_not_exist(save_dir)
544
+
545
+ ################ Reference WOE Table Plot ##########################
546
+ for var in varlist:
547
+ var = var.replace(woe_suffix, "")
548
+ woe_df = ref_woe_table[ref_woe_table['VAR'] == var]
549
+ plot_woe(woe_df, to_show=False, save_dir=save_dir, fig_name = f"{var}.png")
550
+
551
+ ################ Group WOE Table Plot ##########################
552
+ grp_woe_res = get_mapped_woe_summary(data = data,
553
+ ref_woe_table = ref_woe_table,
554
+ tgt_name = sep,
555
+ varlist = varlist,
556
+ grp_name=[group])
557
+
558
+ grp_woe_res = align_bin_num(woe_table = ref_woe_table, grp_woe_df = grp_woe_res, grp_name = [group])
559
+
560
+ if group:
561
+
562
+ for var in varlist:
563
+ var = var.replace(woe_suffix, "")
564
+ woe_grp_df = grp_woe_res[grp_woe_res['VAR'] == var]
565
+ plot_woe_group(woe_grp_df, grp_name = group, to_show=False, save_dir=save_dir, fig_name = f"{var}_{group}.png")
566
+
567
+ return None
568
+
569
+
570
+ # =============================================================================
571
+ # 新增类封装
572
+ # =============================================================================
573
+
574
+ class WOEPlotter:
575
+ """
576
+ WOE绘图类,封装单个变量和分组变量的WOE图表绘制功能。
577
+
578
+ Parameters
579
+ ----------
580
+ var_rename : str, optional
581
+ 变量重命名,用于图表标题显示,默认为None
582
+ to_show : bool, optional
583
+ 是否展示图片,默认为True
584
+ save_dir : str, optional
585
+ 结果图片存放的文件夹路径,默认为None
586
+ fig_name : str, optional
587
+ 保存图片的文件名,默认为'var.png'
588
+ grp_name : str, optional
589
+ 分组字段名称,默认为None
590
+
591
+ Attributes
592
+ ----------
593
+ var_rename : str
594
+ 变量重命名
595
+ to_show : bool
596
+ 是否展示图片
597
+ save_dir : str
598
+ 图片保存目录
599
+ fig_name : str
600
+ 图片文件名
601
+ grp_name : str
602
+ 分组字段名称
603
+
604
+ Examples
605
+ --------
606
+ >>> plotter = WOEPlotter(save_dir='./output', var_rename='年龄')
607
+ >>> plotter.plot(woe_df)
608
+ >>> plotter.plot_group(woe_grp_df, grp_name='城市')
609
+ """
610
+
611
+ def __init__(self, var_rename=None, to_show=True, save_dir=None, fig_name='var.png', grp_name=None):
612
+ """
613
+ 初始化WOEPlotter实例。
614
+
615
+ Parameters
616
+ ----------
617
+ var_rename : str, optional
618
+ 变量重命名,用于图表标题显示
619
+ to_show : bool, optional
620
+ 是否展示图片,默认为True
621
+ save_dir : str, optional
622
+ 结果图片存放的文件夹路径
623
+ fig_name : str, optional
624
+ 保存图片的文件名,默认为'var.png'
625
+ grp_name : str, optional
626
+ 分组字段名称
627
+ """
628
+ self.var_rename = var_rename
629
+ self.to_show = to_show
630
+ self.save_dir = save_dir
631
+ self.fig_name = fig_name
632
+ self.grp_name = grp_name
633
+
634
+ def plot(self, woe_df, var_rename=None, to_show=None, save_dir=None, fig_name=None):
635
+ """
636
+ 绘制单个变量的WOE图。
637
+
638
+ Parameters
639
+ ----------
640
+ woe_df : pandas.DataFrame
641
+ WOE表,包含变量、分箱等信息
642
+ var_rename : str, optional
643
+ 变量重命名,会覆盖实例属性
644
+ to_show : bool, optional
645
+ 是否展示图片,会覆盖实例属性
646
+ save_dir : str, optional
647
+ 图片保存目录,会覆盖实例属性
648
+ fig_name : str, optional
649
+ 图片文件名,会覆盖实例属性
650
+
651
+ Returns
652
+ -------
653
+ None
654
+
655
+ Examples
656
+ --------
657
+ >>> plotter = WOEPlotter(save_dir='./output')
658
+ >>> plotter.plot(woe_df, var_rename='年龄')
659
+ """
660
+ # 使用实例属性作为默认值,允许覆盖
661
+ _var_rename = var_rename if var_rename is not None else self.var_rename
662
+ _to_show = to_show if to_show is not None else self.to_show
663
+ _save_dir = save_dir if save_dir is not None else self.save_dir
664
+ _fig_name = fig_name if fig_name is not None else self.fig_name
665
+
666
+ plot_woe(woe_df, _var_rename, _to_show, _save_dir, _fig_name)
667
+
668
+ def plot_group(self, woe_grp_df, grp_name=None, var_rename=None, to_show=None, save_dir=None, fig_name=None):
669
+ """
670
+ 绘制分组变量的WOE图。
671
+
672
+ Parameters
673
+ ----------
674
+ woe_grp_df : pandas.DataFrame
675
+ 分组WOE表,包含按组别分组后的WOE信息
676
+ grp_name : str, optional
677
+ 分组字段名称,会覆盖实例属性
678
+ var_rename : str, optional
679
+ 变量重命名,会覆盖实例属性
680
+ to_show : bool, optional
681
+ 是否展示图片,会覆盖实例属性
682
+ save_dir : str, optional
683
+ 图片保存目录,会覆盖实例属性
684
+ fig_name : str, optional
685
+ 图片文件名,会覆盖实例属性
686
+
687
+ Returns
688
+ -------
689
+ None
690
+
691
+ Examples
692
+ --------
693
+ >>> plotter = WOEPlotter(save_dir='./output')
694
+ >>> plotter.plot_group(woe_grp_df, grp_name='城市')
695
+ """
696
+ _grp_name = grp_name if grp_name is not None else self.grp_name
697
+ _var_rename = var_rename if var_rename is not None else self.var_rename
698
+ _to_show = to_show if to_show is not None else self.to_show
699
+ _save_dir = save_dir if save_dir is not None else self.save_dir
700
+ _fig_name = fig_name if fig_name is not None else self.fig_name
701
+
702
+ plot_woe_group(woe_grp_df, _grp_name, _var_rename, _to_show, _save_dir, _fig_name)
703
+
704
+
705
+ class WOEAnalyzer:
706
+ """
707
+ WOE分析器类,封装WOE表计算和映射汇总功能。
708
+
709
+ Parameters
710
+ ----------
711
+ ref_woe_table : pandas.DataFrame, optional
712
+ 参考WOE映射表,包含各变量的标准WOE分箱信息,默认为None
713
+ tgt_name : str, optional
714
+ 目标变量名称,用于计算好坏样本比例,默认为None
715
+
716
+ Attributes
717
+ ----------
718
+ ref_woe_table : pandas.DataFrame
719
+ 参考WOE映射表
720
+ tgt_name : str
721
+ 目标变量名称
722
+
723
+ Examples
724
+ --------
725
+ >>> analyzer = WOEAnalyzer(ref_woe_table, tgt_name='default')
726
+ >>> woe_table, woe_dict = analyzer.get_woe_table(binning_res, 'age')
727
+ >>> summary = analyzer.get_summary(data, 'income')
728
+ """
729
+
730
+ def __init__(self, ref_woe_table=None, tgt_name=None):
731
+ """
732
+ 初始化WOEAnalyzer实例。
733
+
734
+ Parameters
735
+ ----------
736
+ ref_woe_table : pandas.DataFrame, optional
737
+ 参考WOE映射表
738
+ tgt_name : str, optional
739
+ 目标变量名称
740
+ """
741
+ self.ref_woe_table = ref_woe_table
742
+ self.tgt_name = tgt_name
743
+
744
+ def get_woe_table(self, binning_res, var, dep=None):
745
+ """
746
+ 根据分箱结果计算并返回WOE表和WOE映射字典。
747
+
748
+ Parameters
749
+ ----------
750
+ binning_res : pandas.DataFrame
751
+ 包含分箱结果的数据框
752
+ var : str
753
+ 变量名称
754
+ dep : str, optional
755
+ 目标变量名称,会覆盖实例属性tgt_name
756
+
757
+ Returns
758
+ -------
759
+ tuple
760
+ - woe_table (pandas.DataFrame): WOE表
761
+ - woe_mapping_dict (dict): WOE映射字典
762
+
763
+ Examples
764
+ --------
765
+ >>> analyzer = WOEAnalyzer(tgt_name='default')
766
+ >>> woe_table, woe_dict = analyzer.get_woe_table(binning_res, 'age')
767
+ """
768
+ _dep = dep if dep is not None else self.tgt_name
769
+ return get_woe_table(binning_res, var, _dep)
770
+
771
+ def get_mapped_woe_summary_single(self, data, var, ref_woe_table=None, tgt_name=None):
772
+ """
773
+ 根据参考WOE映射表对单个变量生成WOE汇总表。
774
+
775
+ Parameters
776
+ ----------
777
+ data : pandas.DataFrame
778
+ 输入数据
779
+ var : str
780
+ 变量名称
781
+ ref_woe_table : pandas.DataFrame, optional
782
+ 参考WOE表,会覆盖实例属性
783
+ tgt_name : str, optional
784
+ 目标变量名称,会覆盖实例属性
785
+
786
+ Returns
787
+ -------
788
+ pandas.DataFrame
789
+ 变量WOE汇总表
790
+
791
+ Examples
792
+ --------
793
+ >>> analyzer = WOEAnalyzer(ref_woe_table, 'default')
794
+ >>> summary = analyzer.get_mapped_woe_summary_single(data, 'income')
795
+ """
796
+ _ref_woe_table = ref_woe_table if ref_woe_table is not None else self.ref_woe_table
797
+ _tgt_name = tgt_name if tgt_name is not None else self.tgt_name
798
+ return get_mapped_woe_summary_single(data, var, _ref_woe_table, _tgt_name)
799
+
800
+ def get_mapped_woe_summary_grp(self, data, var, ref_woe_table=None, tgt_name=None, grp_name=None):
801
+ """
802
+ 获取分组后的单个变量WOE汇总表。
803
+
804
+ Parameters
805
+ ----------
806
+ data : pandas.DataFrame
807
+ 输入数据
808
+ var : str
809
+ 变量名称
810
+ ref_woe_table : pandas.DataFrame, optional
811
+ 参考WOE表,会覆盖实例属性
812
+ tgt_name : str, optional
813
+ 目标变量名称,会覆盖实例属性
814
+ grp_name : str or list, optional
815
+ 分组字段名称
816
+
817
+ Returns
818
+ -------
819
+ pandas.DataFrame
820
+ 分组后的变量WOE汇总表
821
+
822
+ Examples
823
+ --------
824
+ >>> analyzer = WOEAnalyzer(ref_woe_table, 'default')
825
+ >>> summary = analyzer.get_mapped_woe_summary_grp(data, 'age', grp_name='city')
826
+ """
827
+ _ref_woe_table = ref_woe_table if ref_woe_table is not None else self.ref_woe_table
828
+ _tgt_name = tgt_name if tgt_name is not None else self.tgt_name
829
+ return get_mapped_woe_summary_grp(data, var, _ref_woe_table, _tgt_name, grp_name)
830
+
831
+ def get_mapped_woe_summary(self, data, ref_woe_table=None, tgt_name=None, varlist=None, grp_name=None):
832
+ """
833
+ 获取多个变量的WOE汇总表。
834
+
835
+ Parameters
836
+ ----------
837
+ data : pandas.DataFrame
838
+ 输入数据
839
+ ref_woe_table : pandas.DataFrame, optional
840
+ 参考WOE表,会覆盖实例属性
841
+ tgt_name : str, optional
842
+ 目标变量名称,会覆盖实例属性
843
+ varlist : list, optional
844
+ 要计算的变量列表
845
+ grp_name : str or list, optional
846
+ 分组字段名称
847
+
848
+ Returns
849
+ -------
850
+ pandas.DataFrame
851
+ 包含所有变量WOE信息的汇总表
852
+
853
+ Examples
854
+ --------
855
+ >>> analyzer = WOEAnalyzer(ref_woe_table, 'default')
856
+ >>> summary = analyzer.get_mapped_woe_summary(data, varlist=['age', 'income'])
857
+ """
858
+ _ref_woe_table = ref_woe_table if ref_woe_table is not None else self.ref_woe_table
859
+ _tgt_name = tgt_name if tgt_name is not None else self.tgt_name
860
+ return get_mapped_woe_summary(data, _ref_woe_table, _tgt_name, varlist, grp_name)
861
+
862
+ def align_bin_num(self, woe_table, grp_woe_df, grp_name):
863
+ """
864
+ 对齐分组WOE表与参考WOE表的分箱编号。
865
+
866
+ Parameters
867
+ ----------
868
+ woe_table : pandas.DataFrame
869
+ 参考WOE表
870
+ grp_woe_df : pandas.DataFrame
871
+ 分组WOE表
872
+ grp_name : str or list
873
+ 分组字段名称
874
+
875
+ Returns
876
+ -------
877
+ pandas.DataFrame
878
+ 对齐后的分组WOE表
879
+
880
+ Examples
881
+ --------
882
+ >>> analyzer = WOEAnalyzer()
883
+ >>> aligned = analyzer.align_bin_num(ref_table, grp_df, 'city')
884
+ """
885
+ return align_bin_num(woe_table, grp_woe_df, grp_name)
886
+
887
+ def get_bivar_graph(self, data, varlist, sep=None, ref_woe_table=None, save_dir=None, group=None, woe_suffix="_woe"):
888
+ """
889
+ 生成双变量分析图表。
890
+
891
+ Parameters
892
+ ----------
893
+ data : pandas.DataFrame
894
+ 输入数据
895
+ varlist : list
896
+ 要分析的变量列表
897
+ sep : str, optional
898
+ 目标变量名称,会覆盖实例属性tgt_name
899
+ ref_woe_table : pandas.DataFrame, optional
900
+ 参考WOE表,会覆盖实例属性
901
+ save_dir : str, optional
902
+ 图片保存目录
903
+ group : str, optional
904
+ 分组字段名称
905
+ woe_suffix : str, optional
906
+ WOE变量后缀,默认为'_woe'
907
+
908
+ Returns
909
+ -------
910
+ None
911
+
912
+ Examples
913
+ --------
914
+ >>> analyzer = WOEAnalyzer(ref_woe_table, 'default')
915
+ >>> analyzer.get_bivar_graph(data, ['age', 'income'], save_dir='./output', group='city')
916
+ """
917
+ _sep = sep if sep is not None else self.tgt_name
918
+ _ref_woe_table = ref_woe_table if ref_woe_table is not None else self.ref_woe_table
919
+ return get_bivar_graph(data, varlist, _sep, _ref_woe_table, save_dir, group, woe_suffix)