本部分内容基于<oil_data_for_tree.xlsx>这份数据,进行决策树构造,决策树的结果用于策略制定。
1. 数据预处理
1.1 数据大小 (50609, 19)
1.2 数据的维度
1
2
3
4
5Index(['uid', 'oil_actv_dt', 'create_dt', 'total_oil_cnt', 'pay_amount_total', 'class_new', 'bad_ind', 'oil_amount', 'discount_amount', 'sale_amount', 'amount', 'pay_amount', 'coupon_amount', 'payment_coupon_amount', 'channel_code', 'oil_code', 'scene', 'source_app', 'call_source'], dtype='object')
其中,uid是主键,oil_actv_dt为首次贷款的日期,create_dt为每条数据产生的业务时间(我猜的)。
1.3 数据分类
将数据分为三类:直接去重的、需要进行聚合的数值型变量、需要进行计数(去重)的。
org_lst = ['uid','create_dt','oil_actv_dt','class_new','bad_ind']
agg_lst = ['oil_amount','discount_amount','sale_amount','amount','pay_amount','coupon_amount','payment_coupon_amount']dstc_lst = ['channel_code','oil_code','scene','source_app','call_source']
后续将基于uid对三类数据的处理结果进行连接,形成用于训练模型的一张宽表。
1.4 不同类别数据进行不同处理
对数据进行加工之前,需要先做两个预处理的操作。
1. 针对create_dt存在部分的缺失, 用oil_actv_dt进行补全。
2. 只保留oil_actv_dt的180天内的数据。
remark:构造变量的时候不能直接对历史所有数据做累加。 否则随着时间推移,变量分布会有很大的变化。
1.4.1 直接去重
去重,每个uid保留一条记录,保留远的一条记录(这个根据业务来确定吧,这里的保留方式我也不确定)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28def time_isna(x,y): if str(x) == 'NaT': x = y else: x = x return x df2 = df.sort_values(['uid','create_dt'],ascending = False) # 按照uid和create_dt降序排列->感觉这一步没什么意义? df2['create_dt'] = df2.apply(lambda x: time_isna(x.create_dt,x.oil_actv_dt),axis = 1) # axis = 1就是对行进行循环,对每一行使用apply里面的函数 # 做补全的时候,也可以考虑下面的方法: # df2_test = df.sort_values(['uid','create_dt'],ascending = False) # df2_test.loc[df2_test.create_dt.isna(), 'create_dt'] = df2_test.loc[df2_test.create_dt.isna(), 'oil_actv_dt'] df2['dtn'] = (df2.oil_actv_dt - df2.create_dt).apply(lambda x :x.days) df = df2[df2['dtn']<180] df.head() # 保留申请贷款前180天内的数据 base = df[org_lst] base['dtn'] = df['dtn'] base = base.sort_values(['uid','create_dt'],ascending = False) # 按create_dt进行降序排列 # base = base.drop_duplicates(['uid'],keep = 'first') base = base.drop_duplicates(['uid'],keep = 'last') # 私以为,求最大间隔天数的话,应该keep last(因为sort_values用的依据的使create_dt) base.shape
1.4.2 变量衍生-数值型进行聚合操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73gn = pd.DataFrame() for i in agg_lst: # 计数 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:len(df[i])).reset_index()) tp.columns = ['uid',i + '_cnt'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 大于零的个数 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.where(df[i]>0,1,0).sum()).reset_index()) tp.columns = ['uid',i + '_num'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 非空元素的和 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nansum(df[i])).reset_index()) tp.columns = ['uid',i + '_tot'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 非空元素的平均值 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanmean(df[i])).reset_index()) tp.columns = ['uid',i + '_avg'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 非空元素的最大值 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanmax(df[i])).reset_index()) tp.columns = ['uid',i + '_max'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 最小值 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanmin(df[i])).reset_index()) tp.columns = ['uid',i + '_min'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 方差 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanvar(df[i])).reset_index()) tp.columns = ['uid',i + '_var'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 最大值-最小值 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanmax(df[i]) -np.nanmin(df[i]) ).reset_index()) tp.columns = ['uid',i + '_var'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') # 变异系数 tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanvar(df[i])/max(np.nanmean(df[i]),1)).reset_index()) tp.columns = ['uid',i + '_var'] if gn.empty == True: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left')
1.4.3 变量衍生-求distinct数量
1
2
3
4
5
6
7
8gc = pd.DataFrame() for i in dstc_lst: tp = pd.DataFrame(df.groupby('uid').apply(lambda df: len(set(df[i]))).reset_index()) tp.columns = ['uid',i + '_dstc'] if gc.empty == True: gc = tp else: gc = pd.merge(gc,tp,on = 'uid',how = 'left')
1.5 将变量组合在一起
1
2
3
4
5fn = pd.merge(base,gn,on= 'uid') fn = pd.merge(fn,gc,on= 'uid') fn.shape fn = fn.fillna(0)
2. 模型训练
1
2
3
4
5
6
7x = fn.drop(['uid','oil_actv_dt','create_dt','bad_ind','class_new'],axis = 1) # 删去一些列 y = fn.bad_ind.copy() from sklearn import tree dtree = tree.DecisionTreeRegressor(max_depth = 2,min_samples_leaf = 500,min_samples_split = 5000) dtree = dtree.fit(x,y)
使用回归树模型。
3. 输出决策树图像,并作出决策
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import pydotplus from IPython.display import Image from sklearn.externals.six import StringIO import os os.environ["PATH"] += os.pathsep + 'D:setupGraphvizbin' # 为了找到graphviz with open( "dt.dot", "w") as f: tree.export_graphviz(dtree, out_file=f) # 可视化函数 dot_data = StringIO() # 在内存中读写str tree.export_graphviz(dtree, out_file=dot_data, feature_names=x.columns, class_names=['bad_ind'], filled=True, rounded=True, special_characters=True) graph = pydotplus.graph_from_dot_data(dot_data.getvalue()) # getvalue()方法用于获得写入的str Image(graph.create_png())
解读:
1. 图中的value可看作每个叶节点中坏样本出现的概率。
value是指叶节点的预测值,由于令损失函数(mse)最小的预测值为平均值,故途中的value也可以理解为叶节点中样本为坏样本的概率。
2. 对不同叶子节点中的样本进行分类。
2.1 oil_a :pay_amount_tot <= 240387的样本。
2.2 oil_b :pay_amount_tot >240387但discount_amount_cn<=3.5的样本。
2.3 oil_c :discount_amount_cn>3.5的样本。
然后结合原始数据中的类别(class_new),对样本进一步细分,选出bad_rate较小的类别,作为可放宽群体。
1)坏账率分布 | ||||||||
坏账率分布 | 贷前分类 | |||||||
A | B | C | D | E | F | 总计 | ||
油品分类 | oil_A | 0.9% | 0.7% | 1.6% | 1.7% | 2.9% | 5.5% | 1.2% |
oil_B | 1.8% | 2.2% | 2.7% | 5.3% | 6.2% | 13.1% | 3.0% | |
oil_C | 5.1% | 6.7% | 6.3% | 5.9% | 15.2% | 19.9% | 7.4% | |
总计 | 2.9% | 3.9% | 4.2% | 4.9% | 10.6% | 16.1% | 4.7% |
2)人数分布 | ||||||||
人数分布 | 贷前分类 | |||||||
A | B | C | D | E | F | 总计 | ||
油品分类 | oil_A | 4.9% | 12.6% | 3.9% | 2.6% | 0.9% | 0.7% | 25.6% |
oil_B | 5.0% | 12.5% | 4.1% | 3.2% | 0.9% | 0.8% | 26.4% | |
oil_C | 7.3% | 21.6% | 7.5% | 6.8% | 2.4% | 2.4% | 48.0% | |
总计 | 17.1% | 46.7% | 15.5% | 12.6% | 4.3% | 3.8% | 100.0% |
3)结果比对 | |||
可放款人数 | 可放款人数占比 | 坏账率 | |
现计划 | 5052 | 45.5% | 1.6% |
原计划 | 1901 | 17.1% | 2.9% |
remark:
原计划为对class_new值为A的客户进行放款。
最后
以上就是漂亮发卡最近收集整理的关于金融风控实战入门-决策树规则挖掘1. 数据预处理2. 模型训练3. 输出决策树图像,并作出决策的全部内容,更多相关金融风控实战入门-决策树规则挖掘1.内容请搜索靠谱客的其他文章。
发表评论 取消回复