金融风控-- >客户流失预警模型-- >特征工程

上一篇博文中,我们对金融数据(连续性变量,类别性变量)进行了可视化操作,以及单因子分析,多因子分析等初始预处理。得出了变量和目标变量的相关性。

本篇博文中将对金融数据进行全面详细的数据预处理以及特征工程。这里包括以下几点:

  • 极端值的处理
  • 缺失值的处理
  • 特殊变量的处理
  • 构造流失行为的特征

极端值的处理

极端值:又称离群值,往往会扭曲预测结果并影响模型精度。回归模型(线性回归,广义线性回归)中离群值的影响尤其大,使用该模型时我们需要对其进行检测和处理。

  • 处理离群值或者极端值并不是数据建模的必要流程,然而,了解它们对预测模型的影响也是大有裨益的。
  • 数据分析师们需要自己判断处理离群值的必要性,并结合实际问题选取处理方法。
  • 检测离群值的重要性:由于离群值的存在,模型的估计和预测可能会有很大的偏差或者变化
  • 可以选择对极端值不敏感的模型,例如KNN,决策树

那么如何检测某个特征数据是否存在极端值呢?
1)对样本数据进行可视化

这里写图片描述

由上图可知,对于“ASSET_CUR_ALL_BAL"这个特征的数据,很明显存在离群点。
经过对数据的统计可知:
①大于10000000的样本只占0.006%
②除去极端值外,其余样本的均值是115443,标准差是179359
极端值是均值的386倍,偏离247个标准差

  1. 3-sigma方法检
    一般来说,如果某个特征数据,最大值为maxValue,均值为mean,标准差为std。如果满足maxValue>mean+3*std,那么我们就认为这个特征数据存在离群点

那么如果某个特征数据存在离群点,应该如何进行处理呢?

  • 人为降低极端值到某个正常的值,例如用95%的分位点代替 例:因为透支的原因,导致信用卡使用额度超过100%,可以用100%来代替
def decrease_extreme_value(validDf,col):
    descStats = validDf[col].describe()
    mu = descStats['mean']
    std = descStats['std']
    maxVal = descStats['max']
    # detect the extreme value using 3-sigma method
    if maxVal > mu + 3 * std:
        for i in list(validDf.index):
            if validDf.loc[i][col] > mu + 3 * std:
                # decrease the extreme value to normal level
                validDf.loc[i][col] = mu + 3 * std
        # re-calculate the mean based on cleaned data
        mu = validDf[col].describe()['mean']
  • 删除极端值(慎重) 例:极个别持卡人的年龄超过85岁
  • 单独建模型 例:信用卡额度特别高

缺失值的处理

缺失类型

  • 完全随机缺失:缺失值跟其他变量无关,例如婚姻状况的缺失
  • 随机缺失:缺失值依赖于其他变量,例如“配偶姓名”的缺失取决于“婚姻状况”
  • 完全非随机缺失:缺失值依赖于自己,例如高收入人群不愿易提供家庭收入

处理方法

  • 删除有缺失值的属性或者样本(土豪行为)
  • 插补填充(常用于完全随机缺失且缺失度不高的情形中)
  • 将缺失当成一种属性值(常用于完全非随机缺失)

连续变量缺失值的处理

1)对于完全随机缺失,当缺失率不高时,可以:

  • 用常数补缺,例如均值 特别地,如果存在极端值,要考虑是否剔除极端值后再计算均值
  • 从非缺失值中随机抽样赋予缺失样本
def MakeupMissing(df,col,method):
    '''
    :param df: dataset containing columns with missing value
    :param col: columns with missing value
    :param type: the type of the column, should be Continuous or Categorical
    :return: the made up columns
    '''
    #Take the sample with non-missing value in col
    validDf = df.loc[df[col] == df[col]][[col]]
    if validDf.shape[0] == df.shape[0]:
        return 'There is no missing value in {}'.format(col)

    #copy the original value from col to protect the original dataframe
    missingList = [i for i in df[col]]
    #get the descriptive statistics of col
    descStats = validDf[col].describe()
    mu = descStats['mean']
    std = descStats['std']
    maxVal = descStats['max']
    #detect the extreme value using 3-sigma method
    if maxVal > mu+3*std:
        for i in list(validDf.index):
            if validDf.loc[i][col] > mu+3*std:
                #decrease the extreme value to normal level
                validDf.loc[i][col] = mu + 3 * std
        #re-calculate the mean based on cleaned data
        mu = validDf[col].describe()['mean']
    for i in range(df.shape[0]):
        if df.loc[i][col] != df.loc[i][col]: ##自己不等于自己,表示该数值为NAN缺失值
            #use the mean or sampled data to replace the missing value
            if method == 'Mean':
                missingList[i] = mu ## mu为剔除极端值以后的均值
            elif method == 'Random':
                missingList[i] = random.sample(validDf[col],1)[0] ##从非缺失值内随机抽一个样本进行填充

2)对于依赖于其他某变量的随机缺失,可以在同一层内,用完全随机缺失的方法进行补缺

  • 例如:变量“收入”取决于“工作状态”。当“工作状态”=“有工作”时,缺失的“收入”可以用所有“有工作”的持卡人的已知收入的均值代替

3)对于完全非随机缺失,可以当成一种属性,将该变量转化成类别变量

  • 直接进行二值化,将该特征数据分为缺失值和非缺失值两类
  • 考虑给定一个step(比如age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。

类别变量缺失值的处理
当缺失率很低时

  • 最常出现的类别补缺

  • 可以从其他已知的样本中随机抽样进行补缺

     对于类别型变量的随机抽样补缺这里需要详细讲下:
     现在我们假设有一个类别型变量X,它有三个类型的取值分别为[x1,x2,x3],我们分布计算出x1,x2,x3在X中出现频率,分别为p1,p2,p3,
     并将其频率代替为其概率。很明显p1+p2+p3=1。再计算出其累积概率,以列表形式[0,p1,p1+p2,p1+p2+p3]。每次遇到一个缺失值时,
     随机抽取一个值a~unifor(0,1),a是(0,1)之间的数, 如果a<p1:把x1来代替这个缺失值;如果p1<a<=p1+p2:把x2代替这个缺失值;
     如果p1+p2<a<=p1+p2+p3:把x3代替这个缺失值。
     证明:p(p1<a<p1+p2)=p(a<p1+p2)-p(a<p1) ##因为a是从(0,1)之间均匀抽出来的
      =(p1+p2)-p1 =p2,P2正好对应x2类别的概率,故此时我们用x2代替这个缺失值。。
    
def MakeupMissing(validDf,col){
    freqDict = {}
    recdNum = validDf.shape[0]
    for v in set(validDf[col]):
        vDf = validDf.loc[validDf[col] == v]
    freqDict[v] = vDf.shape[0] * 1.0 / recdNum
    # find the category with highest probability
    modeVal = max(freqDict.items(), key=lambda x: x[1])[0]
    freqTuple = freqDict.items()
    # cumulative sum of each category
    freqList = [0] + [i[1] for i in freqTuple]
    freqCumsum = cumsum(freqList)
    for i in range(df.shape[0]):
        if
    df.loc[i][col] != df.loc[i][col]:
    if method == 'Mode':
        missingList[i] = modeVal
    if method == 'Random':
    # determine the sampled category using unifor distributed random variable
        a = random.random(1)
    position = [k + 1 for k in range(len(freqCumsum) - 1) if freqCumsum[k] < a <= freqCumsum[k + 1]][0]
    missingList[i] = freqTuple[position - 1][0]
}

当缺失率很高时

  • 考虑剔除该属性

当缺失率介于“很低”和“很高”时

  • 可以当成一种类别

特殊变量的处理

类别型变量编码

  • one-hot编码

  • 浓度编码

      这里需要详细讲下浓度编码:
      某类别型特征下,每一类数据对应的流失率或者是违约率(也可以是非流失率或者非违约率)作为这类数据的编码。
      例如性别这个特征:男性人数为x1,男性中流失人数x11,女性人数x2,女性中流失人数x22。
      那么我们以x11/x1作为男性编码;x22/x2作为女性编码。
    
def Encoder(df, col, target):
    '''
    :param df: the dataset containing categorical variable
    :param col: the name of categorical variabel
    :param target: class, with value 1 or 0
    :return: the numerical encoding for categorical variable
	df[target]:非0即1.
	'''
    encoder = {}
    for v in set(df[col]):
        if v == v:
            subDf = df[df[col] == v]
        else:
            xList = list(df[col])
            nanInd = [i for i in range(len(xList)) if xList[i] != xList[i]]
            subDf = df.loc[nanInd]
        encoder[v] = sum(subDf[target])*1.0/subDf.shape[0]
    newCol = [encoder[i] for i in df[col]]
    return newCol
  • WOE编码

对日期/时间型变量

  • 时间是否为一个节日,是否在一个时间段(类别型);或者计算距离某个日子变成间隔型;或者某个时间段内发生了多少次变成组合型等等;这个需要结合具体应用场景。使其变成离散型。

  • 可以基于某个基准日期,转化为天数
    以观察点为基准,将所有开户日期转为距离观察点的天数(month-on-book)

def Date2Days(df, dateCol, base):
    '''
    :param df: the dataset containing date variable in the format of 2017/1/1
    :param date: the column of date
    :param base: the base date used in calculating day gap
    :return: the days gap
    '''
    base2 = time.strptime(base,'%Y/%m/%d')
    base3 = datetime.datetime(base2[0],base2[1],base2[2])
    date1 = [time.strptime(i,'%Y/%m/%d') for i in df[dateCol]]
    date2 = [datetime.datetime(i[0],i[1],i[2]) for i in date1]
    daysGap = [(date2[i] - base3).days for i in range(len(date2))]
    return daysGap

构建流失行为的特征

内部自有数据

  • 丰富的内部交易明细数据,包括本币活期储蓄波动率,本币活期储蓄月日均余额,。。。,电话银行总交易笔数

  • 可以构建的特征:
    ①不同交易的数额的比例
    ②单笔交易的平均数额
    ③某种交易的笔数占全部交易笔数的比例

例如:
这里写图片描述

最大波动=max{本币一年以下波动,本币一年以上波动率,储蓄类资产波动率,本币储蓄波动率}

这里写图片描述

这里写图片描述

### Calculate the ratio between two variables
def ColumnDivide(df, colNumerator, colDenominator):
    '''
    :param df: the dataframe containing variable x & y
    :param colNumerator: the numerator variable x
    :param colDenominator: the denominator variable y
    :return: x/y
    '''
    N = df.shape[0]
    rate = [0]*N
    xNum = list(df[colNumerator])
    xDenom = list(df[colDenominator])
    for i in range(N):
        #if the denominator is non-zero, work out the ratio
        if xDenom[i]>0:
            rate[i] = xNum[i]*1.0/xDenom[i]
        # if the denominator is zero, assign 0 to the ratio
        else:
            rate[i] = 0
    return rate

若信息存在冗余,需要按情况进行剔除
情况一:

“本币活期月日均余额占比” = 1 – “本币定期月日均余额占比”

变量“本币活期月日均余额占比 ”与“本币定期月日均余额占比”存在冗余性,知道其一必知道其二,需要剔除一个。

#本币活期月日均余额占比 = 1 - 本币定期月日均余额占比
del modelData['LOCAL_CUR_MON_AVG_BAL_PROP']

情况二

“资产当前总余额 ”= “本币储蓄当前总余额 ”+ “外币储蓄当前总余额”

如果是**(广义)线性回归模型,三者不能同时放进模型**。对于树模型,可以将其中任意两个放进模型,剩余的做转换,比如做一个离散变换。

外部数据包含了客户在电信运营商的详情
包括:

  • 通话时间与次数
  • 话费详情
  • 特定的呼叫行为
  • 其他信息

可以衍生的特征
月平均通话时间的变化=过去三个月月平均通话时间 − 过去六个月月平均通话时间
月平均通话次数的变化=过去三个月月平均通话次数 − 过去六个月月平均通话次数
月平均缴纳话费的变化=过去三个月月平均缴纳话费 − 过去六个月月平均缴纳话费

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值