金融风控违约预测-task3

0.回顾

上一次打卡探索性数据分析,是我们与数据的第一次会面,需要从整体到局部来认识,为数据清洗和特征工程打下基础。
我一般会使用pandas,sklearn和matplotlib等库进行数据EDA,首先看一下各特征是什么意思,大概预测一下相关性,然后就需要详细看各特征的分布,缺失,特点。

import pandas as pd
from sklearn.preprocessing import StandardScaler

data=open('E:\data\\test\loan.csv',encoding='utf-8')
loan_data=pd.read_csv(data)
loan_data.head()
#查看数据缺失比例,ascending表示是否升序
check_null=loan.isnull().sum().sort_values(ascending=False)/float(len(loan))
#series 也可以用[搜查条件]
print(type(check_null),check_null[check_null>0.2])
thresh_count=len(loan)*0.9
#删除缺失比例大于阀值的列,dropna方法,thresh参数
loan=loan.dropna(thresh=thresh_count,axis=1)
print(loan.shape)

这里推荐使用Missingno库来快速评价数值缺失情况

import missingno as msno
import numpy as np
import matplotlib.pyplot as plt

msno.matrix(loans[objectColumns])
plt.show()

结果类似于这样:
在这里插入图片描述

1.概述

特征工程个人觉得包括两部分,一个是特征构造,一个是特征筛选。特征构造包括异常值处理、缺失值处理、数据分桶、特征处理、特征构造。
特征工程是机器学习的重要内容。机器学习有两种提升原始数据表达的方法,特征工程和特征学习。
1.特征学习,自动学习有用的特征
2.特征工程,对数据进行人为处理,有时候也叫洗数据。
特征工程,其实是个经验活,对预测指标要有业务上的熟悉,然后不断将组合的特征,拿到模型中训练,筛选特征,最后取得效果好的特征。

1.1常见的训练数据预处理:

  • 异常处理:

通过箱线图(或 3-Sigma)分析删除异常值;
BOX-COX 转换(处理有偏分布);
长尾截断;
特征归一化/标准化:
标准化(转换为标准正态分布);
归一化(抓换到 [0,1] 区间);
针对幂律分布,可以采用公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rkcdwola-1600702814933)(attachment:image.png)]

  • 缺失值处理:

不处理(针对类似 XGBoost 等树模型);
删除(缺失数据太多);
插值补全,包括均值/中位数/众数/建模预测/多重插补/压缩感知补全/矩阵补全等;
分箱,缺失值一个箱;

1.2 常见的特征工程:

  • 数据分桶:

等频分桶;
等距分桶;
Best-KS 分桶(类似利用基尼指数进行二分类);
卡方分桶;

  • 特征构造:

构造统计量特征,报告计数、求和、比例、标准差等;

时间特征,包括相对时间和绝对时间,节假日,双休日等;

地理信息,包括分箱,分布编码等方法;

非线性变换,包括 log/ 平方/ 根号等;

特征组合,特征交叉;

  • 特征筛选

过滤式(filter):先对数据进行特征选择,然后在训练学习器,常见的方法有 Relief/方差选择发/相关系数法/卡方检验法/互信息法;
包裹式(wrapper):直接把最终将要使用的学习器的性能作为特征子集的评价准则,常见方法有 LVM(Las Vegas Wrapper) ;
嵌入式(embedding):结合过滤式和包裹式,学习器训练过程中自动进行了特征选择,常见的有 lasso 回归;
降维
PCA/ LDA/ ICA;
特征选择也是一种降维。

接下来分享一些做的好的特征工程

2. 特征工程分享

首先是异常值处理

#异常值处理
def outliers_proc(data,col_name,scale=3):
    '''
    用于清洗异常值,默认用box_plot(scale=3)进行清洗
    :param data:接受 pandas 数据格式
    :param col_name:列名
    :param scale:尺度
    :return:'''
    def box_plot_outlier(data_ser,box_scale):
        '''
        利用箱线图去除异常值
        :param data_ser: 接受pandas.series 数据格式
        :param box_scdale:箱线图尺度
        :return:'''
        iqr=box_scale*(data_ser.quantile(0.75)-data_ser.quantile(0.25))
        #极差
        val_low=data_ser.quantile(0.25)-iqr
        val_up= data_up=data_ser.quantile(0.75)+iqr

        rule_low=(data_ser<val_low)
        rule_up=(data_ser>val_up)
        return (rule_low,rule_up),(val_low,val_up)
    
    data_n=data.copy()
    data_series=data_n[col_name]
    rule,value=box_plot_outlier(data_series,box_scale=scale)
    index=np.arange(data_series.shape[0])[rule[0]|rule[1]]
    print('删除的数字是:{}'.format(len(index)))
    
    data_n=data_n.drop(index)
    data_n.reset_index(drop=True,inplace=True)
    print('现在这列有的数字:{}'.format(data_n.shape[0]))
    
    index_low=np.arange(data_series.shape[0])[rule[0]]
    outliers=data_series.iloc[index_low]
    print('大于上限的数据的描述是:')
    print(pd.Series(outliers).describe())
    
    fig, ax = plt.subplots(1, 2, figsize=(10, 7))
    sns.boxplot(y=data[col_name], data=data, palette="Set1", ax=ax[0])
    sns.boxplot(y=data_n[col_name], data=data_n, palette="Set1", ax=ax[1])
    return data_n

然后是数据分桶
也就是把连续的数据进行离散化切分,一类一类的。
为什么要做数据分桶呢,原因有很多

  • 1 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;
  • 2 离散后的特征对异常值更具鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;
  • 3 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量编程 M*N 个变量,进一步引入非线形,提升了表达能力;
  • 4 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化

分享一下其他人的特征工程,首先是天池某大佬的,亮点在于特征的二阶交叉。

from scipy.stats import entropy


feat_cols = []

### count编码
for f in tqdm([
    'regDate', 'creatDate', 'regDate_year',
    'model', 'brand', 'regionCode'
]):
    df[f + '_count'] = df[f].map(df[f].value_counts())
    feat_cols.append(f + '_count')

### 用数值特征对类别特征做统计刻画,随便挑了几个跟price相关性最高的匿名特征
for f1 in tqdm(['model', 'brand', 'regionCode']):
    g = df.groupby(f1, as_index=False)
    for f2 in tqdm(['v_0', 'v_3', 'v_8', 'v_12']):
        feat = g[f2].agg({
    
    
            '{}_{}_max'.format(f1, f2): 'max', '{}_{}_min'.format(f1, f2): 'min',
            '{}_{}_median'.format(f1, f2): 'median', '{}_{}_mean'.format(f1, f2): 'mean',
            '{}_{}_std'.format(f1, f2): 'std', '{}_{}_mad'.format(f1, f2): 'mad'
        })
        df = df.merge(feat, on=f1, how='left')
        feat_list = list(feat)
        feat_list.remove(f1)
        feat_cols.extend(feat_list)

### 类别特征的二阶交叉
for f_pair in tqdm([
    ['model', 'brand'], ['model', 'regionCode'], ['brand', 'regionCode']
]):
    ### 共现次数
    df['_'.join(f_pair) + '_count'] = df.groupby(f_pair)['SaleID'].transform('count')
    ### n unique、熵
    df = df.merge(df.groupby(f_pair[0], as_index=False)[f_pair[1]].agg({
    
    
        '{}_{}_nunique'.format(f_pair[0], f_pair[1]): 'nunique',
        '{}_{}_ent'.format(f_pair[0], f_pair[1]): lambda x: entropy(x.value_counts() / x.shape[0])
    }), on=f_pair[0], how='left')
    df = df.merge(df.groupby(f_pair[1], as_index=False)[f_pair[0]].agg({
    
    
        '{}_{}_nunique'.format(f_pair[1], f_pair[0]): 'nunique',
        '{}_{}_ent'.format(f_pair[1], f_pair[0]): lambda x: entropy(x.value_counts() / x.shape[0])
    }), on=f_pair[1], how='left')
    ### 比例偏好
    df['{}_in_{}_prop'.format(f_pair[0], f_pair[1])] = df['_'.join(f_pair) + '_count'] / df[f_pair[1] + '_count']
    df['{}_in_{}_prop'.format(f_pair[1], f_pair[0])] = df['_'.join(f_pair) + '_count'] / df[f_pair[0] + '_count']
    
    feat_cols.extend([
        '_'.join(f_pair) + '_count',
        '{}_{}_nunique'.format(f_pair[0], f_pair[1]), '{}_{}_ent'.format(f_pair[0], f_pair[1]),
        '{}_{}_nunique'.format(f_pair[1], f_pair[0]), '{}_{}_ent'.format(f_pair[1], f_pair[0]),
        '{}_in_{}_prop'.format(f_pair[0], f_pair[1]), '{}_in_{}_prop'.format(f_pair[1], f_pair[0])
    ])

然后是统计编码,最大会利用数值特征。

from sklearn.model_selection import KFold


train_df = df[~df['price'].isnull()].reset_index(drop=True)
test_df = df[df['price'].isnull()].reset_index(drop=True)


### target encoding目标编码,回归场景相对来说做目标编码的选择更多,不仅可以做均值编码,还可以做标准差编码、中位数编码等
enc_cols = []
stats_default_dict = {
    
    
    'max': train_df['price'].max(),
    'min': train_df['price'].min(),
    'median': train_df['price'].median(),
    'mean': train_df['price'].mean(),
    'sum': train_df['price'].sum(),
    'std': train_df['price'].std(),
    'skew': train_df['price'].skew(),
    'kurt': train_df['price'].kurt(),
    'mad': train_df['price'].mad()
}
### 暂且选择这三种编码
enc_stats = ['mean', 'std', 'mad']
skf = KFold(n_splits=5, shuffle=True, random_state=2020)
for f in tqdm(['model', 'brand', 'regionCode']):
    enc_dict = {
    
    }
    for stat in enc_stats:
        enc_dict['{}_target_{}'.format(f, stat)] = stat
        train_df['{}_target_{}'.format(f, stat)] = 0
        test_df['{}_target_{}'.format(f, stat)] = 0
        enc_cols.append('{}_target_{}'.format(f, stat))
    for i, (trn_idx, val_idx) in enumerate(skf.split(train_df, train_df['price'])):
        trn_x, val_x = train_df.iloc[trn_idx].reset_index(drop=True), train_df.iloc[val_idx].reset_index(drop=True)
        enc_df = trn_x.groupby(f, as_index=False)['price'].agg(enc_dict)
        val_x = val_x[[f]].merge(enc_df, on=f, how='left')
        test_x = test_df[[f]].merge(enc_df, on=f, how='left')
        for stat in enc_stats:
            val_x['{}_target_{}'.format(f, stat)] = val_x['{}_target_{}'.format(f, stat)].fillna(stats_default_dict[stat])
            test_x['{}_target_{}'.format(f, stat)] = test_x['{}_target_{}'.format(f, stat)].fillna(stats_default_dict[stat])
            train_df.loc[val_idx, '{}_target_{}'.format(f, stat)] = val_x['{}_target_{}'.format(f, stat)].values
            test_df['{}_target_{}'.format(f, stat)] += test_x['{}_target_{}'.format(f, stat)].values / skf.n_splits


cols = cate_cols + date_cols + num_cols + feat_cols + enc_cols
sub = test_df[['SaleID']].copy()
test_df = test_df[cols]
labels = train_df['price'].values
train_df = train_df[cols]
print(train_df.shape)
train_df.head()

二次项特征构造,这个也是套路之一。二次项使用sklearn.preprocessing.PolynomialFeatures来进行特征的构造。它是使用多项式的方法来进行的,如果有a,b两个特征,那么它的2次多项式为(1,a,b,a^2,ab, b^2),这个多项式的形式就是是使用poly的效果。

# 二元项变换
feature_cols = [col for col in data.columns if col not in ['SaleID','price']]
poly_data = data[feature_cols] 
poly = PolynomialFeatures(2)
poly_data_ndarray = poly.fit_transform(poly_data)
poly_data_final = pd.DataFrame(poly_data_ndarray)
poly_data_final.columns = poly.get_feature_names(poly_data.columns)   

# 将二次转换过后的数据拼接到原数据集上
Merge_cols = ['price','SaleID']
poly_data_merge = data[Merge_cols]
data = poly_data_merge.merge(poly_data_final, how='left',right_index=True,left_index=True) 

变量间的交互统计

# 构建统计量函数
def Group_Statistic(df,df2,feature,Target):
    Train_gb = df.groupby(feature)
    all_info = {
    
    }
    for kind, kind_data in Train_gb:
        info = {
    
    }
        kind_data = kind_data[kind_data[Target] > 0]
        #info[feature + '_unique'] = len(kind_data['feature'].unique())
        #info[feature + '_amount'] = len(kind_data)
        info[feature + '_' + Target + '_max'] = kind_data[Target].max()
        info[feature + '_' + Target + '_median'] = kind_data[Target].median()
        info[feature + '_' + Target + '_min'] = kind_data[Target].min()
        info[feature + '_' + Target + '_sum'] = kind_data[Target].sum()
        info[feature + '_' + Target + '_std'] = kind_data[Target].std()
        info[feature + '_' + Target + '_average'] = round(kind_data[Target].sum() / (len(kind_data) + 1), 2)
        all_info[kind] = info
    brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={
    
    'index': feature})
    return df2.merge(brand_fe, how='left', on=feature)

# 多个特征建的交互
data_final = Group_Statistic(train_set,data_final,'regionCode','years')
data_final = Group_Statistic(train_set,data_final,'regionCode','months')
data_final = Group_Statistic(train_set,data_final,'regionCode','days')
data_final = Group_Statistic(train_set,data_final,'regionCode','kilometer')
data_final = Group_Statistic(train_set,data_final,'regionCode','power')

data_final = Group_Statistic(train_set,data_final,'name','years')
data_final = Group_Statistic(train_set,data_final,'name','months')
data_final = Group_Statistic(train_set,data_final,'name','days')
data_final = Group_Statistic(train_set,data_final,'name','kilometer')
data_final = Group_Statistic(train_set,data_final,'name','power')

data_final = Group_Statistic(train_set,data_final,'model','years')
data_final = Group_Statistic(train_set,data_final,'model','months')
data_final = Group_Statistic(train_set,data_final,'model','days')
data_final = Group_Statistic(train_set,data_final,'model','kilometer')
data_final = Group_Statistic(train_set,data_final,'model','power')

data_final = Group_Statistic(train_set,data_final,'brand','years')
data_final = Group_Statistic(train_set,data_final,'brand','months')
data_final = Group_Statistic(train_set,data_final,'brand','days')
data_final = Group_Statistic(train_set,data_final,'brand','kilometer')
data_final = Group_Statistic(train_set,data_final,'brand','power')

猜你喜欢

转载自blog.csdn.net/hu_hao/article/details/108721381