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,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)