信贷风险控制管理-策略生成及规则挖掘

  在信贷风险管理领域,通常有两种主要的风险控制方法,即规则引擎和风险模型。规则引擎使用一组简单的规则进行客户分类,使得不同客户群体的期望风险存在显著差异,并能够快速进行风险划分。而风险模型则使用机器学习技术预测客户的违约风险,尽管精度相对较高,但建模和上线周期较长。因此,在需要快速进行客户划分或者精度要求不高的场景中,一般会采用规则挖掘和规则引擎快速上线的方式。对于精度要求较高的场景,则会采用规则引擎粗筛+模型精选相结合的方式进行风险决策。

 本文以某公司的“油品贷”数据为例,使用决策树算法进行策略制定。

业务背景:某打车平台和某些加油站达成合作,联合推出油品贷业务,可以给打车平台的司机提供贷款等业务,但最近发现使用油品贷的人,坏账率很高,数据高达5%,否则项目会被砍掉。

  来申请油品贷的司机本身本身已经通过了评分卡,并分为六个评分等级A-F。公司领导发现只有给等级A的客户放款才能不亏钱,现需要我们在现在的基础上制定有效的规则策略,控制坏账率。
滴滴是和很多加油站有合作的,加油站会给滴滴提供司机数据。

变量类型 最终基础变量名(还需要做上述变换) 释义
数值统计型
oil_amount 加油升数
discount_amount 折扣金额
sale_amount 促销金额
amount 总金额
pay_amount 实际支付金额
coupon_amount 优惠券金额
payment_coupon_amount 支付优惠券金额
分类型
channel_code 渠道
oil_code 油品品类(规格)
scene 场景
source_app 来源端口(1货车帮、2微信)
call_source 订单来源(1:中化扫描枪 2:pos 3:找油网 4:油掌柜5:司机自助加油 6 油站线)

 

一,首先要导入所需要的库

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import tree

二,导入所需要的数据

data = pd.read_csv(r"F:\rankingcard.csv",index_col = 0)   ##导入数据

三,数据预览

3.1 查看数据基本情况

# 查看前5行数据详情
data. Head()

数据部分展示:

信贷风险控制管理-策略生成及规则挖掘_第1张图片

 3.2 查看数据基本情况

# 数据整体描述
data.describe()

信贷风险控制管理-策略生成及规则挖掘_第2张图片

 

 3.3查看数据维度

# 数据维度情况
data.shape

数据一共有50609行,19列,其中有18个特征,1列是标签。

3.4粗略查看数据类型、个数、判断缺失值情况

# 数据基本信息
data.info()

 信贷风险控制管理-策略生成及规则挖掘_第3张图片

 从这里可以发现数据中某些特征具有缺失值。

3.5 查看缺失值情况

## 缺失值百分比显示
data.isnull().sum()/len(data)

信贷风险控制管理-策略生成及规则挖掘_第4张图片

# 查看标签正负样本的分布情况
data['bad_ind'].value_counts()

 

## 可视化饼图
data['bad_ind'].value_counts().plot(kind='pie')

 信贷风险控制管理-策略生成及规则挖掘_第5张图片

 3.6 查看时间跨度

## 查看样本的时间跨度
data['create_dt'].min(),data['create_dt'].max()

 

 3.7 查看在时间上的分布

## 样本在时间上的分布情况,以月为单位进行聚合
data.groupby([pd.DatetimeIndex(data['create_dt']).year,pd.DatetimeIndex(data['create_dt']).month]).agg({"create_dt":np.size})

 信贷风险控制管理-策略生成及规则挖掘_第6张图片

四、数据预处理

4.1 根据特征变量类型和加工方式的不同进行划分

## org_lst 不需要做特殊变换,保留原始内容,然后直接去重
## agg_lst 数值型变量做聚合
## dstc_lst 离散型变量做count
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']
# 拷贝不同类型特征的数据,保留底表
df = data[org_lst].copy()
df[agg_lst] = data[agg_lst].copy()
df[dstc_lst] = data[dstc_lst].copy()

4.2 时间缺失值填充

# 按'uid','create_dt'进行逆序排序
df2 = df.sort_values(['uid','create_dt'], ascending = False)
## 之间已经看出oil_actv_dt没有缺失值
def time_isna(x, y):
    return y if str(x) == 'NaT' else x
# 用oil_actv_dt来对缺失的creat_dt做补全,
df2['create_dt'] = df2.apply(lambda x: time_isna(x.create_dt, x.oil_actv_dt), axis = 1)

4.3 样本截取

对creat_dt做补全,用oil_actv_dt来填补,并且截取6个月的数据

构造变量的时候不能直接对历史所有数据做累加。

否则随着时间的推移,变量分布会有很大的变化,否则规则很快就会不适用。

# 截取放款日和创建日期之差在6个月内的数据。
df2['dtn'] = (df2.oil_actv_dt - df2.create_dt).apply(lambda x :x.days)
df = df2[df2['dtn'] < 180]
df

4.3 重复样本进行去重操作

# 对org_list变量求历史贷款天数的最大间隔,并且去重,保留最新的一条数据
base = df[org_lst]
base['dtn'] = df['dtn']
base = base.sort_values(['uid','create_dt'],ascending = False)
base = base.drop_duplicates(['uid'],keep = 'first')
base. Shape  ##查看数据维度

此时引入一个概念

value = badrate 表示坏账人数除以总人数

(base.loc[:,'bad_ind']==1).sum()/len(base)  ##坏账率

 可以看出该油品贷的坏账率达到4.66%

五、特征衍生

5.1 特征衍生-连续型变量

做一些基础的求和、行数、最大最小值、均值等等特征衍生操作

# 对连续型变量进行聚合衍生
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')
    
    # 统计当前特征值大于0的个数
    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 + '_ran']
    if gn.empty == True:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    
    # 求当前特征历史数据变异系数,避免除0,使用0.01进行平滑
    tp = pd.DataFrame(df.groupby('uid').apply(lambda df:np.nanmean(df[i]) / (np.nanvar(df[i]) + 0.01)).reset_index())
    tp.columns = ['uid',i + '_cva']
    if gn.empty == True:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')

5.2对离散变量进行特征操作

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

5.3 将做好的特征衍生放到一个新的DataFrame中,使用表连接方式

fn = pd.merge(base,gn,on = 'uid')
fn = pd.merge(fn,gc,on='uid')
fn = fn.fillna(0)
fn.shape

六、训练树模型

# 移除训练集中的无关列
x = fn.drop(['uid','oil_actv_dt','create_dt','bad_ind','class_new'],axis = 1)
# 构建标签列
y = fn.bad_ind
# 采用CART树进行规则挖掘
r_tree = tree.DecisionTreeRegressor(
# r_tree = tree.DecisionTreeClassifier(
    max_depth = 3, 
    min_samples_leaf = 500, min_samples_split=5000)
r_tree = r_tree.fit(x, y)

该树的模型当中,设置三个参数,max_depth最大深度为3,min_samples_leaf表示在叶节点处需要的最小样本数为500,min_samples_split表示拆分内部节点所需的最少样本数为5000。

# 使用graphviz进行可视化展示
import graphviz 
dot_data = tree.export_graphviz(
    r_tree, 
    out_file = None, 
    feature_names = x.columns,  
    class_names = ['good','bad'],  
    filled=True, 
    rounded=True,  
    special_characters=True)  
graph = graphviz.Source(dot_data)  

信贷风险控制管理-策略生成及规则挖掘_第7张图片

 这样我们就建好了一棵树,接下来根据生成好的树开始生成策略。

## 生成策略
dff1 = fn.loc[(fn.amount_tot>48077.5)&(fn.coupon_amount_cnt>3.5)].copy()
dff1['level'] = 'oil_A'
dff2 = fn.loc[(fn.amount_tot>48077.5)&(fn.coupon_amount_cnt<=3.5)].copy()
dff2['level'] = 'oil_B'
dff3 = fn.loc[fn.amount_tot<=48077.5].copy()
dff3['level'] = 'oil_C'
dff1 = dff1.append(dff2) ##将dff2填入dff1
dff1 = dff1.append(dff3) ##将dff3填入dff1
len(dff1)  ##查看一下dff1的维度
last = dff1[['class_new','level','bad_ind','uid','oil_actv_dt','bad_ind']].copy()
last['oil_actv_dt'] = last['oil_actv_dt'].apply(lambda x:str(x)[:7]).copy()  ##截取字符串前7个字符
last. head()
last.to_excel(path, index = False)  ##将弄好的文件存放到某一个文件夹

将保存好的excel打开,使用数据透视表进行数据分析:

信贷风险控制管理-策略生成及规则挖掘_第8张图片

 最后结果如下所示:结合原始数据中的类别(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)结果对比 :以前只放贷给评分卡等级为A的用户,现在对深色的都可以放贷,可放贷款人数增加且坏账率降低。

可放款人数 可放款人数占比 坏账率
现计划 5052 45.5% 1.6%
原计划 1901 17.1% 2.9%

 

你可能感兴趣的:(python,决策树,机器学习)