您的位置:首页 > 其它

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

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

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

极端值的处理

缺失值的处理

特殊变量的处理

构造流失行为的特征

极端值的处理

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

处理离群值或者极端值并不是数据建模的必要流程,然而,了解它们对预测模型的影响也是大有裨益的。

数据分析师们需要自己判断处理离群值的必要性,并结合实际问题选取处理方法。

检测离群值的重要性:由于离群值的存在,模型的估计和预测可能会有很大的偏差或者变化

可以选择对极端值不敏感的模型,例如KNN,决策树

那么如何检测某个特征数据是否存在极端值呢?

1)对样本数据进行可视化



由上图可知,对于“ASSET_CUR_ALL_BAL”这个特征的数据,很明显存在离群点。

经过对数据的统计可知:

①大于10000000的样本只占0.006%

②除去极端值外,其余样本的均值是115443,标准差是179359

极端值是均值的386倍,偏离247个标准差

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


情况二

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

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

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

包括:

通话时间与次数

话费详情

特定的呼叫行为

其他信息

可以衍生的特征

月平均通话时间的变化=过去三个月月平均通话时间 − 过去六个月月平均通话时间

月平均通话次数的变化=过去三个月月平均通话次数 − 过去六个月月平均通话次数

月平均缴纳话费的变化=过去三个月月平均缴纳话费 − 过去六个月月平均缴纳话费
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐