【第17期Datawhale | 零基础入门金融风控-贷款违约预测】Task03打卡:特征工程之特征预处理、异常值处理、数据分桶、特征交互、特征编码、特征选择等 【留了大量TODO需要深入学习】


md

零基础入门金融风控-贷款违约预测 Task03 特征工程

Task03目的:

  1. 学习特征预处理/缺失值处理/异常值处理/数据分桶等特征处理方法
  2. 学习特征交互/特征编码/特征选择的相应方法

0.0 导包

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import MinMaxScaler
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostRegressor
from sklearn.model_selection import StratifiedKFold, KFold
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
import warnings
warnings.filterwarnings('ignore')

0.1 公共变量

linux_file_path = '/plus/阿里云开发者-天池比赛/02_零基础入门金融风控_贷款违约预测/'
win_file_path = 'E:\\阿里云开发者-天池比赛\\02_零基础入门金融风控_贷款违约预测\\'

file_path = linux_file_path
train_file_path = file_path+'train.csv'
testA_file_path = file_path+'testA.csv'
now = datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')

0.2 数据读取pandas

data_train = pd.read_csv(train_file_path)
data_test_a = pd.read_csv(testA_file_path)
# print('Train Data shape 行*列:',data_train.shape)
# print('TestA Data shape 行*列:',data_test_a.shape)
print('易得,结果列  isDefault\n'
      'testA相较于train多出两列: \'n2.2\' \'n2.3\' ')

3.3.2 特征预处理

3.3.2.0 解析出数字属性列和类别属性列

# 这里介绍了数据缺失值的填充,时间格式特征的转化处理,某些对象类别特征的处理
numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns)
#                   filter(function or None, iterable)
category_fea = list(filter(lambda x: x not in numerical_fea,list(data_train.columns)))
# 结果了
label = 'isDefault'
numerical_fea.remove(label)

print('numerical_fea:', numerical_fea)
print('category_fea:', category_fea)

3.3.2.1 缺失值填充

#查看缺失值情况
# data_train.isnull().sum()

# 1. 把所有缺失值替换为指定的值0
# data_train = data_train.fillna(0)
# 2. 向用缺失值上面的值替换缺失值
# data_train = data_train.fillna(axis=0,method='ffill')
# 3. 纵向用缺失值下面的值替换缺失值,且设置最多只填充两个连续的缺失值
# data_train = data_train.fillna(axis=0,method='bfill',limit=2)

#按照平均数填充数值型特征
data_train[numerical_fea] = data_train[numerical_fea].fillna(data_train[numerical_fea].median())
data_test_a[numerical_fea] = data_test_a[numerical_fea].fillna(data_train[numerical_fea].median())
# #按照众数填充类别型特征
data_train[category_fea] = data_train[category_fea].fillna(data_train[category_fea].mode())
data_test_a[category_fea] = data_test_a[category_fea].fillna(data_train[category_fea].mode())

data_train.isnull().sum()
# 输出 employmentLength      46799 , 其他列都已经可以填充完毕

3.3.2.3 对象类型特征转换到数值 特指 employmentLength(就业年限(年))列

print('data_train: ', data_train['employmentLength'].head(10))
print('employmentLength: \n', data_train['employmentLength'].drop_duplicates())
def employmentLength_2_int(s):
    if pd.isnull(s):
        return s
    elif(str(s).count(' ') > 0):
        return np.int8(s.split()[0])
    else:
        return s

for data in [data_train, data_test_a]:
    data['employmentLength'].replace('10+ years', value='10 years', inplace=True)
    data['employmentLength'].replace('< 1 year', value='0 years', inplace=True)
    data['employmentLength'] = data['employmentLength'].apply(employmentLength_2_int)
# 打印处理后的结果
    x= data['employmentLength'].value_counts(dropna=False).sort_index()
    print(x)

3.3.2.3 对象类型特征转换到数值 对earliesCreditLine(借款人最早报告的信用额度开立的月份)进行预处理

data_train['earliesCreditLine'].sample(5)
"""
212023    Jun-2005
699597    Jul-2004
428151    Jul-1993
175922    Sep-2011
756776    Jul-2008
"""
# 截取后四位数字
for data in [data_train, data_test_a]:
    data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:]))

类别特征处理

部分类别特征

cate_features = category_fea
for f in cate_features:
    print(f, '类型数:', data[f].nunique())
# 像等级这种类别特征,是有优先级的可以label encode或者自映射
for data in [data_train, data_test_a]:
    data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})

# 类型数在2之上,又不是高维稀疏的,且纯分类特征 [label二进制编码形式]   get_dummies: 意为获得伪造数
for data in [data_train, data_test_a]:
    data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)

3.3.3 异常值处理

3.3.3.1 检测异常的方法一:均方差

# 正态分布, 近似68%的数据在一个u +- std, 近似95%的数据在u+=std*2的范围内, 近似99.7%的数据会在三个标准差+-u的范围内
def find_outliers_by_3segama(data, fea):
    data_std = np.std(data[fea])
    data_mean = np.mean(data[fea])
    outliers_cut_off = data_std * 3
    lower_rule = data_mean - outliers_cut_off
    upper_rule = data_mean + outliers_cut_off
    data[fea+'_outliers'] = data[fea].apply(lambda x:str('异常值') if x > upper_rule or x < lower_rule else '正常值')
    return data

# 1. 得到特征的异常值后可以进一步分析变量异常值和目标变量的关系
data_train_copy = data_train.copy()
for fea in numerical_fea:
    data_train_copy = find_outliers_by_3segama(data_train_copy, fea)
    fea_out_name = fea+'_outliers'
    print(data_train_copy[fea_out_name].value_counts())
    print(data_train_copy.groupby(fea_out_name)['isDefault'].sum())
    print('*'*10)

Q1: 例如可以看到异常值在两个变量上的分布几乎复合整体的分布,如果异常值都属于为1的用户数据里面代表什

    # 么呢?

print('常值都属于为1的用户数据, 那么这就不是异常值,可以建议强关联')

删除异常值

for fea in numerical_fea:
    data_train = data_train_copy[data_train_copy[fea+'_outliers']=='正常值']
    data_train = data_train.reset_index(drop=True)
print('data_train 异常值清除完成')

3.3.3.2 检测异常的方法二:箱型图

# TODO ???
# 总结一句话:四分位数会将数据分为三个点和四个区间,IQR = Q3 -Q1,下触须=Q1 − 1.5x IQR,上触须=Q3+ 1.5x IQR;

3.3.4 数据分桶(特征分箱)

"""
1. 特征分箱的目的:
  a. 从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量
      和因变量的相关度。从而使模型更加稳定。

2. 数据分桶的对象:
  a. 将连续变量离散化
  b. 将多状态的离散变量合并成少状态

3. 分箱的原因:
  a. 数据的特征内的值跨度可能比较大,对有监督和无监督中如k-均值聚类它使用欧氏距离作为相似度函数来
  测量数据点之间的相似度。都会造成大吃小的影响,其中一种解决方法是对计数值进行区间量化即数据
分桶也叫做数据分箱,然后使用量化后的结果。
分箱的优点:
   a. 处理缺失值:当数据源可能存在缺失值,此时可以把null单独作为一个分箱。
   b. 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰
   能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
   c. 业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在
   着非线性关系,此时可经过WOE变换。

4.特别要注意一下分箱的基本原则:
    a. (1)最小分箱占比不低于5%
    b. (2)箱内不能全部是好客户
    c. (3)连续箱单调
"""
# 1. 实践 固定宽度分箱
# 当数值横跨多个数量级时,最好按照 10 的幂(或任何常数的幂)来进行分组:09、1099、100999、10009999,等
# 等。固定宽度分箱非常容易计算,但如果计数值中有比较大的缺口,就会产生很多没有任何数据的空箱子。
# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000
data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000)
# ## 通过对数函数映射到指数宽度分箱
data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))

# 2. 分位数分箱 (10等分)
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)

# 3. 卡方分箱及其他分箱方法的尝试
#   1. 这一部分属于进阶部分,学有余力的同学可以自行搜索尝试。
# TODO

3.3.5 特征交互

# 交互特征的构造非常简单,使用起来却代价不菲。如果线性模型中包含有交互特征对,那它的训练时间和评
# 分时间就会从 O(n) 增加到 O(n2),其中 n 是单一特征的数量。
for col in ['grade', 'subGrade']:
    temp_dict = data_train.groupby([col])
    ['isDefault'].agg(['mean']).reset_index().rename(columns={'mean': col + '_target_mean'})
    temp_dict.index = temp_dict[col].values
    temp_dict = temp_dict[col + '_target_mean'].to_dict()
    data_train[col + '_target_mean'] = data_train[col].map(temp_dict)
    data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict)
# 其他衍生变量 mean 和 std
external_cols = ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']
for df in [data_train, data_test_a]:
    for item in external_cols:
        # transform('sum') == transform(lambda x: x /sum(x))
        df['grade_to_mean_' + item] = df['grade'] / df.groupby([item])['grade'].transform('mean')
        df['grade_to_std_' + item] = df['grade'] / df.groupby([item]) ['grade'].transform('std')

# TODO 搞一个无限跑的特征匹配计划

3.3.6 特征编码

###  3.3.6.1 labelEncode 直接放入树模型中
#label-encode:subGrade,postCode,title
# 高维类别特征需要进行转换
for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']):
    le = LabelEncoder()
    le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values))
    data_train[col] = le.transform(list(data_train[col].astype(str).values))
    data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values))
print('Label Encoding 完成')

3.3.6.2 逻辑回归等模型 要单独增加的特征工程

"""
什么是归一化?
1. 对特征做归一化,去除相关性高的特征
2. 归一化目的是让训练过程更好更快的收敛,避免特征大吃小的问题
3. 去除相关性是增加模型的可解释性,加快预测过程。
"""
# 举例归一化过程 #伪代码
for fea in external_cols:
    data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))

%% 3.3.7 特征选择/特征筛选/特征精简

"""
1.1 Filter
a. 方差选择法
b. 相关系数法(pearson 相关系数)
c. 卡方检验
d. 互信息法
2.2 Wrapper (RFE) a. 递归特征消除法
3.3 Embedded
a. 基于惩罚项的特征选择法
b. 基于树模型的特征选择
"""

3.3.7.1 Filter

# 基于特征间的关系进行筛选  方差选择法
#  方差选择法中,先要计算各个特征的方差,然后根据设定的阈值,选择方差大于阈值的特征
from sklearn.feature_selection import VarianceThreshold
#其中参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(train, target_train)

# 相关系数法
# Pearson 相关系数
# 皮尔森相关系数是一种最简单的,可以帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性。
# 结果的取值区间为 [-1,1] , -1 表示完全的负相关, +1表示完全的正相关,0 表示没有线性相关。
from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
#选择K个最好的特征,返回选择特征后的数据
# #第一个参数为计算评估特征是否好的函数,该函数输入特征矩阵和目标向量,
# #输出二元组(评分,P值)的数组,数组第i项为第i个特征的评分和P值。在此定义为计算相关系数
# #参数k为选择的特征个数
SelectKBest(k=5).fit_transform(train, target_train)

# 卡方检验
# 1. 经典的卡方检验是用于检验自变量对因变量的相关性。 假设自变量有N种取值,因变量有M种取值,考虑自变
# 量等于i且因变量等于j的样本频数的观察值与期望的差距。 其统计量如下: χ2=∑(A−T)2T,其中A为实际值,
# T为理论值
# 2. (注:卡方只能运用在正定矩阵上,否则会报错Input X must be non-negative)
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2 #参数k为选择的特征个数
SelectKBest(chi2, k=5).fit_transform(train, target_train)

# 互信息法
#  经典的互信息也是评价自变量对因变量的相关性的。 在feature_selection库的SelectKBest类结合最大信息系数
#   法可以用于选择特征,
from sklearn.feature_selection import SelectKBest
from minepy import MINE
#由于MINE的设计不是函数式的,定义mic方法将其为函数式的,
# #返回一个二元组,二元组的第2项设置成固定的P值0.5
def mic(x, y):
    m = MINE()
    m.compute_score(x, y)
    return (m.mic(), 0.5) #参数k为选择的特征个数
    SelectKBest(lambda X, Y: np.array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(train,target_train)

3.3.7.2 Wrapper (Recursive feature elimination,RFE)递归特征消除法

"""
递归特征消除法 递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,
再基于新的特征集进行下一轮训练。 在feature_selection库的RFE类可以用于选择特征,相关代码如下(以逻辑
回归为例):
"""
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression #递归特征消除法,返回特征选择后的数据 #参数estimator为基模型 #参数
# n_features_to_select为选择的特征个数
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(train, target_train)

3.3.7.3 Embedded (汉译:嵌入式的/基模型)

# 基于惩罚项的特征选择法 使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。 在
# feature_selection库的SelectFromModel类结合逻辑回归模型可以用于选择特征,相关代码如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression #带L1惩罚项的逻辑回归作为基模型的特征选择
SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(train, target_train)
# 基于树模型的特征选择 树模型中GBDT也可用来作为基模型进行特征选择。 在feature_selection库的
#   SelectFromModel类结合GBDT模型可以用于选择特征,相关代码如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier #GBDT作为基模型的特征选择
SelectFromModel(GradientBoostingClassifier()).fit_transform(train, target_train)

3.3.7.4 数据处理

"""
本数据集中我们删除非入模特征后,并对缺失值填充,然后用计算协方差的方式看一下特征间相关性,然后进 行模型训练
"""
# 删除不需要的数据
for data in [data_train, data_test_a]:
    data.drop(['issueDate','id'], axis=1,inplace=True)

    "纵向用缺失值上面的值替换缺失值"
    data_train = data_train.fillna(axis=0,method='ffill')
    x_train = data_train.drop(['isDefault','id'], axis=1) #计算协方差
    data_corr = x_train.corrwith(data_train.isDefault) #计算相关性

    result = pd.DataFrame(columns=['features', 'corr'])
    result['features'] = data_corr.index
    result['corr'] = data_corr.values    # 当然也可以直接看图.

    data_numeric = data_train[numerical_fea]
    correlation = data_numeric.corr()

    f,ax = plt.subplots(figsize = (7, 7))
    plt.title('Correlation of Numeric Features with Price',y=1,size=16)
    sns.heatmap(correlation,square = True, vmax=0.8)

    features = [f for f in data_train.columns if f not in ['id','issueDate','isDefault'] and '_outliers' not in f]
    x_train = data_train[features]
    x_test = data_test_a[features]
    y_train = data_train['isDefault']

3.4 总结


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM