本部分内容基于
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. 根据各个叶节点的value值,可以对不同叶子节点中的样本制定不同策略。
2.1 直接拒绝 :pay_amount_tot <= 240387的样本。
2.2 人工审核:pay_amount_tot >240387但discount_amount_cn<=3.5的样本。
2.3 通过审核 :discount_amount_cn>3.5的样本。



