概述
本部分内容基于<oil_data_for_tree.xlsx>这份数据,进行决策树构造,决策树的结果用于策略制定。
1. 数据预处理
1.1 数据大小 (50609, 19)
1.2 数据的维度
Index(['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保留一条记录,保留远的一条记录(这个根据业务来确定吧,这里的保留方式我也不确定)。
def 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 变量衍生-数值型进行聚合操作
gn = 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数量
gc = 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 将变量组合在一起
fn = pd.merge(base,gn,on= 'uid')
fn = pd.merge(fn,gc,on= 'uid')
fn.shape
fn = fn.fillna(0)
2. 模型训练
x = 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. 输出决策树图像,并作出决策
import 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. 数据预处理2. 模型训练3. 输出决策树图像,并作出决策所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复