当前位置: 首页 > article >正文

机器学习与金融风控项目篇-day03-业务规则挖掘与特征构建-特征衍生

一.  业务规则(特征)挖掘

规则挖掘简介

两种常见的风险规避手段

  • AI模型

  • 规则

如何使用规则进行风控

  • 使用一系列判断逻辑对客户群体进行区分,不同群体逾期风险有显著差别

    • 举例:多头借贷数量是否超过一定数量

  • 如果一条规则将用户划分到高风险组,则直接拒绝,如果划分到低风险组则进入到下一规则

规则和AI模型的优缺点

  1. 规则,可以快速使用,便于业务人员理解,但判断相对简单粗暴一些,单一维度不满足条件直接拒绝

  2. AI模型,开发周期长,对比使用规则更复杂,但更灵活,用于对风控精度要求高的场景

可以通过AI模型辅助建立规则引擎,决策树很适合规则挖掘的场景

规则挖掘案例

案例背景

  • 某互联网公司拥有多个业务板块,每个板块下都有专门的贷款产品

    • 外卖平台业务的骑手可以向平台申请“骑手贷”

    • 电商平台业务的商户可以申请“网商贷”

    • 网约车业务的司机可以向平台申请“司机贷”

  • 公司有多个类似的场景,共用相同的规则引擎及申请评分卡,贷款人都是该公司的兼职人员

  • 近期发现,“司机贷”的逾期率较高

    • 整个金融板块30天逾期率为1.5%

    • “司机贷”产品的30天逾期超过了5%

  • 给司机增加额外的规则减少违约率

期望解决方案

  • 现有的风控架构趋于稳定

  • 希望快速开发快速上线,解决问题

    • 尽量不使用复杂的方法

    • 考虑使用现有数据挖掘出合适的业务规则

数据字典

变量类型基础变量名释义
数值型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 油站线)
class_new用户评级
日期时间create_dt账户创建时间
oil_actv_dt放款时间
标签bad_ind正负样本标记

代码演示

特征构造(少->多)

原始数据的特征太少,考虑在原始特征基础上衍生出一些新的特征来,将特征分成三类分别处理

  1. 数值类型变量:按照id分组后,采用多种方式聚合,衍生新特征

  2. 分类类型变量,按照id分组后,聚合查询条目数量,衍生新特征

  3. 其它:日期时间类型,是否违约(标签),用户评级等不做特征衍生处理

# 原始列(不用作特征构建)
org_list = ['uid','create_dt','oil_actv_dt','class_new','bad_ind']
​
# 特征工程: 挖掘 -> 构造新的列 -> 特征构造(衍生)
# 衍生就是各种聚合, 不用关心有用否, 后期会进行特征筛选
# 聚合列(正常可也使用原有的列, 比较构造出来的列, 看那些影响大)
agg_list = ['oil_amount','discount_amount','sale_amount','amount','pay_amount','coupon_amount','payment_coupon_amount']
​
# 类别型特征, 用于特征构建(衍生), 衍生方式是统计数量
count_list = ['channel_code','oil_code','scene','source_app','call_source']
​
# 上述方式是通用的做法, 实际公司有自己的特征构造方式, 直接用

数据预处理

填充缺失值
# 用放款日期填充创建账户日期
# 对create_dt列进行时间哎填充, 可以使用oil_actv_dt来填充create_dt缺失值
# 自定函数时间填充
def fill_time(create_dt, oil_actv_dt):
    if str(create_dt) == 'NaT':
        create_dt = oil_actv_dt
    return create_dt
# 填充create_dt列
df2['create_dt'] = df2.apply(lambda line:fill_time(line['create_dt'], line['oil_actv_dt']), axis=1)
df2
计算天数
# 计算创建账户日期到放款日期的天数
df2['dtn'] = (df2['oil_actv_dt'] - df2['create_dt']).apply(lambda x:x.days)
# df2['dtn'] = (df2['oil_actv_dt'] - df2['create_dt']).dt.days    # 效果同上
df2.tail(100)
# 过滤近半年的数据
df = df2[df2['dtn'] < 180]
df
分组去重
base = df[org_list]
base['dtn'] = df['dtn']
base = base.sort_values(['uid','create_dt'],ascending = False)      # 按照uid和create_dt降序排列(方便保留最近的数据)
base = base.drop_duplicates(['uid'],keep = 'first')     # 去除同一用户的不同标签(同一用户只有一条数据), 防止模型训练效果不佳
base.shape

特征衍生

数值衍生
# 数值型衍生
gn = pd.DataFrame()
for i in agg_list:
    tp = df.groupby('uid').apply(lambda df:len(df[i])).reset_index()
    tp.columns = ['uid',i + '_cnt']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求历史特征值大于0的个数
    tp = df.groupby('uid').apply(lambda df:np.where(df[i]>0,1,0).sum()).reset_index()
    tp.columns = ['uid',i + '_num']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求和
    tp = df.groupby('uid').apply(lambda df:np.nansum(df[i])).reset_index()
    tp.columns = ['uid',i + '_tot']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求平均值
    tp = df.groupby('uid').apply(lambda df:np.nanmean(df[i])).reset_index()
    tp.columns = ['uid',i + '_avg']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求最大值
    tp = df.groupby('uid').apply(lambda df:np.nanmax(df[i])).reset_index()
    tp.columns = ['uid',i + '_max']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求最小值
    tp = df.groupby('uid').apply(lambda df:np.nanmin(df[i])).reset_index()
    tp.columns = ['uid',i + '_min']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求方差
    tp = df.groupby('uid').apply(lambda df:np.nanvar(df[i])).reset_index()
    tp.columns = ['uid',i + '_var']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
    #求极差
    tp = df.groupby('uid').apply(lambda df:np.nanmax(df[i]) -np.nanmin(df[i]) ).reset_index()
    tp.columns = ['uid',i + '_ran']
    if gn.empty:
        gn = tp
    else:
        gn = pd.merge(gn,tp,on = 'uid',how = 'left')
类别衍生
# 类别衍生
gc = pd.DataFrame()
for i in count_list:
    tp = df.groupby('uid').apply(lambda df: len(set(df[i]))).reset_index()
    tp.columns = ['uid',i + '_dstc']
    if gc.empty:
        gc = tp
    else:
        gc = pd.merge(gc,tp,on = 'uid',how = 'left')

拼接衍生后字段

拼接
# 拼接base和gn
fn = pd.merge(base,gn,on = 'uid',how = 'left')
# 拼接fn和gc
fn = pd.merge(fn,gc,on = 'uid',how = 'left')
fn.shape
填充空值
# 填充拼接后的控制
fn.isna().sum()
fn.fillna(0, inplace=True)
模型训练
模型训练
from sklearn.tree import DecisionTreeRegressor
x = fn.drop(['uid','oil_actv_dt','create_dt','bad_ind','class_new'],axis = 1)
y = fn['bad_ind']
​
dtree = DecisionTreeRegressor(max_depth = 2,min_samples_leaf = 500,min_samples_split = 5000)
dtree.fit(x,y)
结果可视化
# 结果可视化
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
plot_tree(dtree,feature_names=x.columns,
                         class_names=['bad_ind'],
                         filled=True, rounded=True)
plt.show()

day03-特征工程与特征构造

一. 数据准备

有专业的人员来做

数据的内在逻辑

数据库建模:

用于业务系统, 后端开发工程师

数仓建模:

用于构建数仓, 是数仓工程师来建模

如果没有, 可以使用pandas/sql来做

样本设计和特征框架

定义观察期样本

  • 确定观察期(定X时间切面)和表现期(定Y的标签)

  • 确认样本数目是否合理

数据EDA(探索性数分)

  1. 看数据总体分布

    data.shape

    data.isnull()

    data.info()

    data.describe()

  2. 看好坏样本分布差异

    data[data[label]==0].describe() 好用户

    data[data[label]==1].describe() 坏用户

  3. 看单个数据

    data.sample(n=10,random_state=1)

梳理特征框架

RFM生成新特征

举例 行为评分卡中的用户账单还款特征

用户账单关键信息:时间,金额,还款,额度

总结

数据准备的逻辑

数据来源(征信数据)

梳理数据内在逻辑(关联关系)

查看数据分布(EDA)

二. 特征构造

静态信息特征和时间截面特征

用户静态信息特征

用户的基本信息(半年内不会变化)

如: 姓名, 性别,年龄, 籍贯, 政治面貌, 国籍

用户时间截面特征

未来信息当前时间截面之后的数据(时间戳)

时间截面数据在取数的时候要小心,避免使用未来信息

产生未来信息最直接的原因:缺少快照表

快照表,每天照个相片 23:00点 把当天的数据 备份一份

  • 快照表只会保存当天最终的状态

日志表,每一次操作都记一次, 不会update,只有insert 操作一次记录一次

  • 每一次操作都会记录下来

金融相关数据原则上都需要快照表记录所有痕迹(额度变化情况,多次申请的通过和拒绝情况...)

缺少快照表的可能原因

  • 快照表消耗资源比较大,为了性能不做

  • 原有数据表设计人员疏忽,没做

  • 借用其他业务数据(如电商)做信贷

举例

首次借贷 二次借贷 爬虫授权 三次借贷

——————————————————————→

用户借款授权爬虫逾期
u1l11N0
u1l12N0
u1l13Y0
u2l21N0
u2l22N0
u2l23Y1
u3l11N0
u3l12N0
u3l13Y0

实际存储

用户授权
u1Y
u2Y
u3Y
用户借款逾期
u1l110
u1l120
u1l130
u2l210
u2l220
u2l231
u3l110
u3l120
u3l130

join 结果

用户借款授权爬虫逾期
u1l11Y0
u1l12Y0
u1l13Y0
u2l21Y0
u2l22Y0
u2l23Y1
u3l11Y0
u3l12Y0
u3l13Y0

解决方案:加入快照的存储

用户授权时间
u1Yt3
u2Yt3
u3Yt3

时间序列特征

用户时间序列特征

从观察点往前回溯一段时间的数据

时间序列: 一段时间内的数据

时间序列特征衍生

原理

特征聚合:将单个特征的多个时间节点取值进行聚合。特征聚合是传统评分卡建模的主要特征构造方法。

举例,计算每个用户的额度使用率,记为特征ft,按照时间轴以月份为切片展开

  • 申请前30天内的额度使用率ft1

  • 申请前30天至60天内的额度使用率ft2

  • 申请前60天至90天内的额度使用率ft3

  • 申请前330天至360天内的额度使用率ft12

  • 得到一个用户的12个特征

注意事项
在机器学习阶段的模型训练中, 不提倡直接做特征
而是先跑baseline模型
当baseline模型达不到逾期, 可以做一些和特征相关的操作
​
10个特征
AUC 0.61
​
1. 筛选特征
​
2. 特征衍生
​
​
方式1: 聚合(数值)
import pandas as pd
import numpy as np
data = pd.read_excel('../data/textdata.xlsx')
data.head()

显示结果

customer_idft1ft2ft3ft4ft5ft6ft7ft8ft9...gt3gt4gt5gt6gt7gt8gt9gt10gt11gt12
0111911.01213181012NaNNaN...100181012NaNNaNNaNNaNNaN
111211-11.01010131310NaNNaN...1010131310NaNNaNNaNNaNNaN
2113011.01012610025.010.0...1012610025.010.0NaNNaNNaN
3114-7-1.09870-1910.011.0...1010120-1910.011.0NaNNaNNaN
411511NaN6100171910.030.0...6100171910.030.015.0NaNNaN

可以根据这个时间序列进行基于经验的人工特征衍生,例如计算最近P个月特征大于0的月份数

#最近p个月,ft>0的月份数
def Num(ft,p):  #ft 特征名字 p特征大于0的月份数
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.where(df>0,1,0).sum(axis=1)
    return ft+'_num'+str(p),auto_value

计算最近P个月特征ft等于0的月份数

#最近p个月,ft=0的月份数
def zero_cnt(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.where(df==0,1,0).sum(axis=1)
    return ft+'_zero_cnt'+str(p),auto_value

计算近p个月特征ft大于0的月份数是否大于等于1

#最近p个月,ft>0的月份数是否>=1     
def Evr(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    arr=np.where(df>0,1,0).sum(axis=1)
    auto_value = np.where(arr,1,0)
    return ft+'_evr'+str(p),auto_value

计算最近p个月特征ft的均值

#最近p个月,ft均值
def Avg(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.nanmean(df,axis = 1 )
    return ft+'_avg'+str(p),auto_value

计算最近p个月特征ft的和,最大值,最小值

#最近p个月,ft和
def Tot(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.nansum(df,axis = 1)
    return ft+'_tot'+str(p),auto_value
​
#最近(2,p+1)个月,ft和
def Tot2T(ft,p):
    df=data.loc[:,ft+'2':ft+str(p+1)]
    auto_value=df.sum(1)
    return ft+'_tot2t'+str(p),auto_value
​
#最近p个月,ft最大值
def Max(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.nanmax(df,axis = 1)
    return ft+'_max'+str(p),auto_value
​
#最近p个月,ft最小值
def Min(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.nanmin(df,axis = 1)
    return ft+'_min'+str(p),auto_value

其余衍生方法

#最近p个月,最近一次ft>0到现在的月份数
def Msg(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    df_value=np.where(df>0,1,0)
    auto_value=[]
    for i in range(len(df_value)):
        row_value=df_value[i,:]
        if row_value.max()<=0:
            indexs='0'
            auto_value.append(indexs)
        else:
            indexs=1
            for j in row_value:
                if j>0:
                    break
                indexs+=1
            auto_value.append(indexs)
    return ft+'_msg'+str(p),auto_value
 
​
#最近p个月,最近一次ft=0到现在的月份数
def Msz(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    df_value=np.where(df==0,1,0)
    auto_value=[]
    for i in range(len(df_value)):
        row_value=df_value[i,:]
        if row_value.max()<=0:
            indexs='0'
            auto_value.append(indexs)
        else:
            indexs=1
            for j in row_value:
                if j>0:
                    break
                indexs+=1
            auto_value.append(indexs)
    return ft+'_msz'+str(p),auto_value   
    
#当月ft/(最近p个月ft的均值)
def Cav(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = df[ft+'1']/np.nanmean(df,axis = 1 ) 
    return ft+'_cav'+str(p),auto_value 
​
#当月ft/(最近p个月ft的最小值)
def Cmn(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = df[ft+'1']/np.nanmin(df,axis = 1 ) 
    return ft+'_cmn'+str(p),auto_value 
​
#最近p个月,每两个月间的ft的增长量的最大值
def Mai(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])     
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        for k in range(len(df_value)-1):
            minus = df_value[k] - df_value[k+1]
            value_lst.append(minus)
        auto_value.append(np.nanmax(value_lst))     
    return ft+'_mai'+str(p),auto_value 
​
#最近p个月,每两个月间的ft的减少量的最大值
def Mad(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])      
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        for k in range(len(df_value)-1):
            minus = df_value[k+1] - df_value[k]
            value_lst.append(minus)
        auto_value.append(np.nanmax(value_lst))     
    return ft+'_mad'+str(p),auto_value 
​
#最近p个月,ft的标准差
def Std(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.nanvar(df,axis = 1)
    return ft+'_std'+str(p),auto_value 
    
#最近p个月,ft的变异系数
def Cva(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value=np.nanvar(df,axis = 1)/(np.nanmean(df,axis = 1 )+1e-10)
    return ft+'_cva'+str(p),auto_value 
​
#(当月ft) - (最近p个月ft的均值)
def Cmm(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = df[ft+'1'] - np.nanmean(df,axis = 1 ) 
    return ft+'_cmm'+str(p),auto_value 
​
#(当月ft) - (最近p个月ft的最小值)
def Cnm(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = df[ft+'1'] - np.nanmin(df,axis = 1 ) 
    return ft+'_cnm'+str(p),auto_value 
​
#(当月ft) - (最近p个月ft的最大值)
def Cxm(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = df[ft+'1'] - np.nanmax(df,axis = 1 ) 
    return ft+'_cxm'+str(p),auto_value 
​
​
#( (当月ft) - (最近p个月ft的最大值) ) / (最近p个月ft的最大值) )
def Cxp(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    temp = np.nanmax(df,axis = 1 )
    auto_value = (df[ft+'1'] - temp )/ temp
    return ft+'_cxp'+str(p),auto_value 
​
#最近p个月,ft的极差
def Ran(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = np.nanmax(df,axis = 1 )  -  np.nanmin(df,axis = 1 ) 
    return ft+'_ran'+str(p),auto_value 
​
#最近p个月中,特征ft的值,后一个月相比于前一个月增长了的月份数
def Nci(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])     
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        for k in range(len(df_value)-1):
            minus = df_value[k] - df_value[k+1]
            value_lst.append(minus)           
        value_ng = np.where(np.array(value_lst)>0,1,0).sum()
        auto_value.append(np.nanmax(value_ng))     
    return ft+'_nci'+str(p),auto_value 
​
#最近p个月中,特征ft的值,后一个月相比于前一个月减少了的月份数
def Ncd(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])     
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        for k in range(len(df_value)-1):
            minus = df_value[k] - df_value[k+1]
            value_lst.append(minus)           
        value_ng = np.where(np.array(value_lst)<0,1,0).sum()
        auto_value.append(np.nanmax(value_ng))     
    return ft+'_ncd'+str(p),auto_value    
​
#最近p个月中,相邻月份ft 相等的月份数
def Ncn(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])     
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        for k in range(len(df_value)-1):
            minus = df_value[k] - df_value[k+1]
            value_lst.append(minus)           
        value_ng = np.where(np.array(value_lst)==0,1,0).sum()
        auto_value.append(np.nanmax(value_ng))     
    return ft+'_ncn'+str(p),auto_value    
 
#最近P个月中,特征ft的值是否按月份严格递增,是返回1,否返回0
def Bup(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])     
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        index = 0
        for k in range(len(df_value)-1):
            if df_value[k] > df_value[k+1]:
                break
            index =+ 1
        if index == p:            
            value= 1    
        else:
            value = 0
        auto_value.append(value)     
    return ft+'_bup'+str(p),auto_value   
​
#最近P个月中,特征ft的值是否按月份严格递减,是返回1,否返回0
def Pdn(ft,p):
    arr=np.array(data.loc[:,ft+'1':ft+str(p)])     
    auto_value = []
    for i in range(len(arr)):
        df_value = arr[i,:]
        value_lst = []
        index = 0
        for k in range(len(df_value)-1):
            if df_value[k+1] > df_value[k]:
                break
            index =+ 1
        if index == p:            
            value= 1    
        else:
            value = 0
        auto_value.append(value)     
    return ft+'_pdn'+str(p),auto_value            
​
#最近P个月中,ft的切尾均值,这里去掉了数据中的最大值和最小值
def Trm(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = []
    for i in range(len(df)):
        trm_mean = list(df.loc[i,:])
        trm_mean.remove(np.nanmax(trm_mean))
        trm_mean.remove(np.nanmin(trm_mean))
        temp=np.nanmean(trm_mean) 
        auto_value.append(temp)
    return ft+'_trm'+str(p),auto_value 
​
#当月ft / 最近p个月的ft中的最大值
def Cmx(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = (df[ft+'1'] - np.nanmax(df,axis = 1 )) /np.nanmax(df,axis = 1 ) 
    return ft+'_cmx'+str(p),auto_value 
​
#( 当月ft - 最近p个月的ft均值 ) / ft均值
def Cmp(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = (df[ft+'1'] - np.nanmean(df,axis = 1 )) /np.nanmean(df,axis = 1 ) 
    return ft+'_cmp'+str(p),auto_value 
​
#( 当月ft - 最近p个月的ft最小值 ) /ft最小值 
def Cnp(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    auto_value = (df[ft+'1'] - np.nanmin(df,axis = 1 )) /np.nanmin(df,axis = 1 ) 
    return ft+'_cnp'+str(p),auto_value 
​
#最近p个月取最大值的月份距现在的月份数
def Msx(ft,p):
    df=data.loc[:,ft+'1':ft+str(p)]
    df['_max'] = np.nanmax(df,axis = 1)
    for i in range(1,p+1):
        df[ft+str(i)] = list(df[ft+str(i)] == df['_max'])
    del df['_max']
    df_value = np.where(df==True,1,0)
    auto_value=[]
    for i in range(len(df_value)):
        row_value=df_value[i,:]
        indexs=1
        for j in row_value:
            if j == 1:
                break
            indexs+=1
        auto_value.append(indexs)
    return ft+'_msx'+str(p),auto_value
​
#最近p个月的均值/((p,2p)个月的ft均值)
def Rpp(ft,p):
    df1=data.loc[:,ft+'1':ft+str(p)]
    value1=np.nanmean(df1,axis = 1 )
    df2=data.loc[:,ft+str(p):ft+str(2*p)]
    value2=np.nanmean(df2,axis = 1 )   
    auto_value = value1/value2
    return ft+'_rpp'+str(p),auto_value    
​
#最近p个月的均值 - ((p,2p)个月的ft均值)
def Dpp(ft,p):
    df1=data.loc[:,ft+'1':ft+str(p)]
    value1=np.nanmean(df1,axis = 1 )
    df2=data.loc[:,ft+str(p):ft+str(2*p)]
    value2=np.nanmean(df2,axis = 1 )   
    auto_value = value1 - value2
    return ft+'_dpp'+str(p),auto_value   
​
#(最近p个月的ft最大值)/ (最近(p,2p)个月的ft最大值)
def Mpp(ft,p):
    df1=data.loc[:,ft+'1':ft+str(p)]
    value1=np.nanmax(df1,axis = 1 )
    df2=data.loc[:,ft+str(p):ft+str(2*p)]
    value2=np.nanmax(df2,axis = 1 )   
    auto_value = value1/value2
    return ft+'_mpp'+str(p),auto_value  
  
#(最近p个月的ft最小值)/ (最近(p,2p)个月的ft最小值)
def Npp(ft,p):
    df1=data.loc[:,ft+'1':ft+str(p)]
    value1=np.nanmin(df1,axis = 1 )
    df2=data.loc[:,ft+str(p):ft+str(2*p)]
    value2=np.nanmin(df2,axis = 1 )   
    auto_value = value1/value2
    return ft+'_npp'+str(p),auto_value  

封装成衍生函数

#定义批量调用双参数的函数        
def auto_var2(feature,p):
    #global data_new
    try:
        columns_name,values=Num(feature,p)
        data_new[columns_name]=values
    except:
           print("Num PARSE ERROR",feature,p)
    try:
        columns_name,values=Evr(feature,p)
        data_new[columns_name]=values
    except:
           print("Evr PARSE ERROR",feature,p)
    try:
        columns_name,values=Avg(feature,p)
        data_new[columns_name]=values
    except:
           print("Avg PARSE ERROR",feature,p)
    try:
        columns_name,values=Tot(feature,p)
        data_new[columns_name]=values
    except:
        print("Tot PARSE ERROR",feature,p) 
    try:
        columns_name,values=Tot2T(feature,p)
        data_new[columns_name]=values
    except:
        print("Tot2T PARSE ERROR",feature,p)        
    try:
        columns_name,values=Max(feature,p)
        data_new[columns_name]=values
    except:
        print("Tot PARSE ERROR",feature,p)
    try:
        columns_name,values=Max(feature,p)
        data_new[columns_name]=values
    except:
        print("Max PARSE ERROR",feature,p)
    try:
        columns_name,values=Min(feature,p)
        data_new[columns_name]=values
    except:
        print("Min PARSE ERROR",feature,p)
    try:
        columns_name,values=Msg(feature,p)
        data_new[columns_name]=values
    except:
        print("Msg PARSE ERROR",feature,p)
    try:
        columns_name,values=Msz(feature,p)
        data_new[columns_name]=values
    except:
        print("Msz PARSE ERROR",feature,p)
    try:
        columns_name,values=Cav(feature,p)
        data_new[columns_name]=values
    except:
        print("Cav PARSE ERROR",feature,p)
    try:
        columns_name,values=Cmn(feature,p)
        data_new[columns_name]=values
    except:
        print("Cmn PARSE ERROR",feature,p)        
    try:
        columns_name,values=Std(feature,p)
        data_new[columns_name]=values
    except:
        print("Std PARSE ERROR",feature,p)   
    try:
        columns_name,values=Cva(feature,p)
        data_new[columns_name]=values
    except:
        print("Cva PARSE ERROR",feature,p)   
    try:
        columns_name,values=Cmm(feature,p)
        data_new[columns_name]=values
    except:
        print("Cmm PARSE ERROR",feature,p)  
    try:
        columns_name,values=Cnm(feature,p)
        data_new[columns_name]=values
    except:
        print("Cnm PARSE ERROR",feature,p)         
    try:
        columns_name,values=Cxm(feature,p)
        data_new[columns_name]=values
    except:
        print("Cxm PARSE ERROR",feature,p)          
    try:
        columns_name,values=Cxp(feature,p)
        data_new[columns_name]=values
    except:
        print("Cxp PARSE ERROR",feature,p)
    try:
        columns_name,values=Ran(feature,p)
        data_new[columns_name]=values
    except:
        print("Ran PARSE ERROR",feature,p)
    try:
        columns_name,values=Nci(feature,p)
        data_new[columns_name]=values
    except:
        print("Nci PARSE ERROR",feature,p)
    try:
        columns_name,values=Ncd(feature,p)
        data_new[columns_name]=values
    except:
        print("Ncd PARSE ERROR",feature,p)
    try:
        columns_name,values=Ncn(feature,p)
        data_new[columns_name]=values
    except:
        print("Ncn PARSE ERROR",feature,p)
    try:
        columns_name,values=Pdn(feature,p)
        data_new[columns_name]=values
    except:
        print("Pdn PARSE ERROR",feature,p) 
    try:
        columns_name,values=Cmx(feature,p)
        data_new[columns_name]=values
    except:
        print("Cmx PARSE ERROR",feature,p)         
    try:
        columns_name,values=Cmp(feature,p)
        data_new[columns_name]=values
    except:
        print("Cmp PARSE ERROR",feature,p)   
    try:
        columns_name,values=Cnp(feature,p)
        data_new[columns_name]=values
    except:
        print("Cnp PARSE ERROR",feature,p) 
    try:
        columns_name,values=Msx(feature,p)
        data_new[columns_name]=values
    except:
        print("Msx PARSE ERROR",feature,p)
    try:
        columns_name,values=Nci(feature,p)
        data_new[columns_name]=values
    except:
        print("Nci PARSE ERROR",feature,p)
    try:
        columns_name,values=Trm(feature,p)
        data_new[columns_name]=values
    except:
        print("Trm PARSE ERROR",feature,p)
    try:
        columns_name,values=Bup(feature,p)
        data_new[columns_name]=values
    except:
        print("Bup PARSE ERROR",feature,p)
    try:
        columns_name,values=Mai(feature,p)
        data_new[columns_name]=values
    except:
        print("Mai PARSE ERROR",feature,p)
    try:
        columns_name,values=Mad(feature,p)
        data_new[columns_name]=values
    except:
        print("Mad PARSE ERROR",feature,p)
    try:
        columns_name,values=Rpp(feature,p)
        data_new[columns_name]=values
    except:
        print("Rpp PARSE ERROR",feature,p)
    try:
        columns_name,values=Dpp(feature,p)
        data_new[columns_name]=values
    except:
        print("Dpp PARSE ERROR",feature,p)
    try:
        columns_name,values=Mpp(feature,p)
        data_new[columns_name]=values
    except:
        print("Mpp PARSE ERROR",feature,p)
    try:
        columns_name,values=Npp(feature,p)
        data_new[columns_name]=values
    except:
        print("Npp PARSE ERROR",feature,p)
    return data_new.columns.size

对之前数据应用封装的函数

data_new = pd.DataFrame()
for p in range(1, 12):  
    for inv in ['ft', 'gt']:  
        auto_var2(inv, p)  

上面这种无差别聚合方法进行聚合得到的结果,通常具有较高的共线性,但信息量并无明显增加,影响模型的鲁棒性和稳定性。

评分卡模型对模型的稳定性要求远高于其性能

  • 在时间窗口为1年的场景下,p值会通过先验知识,人为选择3、6、12等,而不是遍历全部取值1~12

  • 在后续特征筛选时,会根据变量的显著性、共线性等指标进行进一步筛选

方式2: 当前和历史对比

最近一次(current) 和历史 (history)做对比

  • current/history

  • current-history

方式3: 统计(类别)

统计数量

用户时间序列缺失值处理
用户时间序列缺失值处理
  1. 优先考虑补零:大多数特征都是计数,缺失用0补充

  2. 用户没有历史购物记录: max_gmv min_gmv 都可以用0补充

  3. 用0填充缺失值带来的问题

    cur/history_avg: 0/0 cur/history_avg:1/0

  4. 根据风险趋势填补缺失值 (违约概率大小 无历史购物记录违约概率>有一单历史购物记录>有两单)

    用户没有历史购物记录 cur/history_avg : 0/0? 可以填充-2

    用户有一单历史购物记录 cur/history_avg : 1/0? 可以填充-1

    用户有两单历史购物记录 cur/history_avg : 1/1 可以计算出>0的值

用户最后一次逾期距今天数,如果是白户如何填补缺失值?

如果缺失值比较多的时候,考虑单独做成特征

  • 举例:用户授权GPS序列特征 gps_count_last_3month

  • 缺失意味着用户未授权GPS权限

缺失有明显业务含义,可以填补业务默认值

  • 授信额度(用初始额度)

缺失值处理小结
缺失值处理
一般计数类特征优先考虑用0填充
有风险趋势按风险趋势填补
缺失数值过多考虑新增是否缺失的特征列
有业务含义填补业务默认值

时间序列数据未来信息

以借贷2发生的时间为观测点,下表中的未来信息会把大量退货行为的用户认为是坏客户,但上线后效果会变差

特征构建补救方法
  1. 对未来信息窗口外的订单计算有效单的特征 net order,NMV

    NMV:Net Merchandise Value(商品净值)

  2. 对未来信息窗口内订单只计算一般特征 order,GMV

    GMV:Gross Merchandise Volume(商品交易总值)

历史信贷特征也非常容易出现未来信息

举例:

信用卡 每月1日为账单日,每月10日为还款日,次月10日左右为M1

在上图所示的截面时间(如3月5日)是看不到2月账单的逾期DPD30的情况的

但如果数据库没有快照表会导致我们可以拿到2月账单的DPD30情况

解决方案跟上面例子一样,分区间讨论,可以把账单分成3类

  1. 当前未出账账单

  2. 最后一个已出账账单

  3. 其他已出账账单 (只有这个特征可以构建逾期类特征)

未来信息处理小结
  1. 及时增加快照表

  2. 没有快照表的情况下,将数据区分为是否有未来信息的区间,分别进行特征构造

特征变换

分箱(离散化)

概念
  1. 特征构造的过程中,对特征做分箱处理时必不可少的过程

  2. 分箱就是将连续变量离散化,合并成较少的状态

分箱作用
  1. 离散特征的增加和减少都很容易,易于模型的快速迭代

  2. 分箱(离散化)后的特征对异常数据有很强的鲁棒性

  3. 单变量分箱(离散化)为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力

  4. 分箱(离散化)后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能

  5. 分箱(离散化)后,模型会更稳定,如对年龄离散化,20-30为一个区间,不会因为年龄+1就变成一个新的特征

  6. 特征离散化以后,可以将缺失作为独立的一类带入模型

分箱方法

常用分箱方法: 卡方分箱, 决策树分箱, 等频分箱, 聚类分箱

等频分箱

  1. 按数据的分布,均匀切分,每个箱体里的样本数基本一样

  2. 在样本少的时候泛化性较差

  3. 在样本不均衡时可能无法分箱

等距分箱

  1. 按数据的特征值的间距均匀切分,每个箱体的数值距离一样

  2. 一定可以分箱

  3. 无法保证箱体样本数均匀

★卡方分箱

使用卡方检验确定最优分箱阈值

  1. 将数据按等频或等距分箱后,计算卡方值,将卡方值较小的两个相邻箱体合并

    使得不同箱体的好坏样本比例区别放大,容易获得高IV

  2. 卡方分箱是利用独立性检验来挑选箱划分节点的阈值。卡方分箱的过程可以拆分为初始化和合并两步

    1. 初始化:根据连续变量值大小进行排序,构建最初的离散化

    2. 合并:遍历相邻两项合并的卡方值,将卡方值最小的两组合并,不断重复直到满足分箱数目要求

[22-35](35-45](45-55](55-65]总计
good32218
bad12238
p50%
p(good+bad)2222-
chi2(1-2)^2/2=1/2(2-2)^2/2=0(2-2)^2/2=0(3-2)^2/2=1/2-

合并坏人比例接近平均水平的箱体,留下比例差异大的箱体

案例-卡方分箱

数据集使用germancredit
  1. Toad 是专为工业界模型开发设计的Python工具包,特别针对评分卡的开发

  2. Toad 的功能覆盖了建模全流程,从EDA、特征工程、特征筛选到模型验证和评分卡转化

  3. Toad 的主要功能极大简化了建模中最重要最费时的流程,即特征筛选和分箱。

第一次使用toad,需要安装:

pip install toad==0.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple/

案例代码实现:

import pandas as pd
import numpy as np
import toad
​
data = pd.read_csv('../data/germancredit.csv')
data.replace({'good':0,'bad':1},inplace=True)
​
print(data.shape) # 1000 data and 20 features 
data.head()

显示结果

(1000, 21)
status.of.existing.checking.accountduration.in.monthcredit.historypurposecredit.amountsavings.account.and.bondspresent.employment.sinceinstallment.rate.in.percentage.of.disposable.incomepersonal.status.and.sexother.debtors.or.guarantors...propertyage.in.yearsother.installment.planshousingnumber.of.existing.credits.at.this.bankjobnumber.of.people.being.liable.to.provide.maintenance.fortelephoneforeign.workercreditability
0... < 0 DM6critical account/ other credits existing (not ...radio/television1169unknown/ no savings account... >= 7 years4male : singlenone...real estate67noneown2skilled employee / official1yes, registered under the customers nameyes0
10 <= ... < 200 DM48existing credits paid back duly till nowradio/television5951... < 100 DM1 <= ... < 4 years2female : divorced/separated/marriednone...real estate22noneown1skilled employee / official1noneyes1
2no checking account12critical account/ other credits existing (not ...education2096... < 100 DM4 <= ... < 7 years2male : singlenone...real estate49noneown1unskilled - resident2noneyes0
3... < 0 DM42existing credits paid back duly till nowfurniture/equipment7882... < 100 DM4 <= ... < 7 years2male : singleguarantor...building society savings agreement/ life insur...45nonefor free1skilled employee / official2noneyes0
4... < 0 DM24delay in paying off in the pastcar (new)4870... < 100 DM1 <= ... < 4 years3male : singlenone...unknown / no property53nonefor free2skilled employee / official2noneyes1
数据字段说明
  1. Status of existing checking account(现有支票帐户的存款状态)

  2. Duration in month(持续月数)

  3. Credit history(信用历史记录)

  4. Purpose(申请目的)

  5. Credit amount(信用保证金额)

  6. Savings account/bonds(储蓄账户/债券金额)

  7. Present employment since(当前就业年限)

  8. Installment rate in percentage of disposable income(可支配收入占比)

  9. Personal status and gender(个人婚姻状态及性别)

  10. Other debtors / guarantors(其他债务人或担保人)

  11. Present residence since(当前居民年限)

  12. Property(财产)

  13. Age in years(年龄)

  14. Other installment plans (其他分期付款计划)

  15. Housing(房屋状况)

  16. Number of existing credits at this bank(在该银行已有的信用卡数)

  17. Job(工作性质)

  18. Number of people being liable to provide maintenance for(可提供维护人数)

  19. Telephone(是否留存电话)

  20. foreign worker(是否外国工人)

  21. creditability数据标签


http://www.kler.cn/news/366184.html

相关文章:

  • 地平线x5下运行yolo11s-seg模型
  • 使用python代码绘制好看的统计图
  • 虚拟机网络设置为桥接模式
  • 力扣143:重排链表
  • 华为“纯血鸿蒙”重磅发布!首次融入原生AI能力
  • vue写个表格,让它滚动起来,没有用datav,有的时候结合会出错,一种简单的方法,直接用animation
  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
  • Windows10搭建Spark3.4.3安装教程
  • OAK相机的RGB-D彩色相机去畸变做对齐
  • 【动态规划】【简单多状态dp问题】买卖股票相关问题(冷冻期、手续费、限制次数)
  • MATLAB基础应用精讲-【数模应用】本量利分析(Cost-Volume-Profit Analysis)
  • 【论文阅读】ESRGAN+
  • 项目管理软件中这6个小技巧帮助项目经理同时管理多个项目
  • 水轮发电机油压自动化控制系统解决方案介绍
  • LDR6020:为VR串流线方案注入高效能与稳定性
  • 多台NFS客户端访问一台nfs服务器
  • vitepress一键push和发布到github部署网站脚本
  • spring整合使用xml方式整合Druid数据源连接池
  • 邮件系统SSL加密传输,保护你的电子邮件免受网络威胁
  • 基于SSM考研助手系统的设计
  • 小白对时序数据库的理解
  • 视频美颜平台是如何搭建的?基于直播美颜SDK源码的开发技术详解
  • 推荐一款三维数值建模软件:3DEC
  • C++ —— 《模板进阶详解》,typename和class的用法,非类型模板参数,模板的特化,模板的分离编译
  • 解决Github下载速度慢的问题
  • 青少年编程能力等级测评CPA C++五级试卷(2)