【金融风控项目-08】:特征构造

时间:2024-11-21 10:44:23

文章目录

  • 1.数据准备
    • 1.1 风控建模特征数据
    • 1.2 人行征信数据
    • 1.3 据之间的内在逻辑
  • 2 样本设计和特征框架
    • 2.1 定义观察期样本
    • 2.2 数据EDA(Explore Data Analysis)
    • 2.3 梳理特征框架
  • 3 特征构造
    • 3.1 静态信息和时间截面特征
    • 3.2 未来信息问题
      • 3.2.1 未来信息案例
      • 3.2.2 时间序列特征的未来信息
      • 3.2.3 历史信贷特征出现未来信息
    • 3.3 特征构造
      • 3.3.1 时序数据特征衍生
      • 3.3.2 用户关联特征

1.数据准备

1.1 风控建模特征数据

  • 用户信息
    在这里插入图片描述
  • 数据来源
    在这里插入图片描述

1.2 人行征信数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 据之间的内在逻辑

  • 关系种类
    • 一对一:一个用户注册对应有一个注册手机号
    • 一对多:一个用户有多笔借款
    • 多对多:一个用户可以登录多个设备,一个设备可以有多个用户登录

**举例:**下图中,蓝色框为二月当期账单,红色框为订单
在这里插入图片描述

  • 梳理类ER图

在这里插入图片描述

  • 任务:分析厚数据(数据量大)常登录首单用户的逾期情况
    在这里插入图片描述
  • 可以将表结构展示到特征文档中,说明取数逻辑
    在这里插入图片描述

2 样本设计和特征框架

2.1 定义观察期样本

  • 确定观察期(定义x时间切面)和表现期(定Y的标签)
  • 确认样本数据是否合理

2.2 数据EDA(Explore Data Analysis)

  • 查看数据总体分布

data.shape
data.isnull()
data.info()
data.describe()

  • 查看好坏样本分布差异

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

  • 查看单个数据

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

2.3 梳理特征框架

  • RFM生成新特征
    举例:行为评分卡中的用户账单还款特征

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

    在这里插入图片描述

小结:在构造特征之前,要完成

  • 类ER图
  • 样本设计表
  • 特征框架图

3 特征构造

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

  • 用户静态信息:用户的基本信息(半年以上不会发生变化)

    • 姓名
    • 性别
    • 年龄
  • 用户时间截面:取时间轴上的一个点,作为时间截面

    • 截面时间点的购物GVM、银行存款额、逾期最大天数
  • 用户时间序列特征:从观察点往前回溯一段时间的数据

    • 过去 一个月的GPS数据
    • 过去六个月的银行流水
    • 过去一年的逾期记录
  • 用户时间截面特征相关概念

    • 未来信息:当前时间截面之后的数据
    • 时间截面数据在取数据的时候,要避免使用未来信息
    • 产生未来信息的直接原因:缺少快照表
    • 金融相关数据原则上都需要快照表记录所有痕迹(额度变化情况,多次申请的通过和拒绝情况)
  • 缺少快照表的原因
    * 快照表消耗资源比较大,为了性能不做
    * 原有数据表设计人员疏忽,没做
    * 借用其他业务数据(如电商)做信贷
  • 快照表:每天定时存储一个状态(类似于每天23:00都拍一张照片),每天会把当天的状态进行备份,只存储当天的最终状态。
  • 日志表:每一次操作都会记录,不会进行update,只有insert操作,操作一次,插入一条记录。

3.2 未来信息问题

3.2.1 未来信息案例

  • 首次借贷 --》二次借贷–》爬虫授权–》三次借贷
    举例:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    解决方式:加入快照表存储
    在这里插入图片描述

3.2.2 时间序列特征的未来信息

时间序列特征:从观察点向前回溯一段时间的数据

在这里插入图片描述
在这里插入图片描述

  • 以借贷2发生的时间为观测点,下表中的未来信息会将大量退货行为的用户认为是坏客户,但是上下之后效果会变差。
    在这里插入图片描述
  • 特征构建时的补救方法
    • 对未来信息窗口外的订单计算有效的特征(NMV)
    • 对未来信息窗口内的订单计算一般特征(GMV)

3.2.3 历史信贷特征出现未来信息

  • 举例:信用卡每月1日为账单日,每月10日为还款日,次月10日左右为M1(逾期一个月)
    在这里插入图片描述
  • 在上图所示的截面时间(如3月5日)是看不到2月账单的逾期DPD30的情况的
  • 但如果数据库没有快照表会导致我们可以拿到2月账单的DPD30情况
  • 解决方案跟上面例子一样,分区间讨论,可以把账单分成3类
    • 当前未出账账单

    • 最后一个已出账账单

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

小结:处理未来信息问题

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

3.3 特征构造

3.3.1 时序数据特征衍生

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

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

    • 申请前30天内的额度使用率ft1
    • 申请前30天至60天内的额度使用率ft2
    • 申请前60天至90天内的额度使用率ft3
    • 申请前330天至360天内的额度使用率ft12
    • 得到一个用户的12个特征
import pandas as pd
import numpy as np
data = pd.read_excel('../data/textdata.xlsx')
data.head()

在这里插入图片描述

  • 可以根据这个时间序列进行基于经验的人工特征衍生,例如计算最近P个月特征大于0的月份数
#最近p个月,ft>0的月份数
def Num(ft,p):  #ft 特征名字 p特征大于0的月份数
    df=data.loc[:,ft+'1':ft+str(p)] # 选择ft1 - ftp的数据
    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 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