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,778 @@
|
|
|
1
|
+
"""
|
|
2
|
+
向后变量消除工具包(统一版)
|
|
3
|
+
=============================
|
|
4
|
+
|
|
5
|
+
本模块提供基于LightGBM和XGBoost的向后变量消除(Backward Variable Elimination)功能,
|
|
6
|
+
通过累计特征重要性阈值进行变量筛选,并支持训练后的性能分析。
|
|
7
|
+
|
|
8
|
+
Functions
|
|
9
|
+
---------
|
|
10
|
+
backward_lgbm
|
|
11
|
+
使用LightGBM模型进行向后变量消除
|
|
12
|
+
backward_xgbm
|
|
13
|
+
使用XGBoost模型进行向后变量消除
|
|
14
|
+
|
|
15
|
+
Classes
|
|
16
|
+
-------
|
|
17
|
+
BackwardVariableEliminator
|
|
18
|
+
向后变量消除器,支持LightGBM和XGBoost
|
|
19
|
+
BackwardEliminationAnalyzer
|
|
20
|
+
向后消除结果分析器
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import copy
|
|
26
|
+
import logging
|
|
27
|
+
import warnings
|
|
28
|
+
import functools
|
|
29
|
+
from collections import OrderedDict
|
|
30
|
+
from typing import Optional, List, Dict, Union, Any, Tuple
|
|
31
|
+
|
|
32
|
+
import numpy as np
|
|
33
|
+
import pandas as pd
|
|
34
|
+
import matplotlib
|
|
35
|
+
import matplotlib.pyplot as plt
|
|
36
|
+
import matplotlib.ticker as mticker
|
|
37
|
+
|
|
38
|
+
from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
|
|
39
|
+
|
|
40
|
+
# Suppress warnings for cleaner output
|
|
41
|
+
warnings.filterwarnings('ignore')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def backward_lgbm(
|
|
45
|
+
train_data: pd.DataFrame,
|
|
46
|
+
varlist: List[str],
|
|
47
|
+
dep: str,
|
|
48
|
+
varreduct_params: Optional[Dict] = None,
|
|
49
|
+
stopping_metric: str = "auc",
|
|
50
|
+
seed: int = 42,
|
|
51
|
+
num_boost_round: int = 200,
|
|
52
|
+
early_stopping_rounds: int = 20,
|
|
53
|
+
importance_type: str = "gain",
|
|
54
|
+
cum_importance_threshold: float = 0.99,
|
|
55
|
+
min_vars: int = 10,
|
|
56
|
+
validation_data: Optional[pd.DataFrame] = None,
|
|
57
|
+
test_data_dict: Optional[Dict[str, pd.DataFrame]] = None,
|
|
58
|
+
ret_perf: bool = True,
|
|
59
|
+
nbins: int = 10,
|
|
60
|
+
precision: int = 5,
|
|
61
|
+
min_bin_prop: float = 0.05,
|
|
62
|
+
include_missing: bool = True,
|
|
63
|
+
equal_freq: bool = True,
|
|
64
|
+
ascending: bool = True,
|
|
65
|
+
fillna: Optional[float] = None,
|
|
66
|
+
spec_values: Optional[List] = None,
|
|
67
|
+
weight_col: Optional[str] = None,
|
|
68
|
+
validation_weight_col: Optional[str] = None,
|
|
69
|
+
wgt_col: Optional[str] = None,
|
|
70
|
+
) -> Tuple:
|
|
71
|
+
"""
|
|
72
|
+
使用LightGBM模型进行向后变量消除。
|
|
73
|
+
|
|
74
|
+
通过训练LightGBM模型并根据特征重要性累计阈值筛选变量,
|
|
75
|
+
实现向后变量消除(Backward Variable Elimination)。
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
train_data : pd.DataFrame
|
|
80
|
+
训练数据集,必须包含dep列和varlist中的所有特征列
|
|
81
|
+
varlist : list of str
|
|
82
|
+
参与建模的特征变量列表
|
|
83
|
+
dep : str
|
|
84
|
+
目标变量列名(0/1二元变量)
|
|
85
|
+
varreduct_params : dict, optional
|
|
86
|
+
LightGBM超参数字典,未指定的必需参数将使用预设值
|
|
87
|
+
stopping_metric : str, default "auc"
|
|
88
|
+
早停评估指标,可选 "auc", "binary_logloss" 等
|
|
89
|
+
seed : int, default 42
|
|
90
|
+
随机种子,保证可复现性
|
|
91
|
+
num_boost_round : int, default 200
|
|
92
|
+
最大迭代轮数
|
|
93
|
+
early_stopping_rounds : int, default 20
|
|
94
|
+
早停轮数,验证集指标连续N轮无提升则停止
|
|
95
|
+
importance_type : str, default "gain"
|
|
96
|
+
特征重要性类型,可选 "gain", "split"
|
|
97
|
+
cum_importance_threshold : float, default 0.99
|
|
98
|
+
累计特征重要性阈值,筛选覆盖该比例重要性的最少特征
|
|
99
|
+
min_vars : int, default 10
|
|
100
|
+
保留的最小变量数量
|
|
101
|
+
validation_data : pd.DataFrame, optional
|
|
102
|
+
验证数据集,用于早停
|
|
103
|
+
test_data_dict : dict, optional
|
|
104
|
+
测试数据集字典,格式为 {名称: DataFrame}
|
|
105
|
+
ret_perf : bool, default True
|
|
106
|
+
是否返回模型性能指标
|
|
107
|
+
nbins : int, default 10
|
|
108
|
+
增益表分箱数
|
|
109
|
+
precision : int, default 5
|
|
110
|
+
数值精度
|
|
111
|
+
min_bin_prop : float, default 0.05
|
|
112
|
+
最小分箱比例
|
|
113
|
+
include_missing : bool, default True
|
|
114
|
+
是否包含缺失值分箱
|
|
115
|
+
equal_freq : bool, default True
|
|
116
|
+
是否使用等频分箱
|
|
117
|
+
ascending : bool, default True
|
|
118
|
+
增益表是否升序排列
|
|
119
|
+
fillna : float, optional
|
|
120
|
+
缺失值填充值
|
|
121
|
+
spec_values : list, optional
|
|
122
|
+
特殊值列表
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
tuple
|
|
127
|
+
(selected_vars, model, perf_dict) 或 (selected_vars, model)
|
|
128
|
+
|
|
129
|
+
Raises
|
|
130
|
+
------
|
|
131
|
+
TypeError
|
|
132
|
+
当输入数据不是pandas.DataFrame格式时
|
|
133
|
+
ImportError
|
|
134
|
+
当lightgbm未安装时
|
|
135
|
+
|
|
136
|
+
Examples
|
|
137
|
+
--------
|
|
138
|
+
>>> selected_vars, model, perf = backward_lgbm(
|
|
139
|
+
... train_data=train_df,
|
|
140
|
+
... varlist=feature_cols,
|
|
141
|
+
... dep='target',
|
|
142
|
+
... validation_data=val_df
|
|
143
|
+
... )
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
import lightgbm as lgb
|
|
147
|
+
except ImportError:
|
|
148
|
+
raise ImportError("请安装lightgbm: pip install lightgbm")
|
|
149
|
+
|
|
150
|
+
from Modeling_Tool.Eval.Model_Eval_Tool import get_perf_summary
|
|
151
|
+
|
|
152
|
+
if varreduct_params is None:
|
|
153
|
+
varreduct_params = {}
|
|
154
|
+
|
|
155
|
+
if test_data_dict is None:
|
|
156
|
+
test_data_dict = {}
|
|
157
|
+
|
|
158
|
+
if spec_values is None:
|
|
159
|
+
spec_values = []
|
|
160
|
+
|
|
161
|
+
datain_all = OrderedDict()
|
|
162
|
+
datain_all["mdl"] = train_data
|
|
163
|
+
if validation_data is not None:
|
|
164
|
+
datain_all["hd"] = validation_data
|
|
165
|
+
datain_all.update(test_data_dict)
|
|
166
|
+
|
|
167
|
+
# 检查数据格式一致性
|
|
168
|
+
try:
|
|
169
|
+
for k, v in datain_all.items():
|
|
170
|
+
assert isinstance(v, pd.DataFrame)
|
|
171
|
+
except AssertionError:
|
|
172
|
+
logging.warning("请提供pandas.DataFrame格式数据")
|
|
173
|
+
raise TypeError("请提供pandas.DataFrame格式数据")
|
|
174
|
+
|
|
175
|
+
# 预设参数(保证模型可复现性)
|
|
176
|
+
hyperparams_preset = {
|
|
177
|
+
"metric": stopping_metric,
|
|
178
|
+
"seed": seed,
|
|
179
|
+
"objective": "binary",
|
|
180
|
+
"boosting_type": "gbdt",
|
|
181
|
+
"num_threads": 8
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# 补充缺失的必需参数
|
|
185
|
+
lacked_params = [k for k in list(hyperparams_preset.keys()) if k not in list(varreduct_params.keys())]
|
|
186
|
+
for param in lacked_params:
|
|
187
|
+
varreduct_params[param] = hyperparams_preset[param]
|
|
188
|
+
|
|
189
|
+
weight_col = weight_col or wgt_col
|
|
190
|
+
|
|
191
|
+
# 构建 LightGBM 数据集
|
|
192
|
+
train_weight = resolve_sample_weight(data=train_data, weight_col=weight_col, expected_len=len(train_data))
|
|
193
|
+
lgb_train = lgb.Dataset(train_data[varlist], label=train_data[dep], weight=train_weight)
|
|
194
|
+
|
|
195
|
+
if validation_data is not None:
|
|
196
|
+
valid_weight = resolve_sample_weight(
|
|
197
|
+
data=validation_data,
|
|
198
|
+
weight_col=validation_weight_col or weight_col,
|
|
199
|
+
expected_len=len(validation_data),
|
|
200
|
+
)
|
|
201
|
+
lgb_valid = lgb.Dataset(validation_data[varlist], label=validation_data[dep], weight=valid_weight)
|
|
202
|
+
valid_sets = [lgb_valid]
|
|
203
|
+
valid_names = ["hd"]
|
|
204
|
+
else:
|
|
205
|
+
valid_sets = [lgb_train]
|
|
206
|
+
valid_names = ["mdl"]
|
|
207
|
+
|
|
208
|
+
callbacks = [
|
|
209
|
+
lgb.early_stopping(stopping_rounds=early_stopping_rounds, verbose=False),
|
|
210
|
+
lgb.log_evaluation(period=-1)
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
# 训练模型
|
|
214
|
+
model = lgb.train(
|
|
215
|
+
params=varreduct_params,
|
|
216
|
+
train_set=lgb_train,
|
|
217
|
+
num_boost_round=num_boost_round,
|
|
218
|
+
valid_sets=valid_sets,
|
|
219
|
+
valid_names=valid_names,
|
|
220
|
+
callbacks=callbacks
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# 获取特征重要性并筛选变量
|
|
224
|
+
importance_df = pd.DataFrame({
|
|
225
|
+
"feature": model.feature_name(),
|
|
226
|
+
"importance": model.feature_importance(importance_type=importance_type)
|
|
227
|
+
}).sort_values("importance", ascending=False).reset_index(drop=True)
|
|
228
|
+
|
|
229
|
+
importance_df["cum_importance"] = importance_df["importance"].cumsum() / importance_df["importance"].sum()
|
|
230
|
+
|
|
231
|
+
# 筛选达到阈值的变量
|
|
232
|
+
selected_idx = importance_df[importance_df["cum_importance"] <= cum_importance_threshold].index.tolist()
|
|
233
|
+
|
|
234
|
+
# 确保至少保留 min_vars 个变量
|
|
235
|
+
if len(selected_idx) < min_vars:
|
|
236
|
+
selected_idx = list(range(min(min_vars, len(importance_df))))
|
|
237
|
+
|
|
238
|
+
selected_vars = importance_df.loc[selected_idx, "feature"].tolist()
|
|
239
|
+
|
|
240
|
+
if not ret_perf:
|
|
241
|
+
return selected_vars, model
|
|
242
|
+
|
|
243
|
+
# 计算性能
|
|
244
|
+
score_col = "_lgbm_score"
|
|
245
|
+
perf_dict = {}
|
|
246
|
+
|
|
247
|
+
for name, df in datain_all.items():
|
|
248
|
+
df_score = df.copy()
|
|
249
|
+
df_score[score_col] = model.predict(df_score[varlist])
|
|
250
|
+
perf = get_perf_summary(
|
|
251
|
+
train=df_score if name == "mdl" else None,
|
|
252
|
+
validation=df_score if name == "hd" else None,
|
|
253
|
+
oot=df_score if name not in ("mdl", "hd") else None,
|
|
254
|
+
tgt_name=dep,
|
|
255
|
+
scr_name=score_col,
|
|
256
|
+
nbins=nbins,
|
|
257
|
+
precision=precision,
|
|
258
|
+
min_bin_prop=min_bin_prop,
|
|
259
|
+
include_missing=include_missing,
|
|
260
|
+
equal_freq=equal_freq,
|
|
261
|
+
ascending=ascending,
|
|
262
|
+
fillna=fillna,
|
|
263
|
+
spec_values=spec_values
|
|
264
|
+
)
|
|
265
|
+
perf_dict[name] = perf
|
|
266
|
+
|
|
267
|
+
return selected_vars, model, perf_dict
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def backward_xgbm(
|
|
271
|
+
train_data: pd.DataFrame,
|
|
272
|
+
varlist: List[str],
|
|
273
|
+
dep: str,
|
|
274
|
+
varreduct_params: Optional[Dict] = None,
|
|
275
|
+
stopping_metric: str = "auc",
|
|
276
|
+
seed: int = 42,
|
|
277
|
+
num_boost_round: int = 200,
|
|
278
|
+
early_stopping_rounds: int = 20,
|
|
279
|
+
importance_type: str = "gain",
|
|
280
|
+
cum_importance_threshold: float = 0.99,
|
|
281
|
+
min_vars: int = 10,
|
|
282
|
+
validation_data: Optional[pd.DataFrame] = None,
|
|
283
|
+
test_data_dict: Optional[Dict[str, pd.DataFrame]] = None,
|
|
284
|
+
ret_perf: bool = True,
|
|
285
|
+
nbins: int = 10,
|
|
286
|
+
precision: int = 5,
|
|
287
|
+
min_bin_prop: float = 0.05,
|
|
288
|
+
include_missing: bool = True,
|
|
289
|
+
equal_freq: bool = True,
|
|
290
|
+
ascending: bool = True,
|
|
291
|
+
fillna: Optional[float] = None,
|
|
292
|
+
spec_values: Optional[List] = None,
|
|
293
|
+
monotone_constraints: Optional[Dict[str, int]] = None,
|
|
294
|
+
weight_col: Optional[str] = None,
|
|
295
|
+
validation_weight_col: Optional[str] = None,
|
|
296
|
+
wgt_col: Optional[str] = None,
|
|
297
|
+
) -> Tuple:
|
|
298
|
+
"""
|
|
299
|
+
使用XGBoost模型进行向后变量消除。
|
|
300
|
+
|
|
301
|
+
通过训练XGBoost模型并根据特征重要性累计阈值筛选变量,
|
|
302
|
+
实现向后变量消除(Backward Variable Elimination)。
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
train_data : pd.DataFrame
|
|
307
|
+
训练数据集,必须包含dep列和varlist中的所有特征列
|
|
308
|
+
varlist : list of str
|
|
309
|
+
参与建模的特征变量列表
|
|
310
|
+
dep : str
|
|
311
|
+
目标变量列名(0/1二元变量)
|
|
312
|
+
varreduct_params : dict, optional
|
|
313
|
+
XGBoost超参数字典,未指定的必需参数将使用预设值
|
|
314
|
+
stopping_metric : str, default "auc"
|
|
315
|
+
早停评估指标
|
|
316
|
+
seed : int, default 42
|
|
317
|
+
随机种子
|
|
318
|
+
num_boost_round : int, default 200
|
|
319
|
+
最大迭代轮数
|
|
320
|
+
early_stopping_rounds : int, default 20
|
|
321
|
+
早停轮数
|
|
322
|
+
importance_type : str, default "gain"
|
|
323
|
+
特征重要性类型
|
|
324
|
+
cum_importance_threshold : float, default 0.99
|
|
325
|
+
累计特征重要性阈值
|
|
326
|
+
min_vars : int, default 10
|
|
327
|
+
保留的最小变量数量
|
|
328
|
+
validation_data : pd.DataFrame, optional
|
|
329
|
+
验证数据集
|
|
330
|
+
test_data_dict : dict, optional
|
|
331
|
+
测试数据集字典
|
|
332
|
+
ret_perf : bool, default True
|
|
333
|
+
是否返回性能指标
|
|
334
|
+
nbins : int, default 10
|
|
335
|
+
增益表分箱数
|
|
336
|
+
precision : int, default 5
|
|
337
|
+
数值精度
|
|
338
|
+
min_bin_prop : float, default 0.05
|
|
339
|
+
最小分箱比例
|
|
340
|
+
include_missing : bool, default True
|
|
341
|
+
是否包含缺失值分箱
|
|
342
|
+
equal_freq : bool, default True
|
|
343
|
+
是否使用等频分箱
|
|
344
|
+
ascending : bool, default True
|
|
345
|
+
增益表是否升序排列
|
|
346
|
+
fillna : float, optional
|
|
347
|
+
缺失值填充值
|
|
348
|
+
spec_values : list, optional
|
|
349
|
+
特殊值列表
|
|
350
|
+
monotone_constraints : dict, optional
|
|
351
|
+
单调约束字典,格式为 {特征名: 1/-1}
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
tuple
|
|
356
|
+
(selected_vars, model, perf_dict) 或 (selected_vars, model)
|
|
357
|
+
|
|
358
|
+
Raises
|
|
359
|
+
------
|
|
360
|
+
TypeError
|
|
361
|
+
当输入数据不是pandas.DataFrame格式时
|
|
362
|
+
ImportError
|
|
363
|
+
当xgboost未安装时
|
|
364
|
+
|
|
365
|
+
Examples
|
|
366
|
+
--------
|
|
367
|
+
>>> selected_vars, model, perf = backward_xgbm(
|
|
368
|
+
... train_data=train_df,
|
|
369
|
+
... varlist=feature_cols,
|
|
370
|
+
... dep='target',
|
|
371
|
+
... validation_data=val_df
|
|
372
|
+
... )
|
|
373
|
+
"""
|
|
374
|
+
try:
|
|
375
|
+
import xgboost as xgb
|
|
376
|
+
except ImportError:
|
|
377
|
+
raise ImportError("请安装xgboost: pip install xgboost")
|
|
378
|
+
|
|
379
|
+
from Modeling_Tool.Eval.Model_Eval_Tool import get_perf_summary
|
|
380
|
+
|
|
381
|
+
if varreduct_params is None:
|
|
382
|
+
varreduct_params = {}
|
|
383
|
+
|
|
384
|
+
if test_data_dict is None:
|
|
385
|
+
test_data_dict = {}
|
|
386
|
+
|
|
387
|
+
if spec_values is None:
|
|
388
|
+
spec_values = []
|
|
389
|
+
|
|
390
|
+
if monotone_constraints is None:
|
|
391
|
+
monotone_constraints = {}
|
|
392
|
+
|
|
393
|
+
# 构建单调约束向量
|
|
394
|
+
mc_dict = {var: monotone_constraints.get(var, 0) for var in varlist}
|
|
395
|
+
|
|
396
|
+
datain_all = OrderedDict()
|
|
397
|
+
datain_all["mdl"] = train_data
|
|
398
|
+
if validation_data is not None:
|
|
399
|
+
datain_all["hd"] = validation_data
|
|
400
|
+
datain_all.update(test_data_dict)
|
|
401
|
+
|
|
402
|
+
# 检查数据格式一致性
|
|
403
|
+
try:
|
|
404
|
+
for k, v in datain_all.items():
|
|
405
|
+
assert isinstance(v, pd.DataFrame)
|
|
406
|
+
except AssertionError:
|
|
407
|
+
logging.warning("请提供pandas.DataFrame格式数据")
|
|
408
|
+
raise TypeError("请提供pandas.DataFrame格式数据")
|
|
409
|
+
|
|
410
|
+
# 预设参数(保证模型可复现性)
|
|
411
|
+
hyperparams_preset = {
|
|
412
|
+
'eval_metric': stopping_metric,
|
|
413
|
+
'tree_method': 'exact',
|
|
414
|
+
'booster': 'gbtree',
|
|
415
|
+
'seed': seed,
|
|
416
|
+
'monotone_constraints': mc_dict
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
# 补充缺失的必需参数
|
|
420
|
+
lacked_params = [k for k in list(hyperparams_preset.keys()) if k not in list(varreduct_params.keys())]
|
|
421
|
+
for param in lacked_params:
|
|
422
|
+
varreduct_params[param] = hyperparams_preset[param]
|
|
423
|
+
|
|
424
|
+
weight_col = weight_col or wgt_col
|
|
425
|
+
|
|
426
|
+
# 构建 XGBoost 数据集
|
|
427
|
+
train_weight = resolve_sample_weight(data=train_data, weight_col=weight_col, expected_len=len(train_data))
|
|
428
|
+
xgb_train = xgb.DMatrix(train_data[varlist], label=train_data[dep], weight=train_weight)
|
|
429
|
+
|
|
430
|
+
evals = [(xgb_train, "mdl")]
|
|
431
|
+
if validation_data is not None:
|
|
432
|
+
valid_weight = resolve_sample_weight(
|
|
433
|
+
data=validation_data,
|
|
434
|
+
weight_col=validation_weight_col or weight_col,
|
|
435
|
+
expected_len=len(validation_data),
|
|
436
|
+
)
|
|
437
|
+
xgb_valid = xgb.DMatrix(validation_data[varlist], label=validation_data[dep], weight=valid_weight)
|
|
438
|
+
evals.append((xgb_valid, "hd"))
|
|
439
|
+
|
|
440
|
+
# 训练模型
|
|
441
|
+
evals_result = {}
|
|
442
|
+
model = xgb.train(
|
|
443
|
+
params=varreduct_params,
|
|
444
|
+
dtrain=xgb_train,
|
|
445
|
+
num_boost_round=num_boost_round,
|
|
446
|
+
evals=evals,
|
|
447
|
+
early_stopping_rounds=early_stopping_rounds,
|
|
448
|
+
evals_result=evals_result,
|
|
449
|
+
verbose_eval=False
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# 获取特征重要性并筛选变量
|
|
453
|
+
importance_raw = model.get_score(importance_type=importance_type)
|
|
454
|
+
importance_df = pd.DataFrame(
|
|
455
|
+
list(importance_raw.items()), columns=["feature", "importance"]
|
|
456
|
+
).sort_values("importance", ascending=False).reset_index(drop=True)
|
|
457
|
+
|
|
458
|
+
# 补充重要性为0的特征
|
|
459
|
+
missing_feats = [f for f in varlist if f not in importance_df["feature"].values]
|
|
460
|
+
if missing_feats:
|
|
461
|
+
zero_df = pd.DataFrame({"feature": missing_feats, "importance": 0.0})
|
|
462
|
+
importance_df = pd.concat([importance_df, zero_df], ignore_index=True)
|
|
463
|
+
|
|
464
|
+
importance_df["cum_importance"] = importance_df["importance"].cumsum() / (importance_df["importance"].sum() or 1)
|
|
465
|
+
|
|
466
|
+
selected_idx = importance_df[importance_df["cum_importance"] <= cum_importance_threshold].index.tolist()
|
|
467
|
+
|
|
468
|
+
if len(selected_idx) < min_vars:
|
|
469
|
+
selected_idx = list(range(min(min_vars, len(importance_df))))
|
|
470
|
+
|
|
471
|
+
selected_vars = importance_df.loc[selected_idx, "feature"].tolist()
|
|
472
|
+
|
|
473
|
+
if not ret_perf:
|
|
474
|
+
return selected_vars, model
|
|
475
|
+
|
|
476
|
+
# 计算性能
|
|
477
|
+
score_col = "_xgbm_score"
|
|
478
|
+
perf_dict = {}
|
|
479
|
+
|
|
480
|
+
for name, df in datain_all.items():
|
|
481
|
+
df_score = df.copy()
|
|
482
|
+
xgb_dmat = xgb.DMatrix(df_score[varlist])
|
|
483
|
+
df_score[score_col] = model.predict(xgb_dmat)
|
|
484
|
+
perf = get_perf_summary(
|
|
485
|
+
train=df_score if name == "mdl" else None,
|
|
486
|
+
validation=df_score if name == "hd" else None,
|
|
487
|
+
oot=df_score if name not in ("mdl", "hd") else None,
|
|
488
|
+
tgt_name=dep,
|
|
489
|
+
scr_name=score_col,
|
|
490
|
+
nbins=nbins,
|
|
491
|
+
precision=precision,
|
|
492
|
+
min_bin_prop=min_bin_prop,
|
|
493
|
+
include_missing=include_missing,
|
|
494
|
+
equal_freq=equal_freq,
|
|
495
|
+
ascending=ascending,
|
|
496
|
+
fillna=fillna,
|
|
497
|
+
spec_values=spec_values
|
|
498
|
+
)
|
|
499
|
+
perf_dict[name] = perf
|
|
500
|
+
|
|
501
|
+
return selected_vars, model, perf_dict
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class BackwardVariableEliminator:
|
|
505
|
+
"""
|
|
506
|
+
向后变量消除器。
|
|
507
|
+
|
|
508
|
+
封装LightGBM/XGBoost向后变量消除流程,
|
|
509
|
+
支持多轮消除和结果汇总。
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
train_data : pd.DataFrame
|
|
514
|
+
训练数据集
|
|
515
|
+
varlist : list of str
|
|
516
|
+
初始特征变量列表
|
|
517
|
+
dep : str
|
|
518
|
+
目标变量列名
|
|
519
|
+
model_type : str, default "lgbm"
|
|
520
|
+
模型类型,可选 "lgbm" 或 "xgbm"
|
|
521
|
+
validation_data : pd.DataFrame, optional
|
|
522
|
+
验证数据集
|
|
523
|
+
test_data_dict : dict, optional
|
|
524
|
+
测试数据集字典
|
|
525
|
+
|
|
526
|
+
Examples
|
|
527
|
+
--------
|
|
528
|
+
>>> eliminator = BackwardVariableEliminator(
|
|
529
|
+
... train_data=train_df,
|
|
530
|
+
... varlist=feature_cols,
|
|
531
|
+
... dep='target',
|
|
532
|
+
... model_type='lgbm',
|
|
533
|
+
... validation_data=val_df
|
|
534
|
+
... )
|
|
535
|
+
>>> results = eliminator.run(n_rounds=5)
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
def __init__(
|
|
539
|
+
self,
|
|
540
|
+
train_data: pd.DataFrame,
|
|
541
|
+
varlist: List[str],
|
|
542
|
+
dep: str,
|
|
543
|
+
model_type: str = "lgbm",
|
|
544
|
+
validation_data: Optional[pd.DataFrame] = None,
|
|
545
|
+
test_data_dict: Optional[Dict[str, pd.DataFrame]] = None,
|
|
546
|
+
weight_col: Optional[str] = None,
|
|
547
|
+
validation_weight_col: Optional[str] = None,
|
|
548
|
+
wgt_col: Optional[str] = None,
|
|
549
|
+
):
|
|
550
|
+
self.train_data = train_data
|
|
551
|
+
self.varlist = varlist
|
|
552
|
+
self.dep = dep
|
|
553
|
+
self.model_type = model_type.lower()
|
|
554
|
+
self.validation_data = validation_data
|
|
555
|
+
self.test_data_dict = test_data_dict or {}
|
|
556
|
+
self.weight_col = weight_col or wgt_col
|
|
557
|
+
self.validation_weight_col = validation_weight_col
|
|
558
|
+
self._results = []
|
|
559
|
+
|
|
560
|
+
def run(
|
|
561
|
+
self,
|
|
562
|
+
n_rounds: int = 5,
|
|
563
|
+
varreduct_params: Optional[Dict] = None,
|
|
564
|
+
stopping_metric: str = "auc",
|
|
565
|
+
seed: int = 42,
|
|
566
|
+
num_boost_round: int = 200,
|
|
567
|
+
early_stopping_rounds: int = 20,
|
|
568
|
+
importance_type: str = "gain",
|
|
569
|
+
cum_importance_threshold: float = 0.99,
|
|
570
|
+
min_vars: int = 10,
|
|
571
|
+
ret_perf: bool = True,
|
|
572
|
+
nbins: int = 10,
|
|
573
|
+
**kwargs,
|
|
574
|
+
) -> List[Dict]:
|
|
575
|
+
"""
|
|
576
|
+
运行多轮向后变量消除。
|
|
577
|
+
|
|
578
|
+
Parameters
|
|
579
|
+
----------
|
|
580
|
+
n_rounds : int, default 5
|
|
581
|
+
消除轮数
|
|
582
|
+
varreduct_params : dict, optional
|
|
583
|
+
模型超参数
|
|
584
|
+
stopping_metric : str, default "auc"
|
|
585
|
+
早停指标
|
|
586
|
+
seed : int, default 42
|
|
587
|
+
随机种子
|
|
588
|
+
num_boost_round : int, default 200
|
|
589
|
+
最大迭代轮数
|
|
590
|
+
early_stopping_rounds : int, default 20
|
|
591
|
+
早停轮数
|
|
592
|
+
importance_type : str, default "gain"
|
|
593
|
+
特征重要性类型
|
|
594
|
+
cum_importance_threshold : float, default 0.99
|
|
595
|
+
累计重要性阈值
|
|
596
|
+
min_vars : int, default 10
|
|
597
|
+
最小保留变量数
|
|
598
|
+
ret_perf : bool, default True
|
|
599
|
+
是否返回性能指标
|
|
600
|
+
nbins : int, default 10
|
|
601
|
+
分箱数
|
|
602
|
+
|
|
603
|
+
Returns
|
|
604
|
+
-------
|
|
605
|
+
list of dict
|
|
606
|
+
每轮消除结果列表
|
|
607
|
+
"""
|
|
608
|
+
current_vars = self.varlist.copy()
|
|
609
|
+
self._results = []
|
|
610
|
+
|
|
611
|
+
backward_fn = backward_lgbm if self.model_type == "lgbm" else backward_xgbm
|
|
612
|
+
|
|
613
|
+
for round_idx in range(1, n_rounds + 1):
|
|
614
|
+
logging.info(f"[BackwardVariableEliminator] Round {round_idx}/{n_rounds}, vars={len(current_vars)}")
|
|
615
|
+
|
|
616
|
+
result = backward_fn(
|
|
617
|
+
train_data=self.train_data,
|
|
618
|
+
varlist=current_vars,
|
|
619
|
+
dep=self.dep,
|
|
620
|
+
varreduct_params=copy.deepcopy(varreduct_params),
|
|
621
|
+
stopping_metric=stopping_metric,
|
|
622
|
+
seed=seed,
|
|
623
|
+
num_boost_round=num_boost_round,
|
|
624
|
+
early_stopping_rounds=early_stopping_rounds,
|
|
625
|
+
importance_type=importance_type,
|
|
626
|
+
cum_importance_threshold=cum_importance_threshold,
|
|
627
|
+
min_vars=min_vars,
|
|
628
|
+
validation_data=self.validation_data,
|
|
629
|
+
test_data_dict=self.test_data_dict,
|
|
630
|
+
ret_perf=ret_perf,
|
|
631
|
+
nbins=nbins,
|
|
632
|
+
weight_col=self.weight_col,
|
|
633
|
+
validation_weight_col=self.validation_weight_col,
|
|
634
|
+
**kwargs,
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
if ret_perf:
|
|
638
|
+
selected_vars, model, perf_dict = result
|
|
639
|
+
else:
|
|
640
|
+
selected_vars, model = result
|
|
641
|
+
perf_dict = {}
|
|
642
|
+
|
|
643
|
+
round_result = {
|
|
644
|
+
"round": round_idx,
|
|
645
|
+
"n_vars_in": len(current_vars),
|
|
646
|
+
"n_vars_out": len(selected_vars),
|
|
647
|
+
"selected_vars": selected_vars,
|
|
648
|
+
"model": model,
|
|
649
|
+
"perf": perf_dict,
|
|
650
|
+
}
|
|
651
|
+
self._results.append(round_result)
|
|
652
|
+
current_vars = selected_vars
|
|
653
|
+
|
|
654
|
+
if len(current_vars) <= min_vars:
|
|
655
|
+
logging.info(f"[BackwardVariableEliminator] Reached min_vars={min_vars}, stopping early.")
|
|
656
|
+
break
|
|
657
|
+
|
|
658
|
+
return self._results
|
|
659
|
+
|
|
660
|
+
def get_final_vars(self) -> List[str]:
|
|
661
|
+
"""获取最终筛选后的变量列表。"""
|
|
662
|
+
if not self._results:
|
|
663
|
+
return self.varlist
|
|
664
|
+
return self._results[-1]["selected_vars"]
|
|
665
|
+
|
|
666
|
+
def get_summary(self) -> pd.DataFrame:
|
|
667
|
+
"""获取每轮消除汇总表。"""
|
|
668
|
+
rows = []
|
|
669
|
+
for r in self._results:
|
|
670
|
+
rows.append({
|
|
671
|
+
"round": r["round"],
|
|
672
|
+
"n_vars_in": r["n_vars_in"],
|
|
673
|
+
"n_vars_out": r["n_vars_out"],
|
|
674
|
+
"vars_removed": r["n_vars_in"] - r["n_vars_out"],
|
|
675
|
+
})
|
|
676
|
+
return pd.DataFrame(rows)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
class BackwardEliminationAnalyzer:
|
|
680
|
+
"""
|
|
681
|
+
向后消除结果分析器。
|
|
682
|
+
|
|
683
|
+
对 BackwardVariableEliminator 的运行结果进行分析和可视化。
|
|
684
|
+
|
|
685
|
+
Parameters
|
|
686
|
+
----------
|
|
687
|
+
results : list of dict
|
|
688
|
+
BackwardVariableEliminator.run() 的返回值
|
|
689
|
+
|
|
690
|
+
Examples
|
|
691
|
+
--------
|
|
692
|
+
>>> analyzer = BackwardEliminationAnalyzer(results)
|
|
693
|
+
>>> analyzer.plot_var_reduction()
|
|
694
|
+
>>> final_vars = analyzer.get_stable_vars(top_n=20)
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
def __init__(self, results: List[Dict]):
|
|
698
|
+
self.results = results
|
|
699
|
+
|
|
700
|
+
def get_stable_vars(self, top_n: Optional[int] = None) -> List[str]:
|
|
701
|
+
"""
|
|
702
|
+
获取在所有轮次中均被保留的稳定变量。
|
|
703
|
+
|
|
704
|
+
Parameters
|
|
705
|
+
----------
|
|
706
|
+
top_n : int, optional
|
|
707
|
+
返回前N个稳定变量,None表示返回全部
|
|
708
|
+
|
|
709
|
+
Returns
|
|
710
|
+
-------
|
|
711
|
+
list of str
|
|
712
|
+
"""
|
|
713
|
+
if not self.results:
|
|
714
|
+
return []
|
|
715
|
+
|
|
716
|
+
stable = set(self.results[0]["selected_vars"])
|
|
717
|
+
for r in self.results[1:]:
|
|
718
|
+
stable &= set(r["selected_vars"])
|
|
719
|
+
|
|
720
|
+
stable_list = sorted(stable)
|
|
721
|
+
if top_n is not None:
|
|
722
|
+
stable_list = stable_list[:top_n]
|
|
723
|
+
return stable_list
|
|
724
|
+
|
|
725
|
+
def plot_var_reduction(
|
|
726
|
+
self,
|
|
727
|
+
figsize: Tuple[int, int] = (8, 4),
|
|
728
|
+
save_path: Optional[str] = None,
|
|
729
|
+
) -> None:
|
|
730
|
+
"""
|
|
731
|
+
绘制变量数量随消除轮次变化的折线图。
|
|
732
|
+
|
|
733
|
+
Parameters
|
|
734
|
+
----------
|
|
735
|
+
figsize : tuple, default (8, 4)
|
|
736
|
+
图形尺寸
|
|
737
|
+
save_path : str, optional
|
|
738
|
+
图片保存路径,None表示直接显示
|
|
739
|
+
"""
|
|
740
|
+
rounds = [r["round"] for r in self.results]
|
|
741
|
+
n_vars = [r["n_vars_out"] for r in self.results]
|
|
742
|
+
|
|
743
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
744
|
+
ax.plot(rounds, n_vars, marker="o", linewidth=2, color="#4C72B0")
|
|
745
|
+
ax.set_xlabel("消除轮次")
|
|
746
|
+
ax.set_ylabel("保留变量数")
|
|
747
|
+
ax.set_title("向后变量消除:变量数量变化")
|
|
748
|
+
ax.xaxis.set_major_locator(mticker.MaxNLocator(integer=True))
|
|
749
|
+
plt.tight_layout()
|
|
750
|
+
|
|
751
|
+
if save_path:
|
|
752
|
+
fig.savefig(save_path, dpi=150, bbox_inches="tight")
|
|
753
|
+
plt.close(fig)
|
|
754
|
+
else:
|
|
755
|
+
plt.show()
|
|
756
|
+
|
|
757
|
+
def get_perf_trend(self, dataset: str = "mdl", metric: str = "IV") -> pd.DataFrame:
|
|
758
|
+
"""
|
|
759
|
+
获取指定数据集上性能指标随轮次变化趋势。
|
|
760
|
+
|
|
761
|
+
Parameters
|
|
762
|
+
----------
|
|
763
|
+
dataset : str, default "mdl"
|
|
764
|
+
数据集名称,如 "mdl", "hd", "oot"
|
|
765
|
+
metric : str, default "IV"
|
|
766
|
+
性能指标列名
|
|
767
|
+
|
|
768
|
+
Returns
|
|
769
|
+
-------
|
|
770
|
+
pd.DataFrame
|
|
771
|
+
"""
|
|
772
|
+
rows = []
|
|
773
|
+
for r in self.results:
|
|
774
|
+
perf = r.get("perf", {})
|
|
775
|
+
if dataset in perf and perf[dataset] is not None:
|
|
776
|
+
val = perf[dataset].get(metric, None) if isinstance(perf[dataset], dict) else None
|
|
777
|
+
rows.append({"round": r["round"], metric: val})
|
|
778
|
+
return pd.DataFrame(rows)
|