Skip to content

Commit 5667447

Browse files
committed
fix rule report
1 parent c103e79 commit 5667447

3 files changed

Lines changed: 49 additions & 13 deletions

File tree

scorecardpipeline/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .rule import Rule
2020

2121

22-
__version__ = "0.1.30"
22+
__version__ = "0.1.31"
2323
__all__ = (
2424
"__version__"
2525
, "FeatureSelection", "FeatureImportanceSelector", "StepwiseSelection", "Combiner", "WOETransformer"

scorecardpipeline/processing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,14 +556,14 @@ def export(self, to_json=None):
556556
"""
557557
return self.combiner.export(to_json=to_json)
558558

559-
def load(self, from_json=None):
559+
def load(self, from_json):
560560
"""特征分箱器加载离线保存的 json 文件
561561
562562
:param from_json: json 文件的路径
563563
564564
:return: Combiner,特征分箱器
565565
"""
566-
self.combiner.load(from_json=from_json)
566+
self.combiner.load(from_json)
567567
return self
568568

569569
@classmethod

scorecardpipeline/rule.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from sklearn.utils import check_array
1414
from sklearn.metrics import f1_score, recall_score, accuracy_score, precision_score
1515

16-
from .processing import feature_bin_stats
16+
from .processing import feature_bin_stats, Combiner
1717

1818

1919
def _get_context(X, feature_names):
@@ -75,6 +75,24 @@ def json2expr(data, max_index, feature_list):
7575

7676
class Rule:
7777
def __init__(self, expr): # expr 既可以传递字符串,也可以传递dict
78+
"""规则集
79+
80+
:param expr: 类似 DataFrame 的 query 方法传参方式即可,目前仅支持数值型变量规则
81+
82+
**参考样例**
83+
84+
>>> from scorecardpipeline import *
85+
>>> target = "creditability"
86+
>>> data = germancredit()
87+
>>> data[target] = data[target].map({"good": 0, "bad": 1})
88+
>>> data = data.select_dtypes("number") # 暂不支持字符型规则
89+
>>> rule1 = Rule("duration_in_month < 10")
90+
>>> rule2 = Rule("credit_amount < 500")
91+
>>> rule1.report(data, target=target)
92+
>>> rule2.report(data, target=target)
93+
>>> (rule1 | rule2).report(data, target=target)
94+
>>> (rule1 & rule2).report(data, target=target)
95+
"""
7896
self._state = RuleState.INITIALIZED
7997
self.expr = expr
8098

@@ -113,9 +131,22 @@ def predict(self, X: DataFrame, part=""): # dict预测对应part_dict 、字符
113131

114132
return result
115133

116-
def report(self, datasets, target="target", overdue="overdue", dpd=-1, del_grey=False, valid=None, desc="", return_cols=None, prior_rules=None):
134+
def report(self, datasets, target="target", overdue="overdue", dpd=-1, del_grey=False, desc="", return_cols=None, prior_rules=None) -> pd.DataFrame:
135+
"""规则效果报告表格输出
136+
137+
:param datasets: 数据集,需要包含 目标变量 或 逾期天数,当不包含目标变量时,会通过逾期天数计算目标变量,同时需要传入逾期定义的DPD天数
138+
:param target: 目标变量名称,默认 target
139+
:param desc: 规则相关的描述,会出现在返回的表格当中
140+
:param return_cols: 指定返回的字段列表,默认不传
141+
:param prior_rules: 先验规则,可以传入先验规则先筛选数据后再评估规则效果
142+
:param overdue: 逾期天数字读名称
143+
:param dpd: 逾期定义方式,逾期天数 > DPD 为 1,其他为 0,仅 overdue 字段起作用时有用
144+
:param del_grey: 是否删除逾期天数 (0, dpd] 的数据,仅 overdue 字段起作用时有用
145+
146+
:return: pd.DataFrame,规则效果评估表
147+
"""
117148
if return_cols is None:
118-
return_cols = ['指标名称', "指标含义", '分箱', '样本总数', '样本占比', '好样本数', '好样本占比', '坏样本数', '坏样本占比', '坏样本率', 'LIFT值', '分档KS值']
149+
return_cols = ['指标名称', "指标含义", '分箱', '样本总数', '样本占比', '好样本数', '好样本占比', '坏样本数', '坏样本占比', '坏样本率', 'LIFT值']
119150
if desc is None or desc == "" and "指标含义" in return_cols:
120151
return_cols.remove("指标含义")
121152

@@ -130,14 +161,16 @@ def report(self, datasets, target="target", overdue="overdue", dpd=-1, del_grey=
130161
rule_expr = self.expr
131162

132163
if prior_rules:
133-
prior_tables = prior_rules.report(datasets, target=target, overdue=overdue, dpd=dpd, del_grey=del_grey, valid=valid, desc=desc, return_cols=return_cols, prior_rules=None)
164+
prior_tables = prior_rules.report(datasets, target=target, overdue=overdue, dpd=dpd, del_grey=del_grey, desc=desc, return_cols=return_cols, prior_rules=None)
134165
temp = datasets[~prior_rules.predict(datasets)]
135166
rule_result = pd.DataFrame({rule_expr: np.where(self.predict(temp), "命中", "未命中"), "target": temp[target].tolist()})
136167
else:
137168
prior_tables = pd.DataFrame(columns=return_cols)
138169
rule_result = pd.DataFrame({rule_expr: np.where(self.predict(datasets), "命中", "未命中"), "target": datasets[target].tolist()})
139170

140-
table = feature_bin_stats(rule_result, rule_expr, rules=[["命中"], ["未命中"]], desc=desc, return_cols=return_cols)
171+
combiner = Combiner(target=target)
172+
combiner.load({rule_expr: [["命中"], ["未命中"]]})
173+
table = feature_bin_stats(rule_result, rule_expr, combiner=combiner, desc=desc, return_cols=return_cols)
141174

142175
# 准确率、精确率、召回率、F1分数
143176
metrics = pd.DataFrame({
@@ -149,14 +182,17 @@ def report(self, datasets, target="target", overdue="overdue", dpd=-1, del_grey=
149182
})
150183
table = table.merge(metrics, on="分箱", how="left")
151184

185+
# 规则上线后增益评估
186+
# 坏账率变化情况: 上线后拒绝多少比例的坏客户同时拒绝后坏账水平多少,在原始数据基础上换张改善多少
187+
total_bad, total = table["坏样本数"].sum(), table["样本总数"].sum()
188+
total_bad_rate = total_bad / total
189+
table["坏账改善"] = (total_bad_rate - (total_bad - table["坏样本数"]) / (total - table["样本总数"])) / total_bad_rate
190+
152191
if prior_rules:
153192
prior_tables.insert(loc=0, column="规则分类", value=["先验规则"] * len(prior_tables))
193+
prior_tables["坏账改善"] = np.nan
154194
table.insert(loc=0, column="规则分类", value=["验证规则"] * len(table))
155-
table = pd.concat([prior_tables, table])
156-
157-
# 规则上线后增益评估
158-
# 通过率变化情况: 上线后拒绝多少客户
159-
# 坏账率变化情况: 上线后拒绝多少比例的坏客户同时拒绝后坏账水平多少
195+
table = pd.concat([prior_tables, table]).set_index(["规则分类"])
160196

161197
return table
162198

0 commit comments

Comments
 (0)