学习地址:https: //github.com/datawhalechina/team-learning-data-mining/tree/master/FinancialRiskControl
天池竞赛地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction
特征工程指的是把原始数据转变为模型的训练数据的过程,它的目的就是获取更好的训练数据特征,使得机器学习模型逼近这个上限。特征工程能使得模型的性能得到提升,有时甚至在简单的模型上也能取得不错的效果。特征工程在机器学习中占有非常重要的作用,一般认为括特征构建(数据预处理)、特征提取、特征选择三个部分。
相关包及数据导入
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
'''
Tqdm 是一个快速,可扩展的Python进度条,
可以在 Python 长循环中添加一个进度提示信息,
用户只需要封装任意的迭代器 tqdm(iterator)
使用参考:https://blog.csdn.net/zkp_987/article/details/81748098
'''
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
'''
class sklearn.feature_selection.SelectKBest(score_func=, *, k=10)
作用:根据k的最高分数选择特征。
使用参考:https://blog.csdn.net/niutingbaby/article/details/96102393
'''
from sklearn.feature_selection import SelectKBest
'''
计算每个非负要素与类之间的卡方统计量。
此分数可用于从X中选择测试卡方统计量具有最高值的n_features特征,
该特征必须仅包含非负特征,例如布尔值或频率(例如,文档分类中的术语计数),相对于类。
'''
from sklearn.feature_selection import chi2
#数据归一化函数 MinMaxScaler:归一到 [ 0,1 ] MaxAbsScaler:归一到 [ -1,1 ]
from sklearn.preprocessing import MinMaxScaler
import xgboost as xgb
import lightgbm as lgb
'''
对于catboost的安装可以直接在jupyter中进行 采用
!pip install -i https://pypi.tuna.tsinghua.edu.cn/simple catboost
'''
from catboost import CatBoostRegressor
import warnings
warnings.filterwarnings('ignore')
from sklearn.model_selection import StratifiedKFold,KFold
from sklearn.metrics import accuracy_score,f1_score,roc_auc_score,log_loss
#导入数据
data_train=pd.read_csv('train.csv')
data_test_a=pd.read_csv('testA.csv')
#数值特征
numerical_fea=list(data_train.select_dtypes(exclude=['object']).columns)
#类别特征
category_fea=list(filter(lambda x:x not in numerical_fea,list(data_train.columns)))
label='isDefault' #需要预测的结果,即预测用户是够违约是否违约
#将是否违约者一项从numerical_fea中剔除,这是需要预测的结果
numerical_fea.remove(label) #注意如何从list中移除某值采用的是remove()
进行缺失值的处理方法有多种,可以尝试多种填充方式,选择效果最好的一种
data_train = data_train.fillna(0)
data_train = data_train.fillna(axis=0,method='ffill')
data_train = data_train.fillna(axis=0,method='bfill',limit=2)
#查看缺失值情况
data_train.isnull().sum()
#按照平均数填充数值型特征
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_test_a[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_test_a[category_fea].mode())
data_train.isnull().sum()
#发现只有employmentLength没有被填充上
#查看类别特征
category_fea
#['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
for i in category_fea:
print('=={}的变量分布=='.format(i))
print(data_train[i].value_counts())
print('\n')
==grade的变量分布==
B 233690
C 227118
A 139661
D 119453
E 55661
F 19053
G 5364
Name: grade, dtype: int64
==subGrade的变量分布==
C1 50763
B4 49516
B5 48965
B3 48600
C2 47068
C3 44751
C4 44272
B2 44227
B1 42382
C5 40264
A5 38045
A4 30928
D1 30538
D2 26528
A1 25909
D3 23410
A3 22655
A2 22124
D4 21139
D5 17838
E1 14064
E2 12746
E3 10925
E4 9273
E5 8653
F1 5925
F2 4340
F3 3577
F4 2859
F5 2352
G1 1759
G2 1231
G3 978
G4 751
G5 645
Name: subGrade, dtype: int64
==employmentLength的变量分布==
10+ years 262753
2 years 72358
< 1 year 64237
3 years 64152
1 year 52489
5 years 50102
4 years 47985
6 years 37254
8 years 36192
7 years 35407
9 years 30272
Name: employmentLength, dtype: int64
==issueDate的变量分布==
2016-03-01 29066
2015-10-01 25525
2015-07-01 24496
2015-12-01 23245
2014-10-01 21461
...
2007-08-01 23
2007-07-01 21
2008-09-01 19
2007-09-01 7
2007-06-01 1
Name: issueDate, Length: 139, dtype: int64
==earliesCreditLine的变量分布==
Aug-2001 5567
Sep-2003 5403
Aug-2002 5403
Oct-2001 5258
Aug-2000 5246
...
Feb-1960 1
Jun-1958 1
May-1960 1
Nov-1954 1
Apr-1958 1
Name: earliesCreditLine, Length: 720, dtype: int64
#转化为时间格式
#显示所有列
#issueDate原来的格式为
pd.set_option('display.max_columns', None)
for data in[data_train,data_test_a]:
#data['issueDate']=pd.to_datetime(data['issueDate']) #变成了2014-07-01格式
data['issueDate']=pd.to_datetime(data['issueDate'],format='%Y-%m-%d') #变成了2014-07-01格式(可能是怕之前转化不够彻底,所以又这样做了一步?)
#构造时间特征,即距离最早时间的天数
startdate=datetime.datetime.strptime('2007-06-01','%Y-%m-%d') #datetime.datetime(2007, 6, 1, 0, 0)
data['issueDateDT']=data['issueDate'].apply(lambda x:x-startdate).dt.days
data_train.head()
#统计就业年限分布,并将其按顺序排列
data_train['employmentLength'].value_counts(dropna=False).sort_index()
#在进行数据填充的时候只有这个没被填充上,而且它是对象特征
def employmentLength_to_int(s):
if pd.isnull(s):
return s
else:
#通过这一步骤可以提取出前面的年份,通过这一步骤可以完成1 years~9 years的转换
#还剩下10+ years和<1 year没有被转换
return np.int8(s.split()[0])
for data in [data_train,data_test_a]:
#将10+ years替换为10 year方便和前面一起做统一处理
data['employmentLength'].replace(to_replace='10+ years',value='10 years',inplace=True)
data['employmentLength'].replace('< 1 year','0 year',inplace=True)
data['employmentLength']=data['employmentLength'].apply(employmentLength_to_int)
#对`earliesCreditLine`(贷款人最早报告信用额度开立的月份)进行预处理
#原来的数据格式是 May-2002形式的,这里采用了比较简单的处理方式,舍弃了月份,只保留了年份,并将其转化为了int形式
#对earliesCreditLine值保留了年份,舍弃了月份,并将年份转换为了int形式
for data in [data_train, data_test_a]:
data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:]))
data_train.head()
查看类别特征的维数
cate_features = ['grade', 'subGrade', 'employmentTitle', 'homeOwnership', 'verificationStatus', 'purpose', 'postCode', 'regionCode', \
'applicationType', 'initialListStatus', 'title', 'policyCode']
for f in cate_features:
print(f, '类型数:', data[f].nunique())
像等级这种类别特征,是有优先级的可以用labelencode或者自映射
for data in [data_train,data_test_a]:
#grade原来有ABCDEFG其中特征
data['grade']=data['grade'].map({
'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
data_train['grade'].value_counts().sort_index()
for data in [data_train, data_test_a]:
data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
data.head()
如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内
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
for fea in numerical_fea:
data_train=find_outliers_by_3segama(data_train,fea)
print(data_train[fea+'_outliers'].value_counts())
print(data_train.groupby(fea+'_outliers')['isDefault'].sum())
print('*'*10)
#删除异常值
for fea in numerical_fea:
data_train=data_train[data_train[fea+'_outliers']=='正常值']
data_train = data_train.reset_index(drop=True)
四分位数(Quartile)是统计学中分位数的一种,即把所有数值由小到大排列并分成四等份,处于三个分割点位置的数值就是四分位数。
第一四分位数(Q1),又称“较小四分位数”,等于该样本中所有数值由小到大排列后第25%的数字。
第二四分位数(Q2),又称“中位数”,等于该样本中所有数值由小到大排列后第50%的数字。
第三四分位数(Q3),又称“较大四分位数”,等于该样本中所有数值由小到大排列后第75%的数字。
第三四分位数与第一四分位数的差距又称四分位距(InterQuartile Range, IQR)。 公式:IQR = Q3 − Q1
特征分箱的目的:
数据分桶的对象:
分箱的原因:
分箱的优点:
分箱的基本原则:
Python实现连续数据的离散化处理主要基于两个函数,pandas.cut
和pandas.qcut
,前者根据指定分界点对连续数据进行分箱处理,后者则可以根据指定箱子的数量对连续数据进行等宽分箱处理,所谓等宽指的是每个箱子中的数据量是相同的
当数值横跨多个数量级时,最好按照 10 的幂(或任何常数的幂)来进行分组:09、1099、100999、10009999,等等。固定宽度分箱非常容易计算,但如果计数值中有比较大的缺口,就会产生很多没有任何数据的空箱子。
#贷款金额数值最小的是500,最大的是40000,所以分箱后值是从0取到40
#通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000
#np.floor_divide只保留整数结果(两个数组元素都是整数,输出整数,若有某些元素为小数,则对应结果是取整的小数)
data['loanAmnt_bin1']=np.floor_divide(data['loanAmnt'],1000)
data['loanAmnt_bin1'].value_counts().sort_index()
#通过对函数的映射到指数宽度分箱
data['loanAmnt_bin2']=np.floor(np.log10(data['loanAmnt']))
data['loanAmnt_bin2'].value_counts().sort_index()
#2.0即原来的数是100-999 3.0是1000到10000
#这里是指定了要分十个箱子
#参考https://blog.csdn.net/yeshang_lady/article/details/107957020
#接收array型或False型数据,默认取值为None. 这个参数需要和retbins参数一起使用。当labels=False时,只返回分箱的索引。
#当labels为array时,其长度要和bins的个数相等,label为False返回的是分箱的编号
data['loanAmnt_bin3']=pd.qcut(data['loanAmnt'],10,labels=False)
data['loanAmnt_bin3'].value_counts().sort_index()
#如果不写labels=False
data['loanAmnt_bin4']=pd.qcut(data['loanAmnt'],10)
data['loanAmnt_bin4'].value_counts().sort_index()
卡方分箱参考:https://blog.csdn.net/hxcaifly/article/details/80203663?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
交互特征的构造很简单,但是使用起来却代价不菲,如果线性模型中包含有交互特征对,那么他的训练时间和评分时间就会从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)
#计算的是每个贷款等级违约的概率,贷款等级有7个,所以算出来的概率也有七个
data_train['grade_target_mean'].value_counts()
#其他衍生变量mean和std
for df in[data_train,data_test_a]:
for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:
df['grade_to_mean_'+item]=df['grade']/df.groupby([item])['grade'].transform('mean')
#pandas中transform的作用是使得groupby的长度一样长参照https://zhuanlan.zhihu.com/p/86350553
df['grade_to_std'+item]=df['grade']/df.groupby([item])['grade'].transform('std')
#对subGrade,postCode,title 贷款之子等级、贷款人在贷款申请中提供的邮政编码的前3位数进行标签编码
#高维类别特征需要转换
for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']): #tqdm进度条
le=LabelEncoder()
#提取出来所有的值
#le.fit是进行编码
#le.transform是把列表中的转化为编码后的
#如le.fit([1,5,67,100]) 那么用1-》0,5-》1,67-》2,100-》3
#le.transform([1,1,100,67,5])就会被编码成0 0 3 2 1
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 完成')
#归一化过程的例子,这里采用的最大最小归一化
#伪代码
for fea in [要归一化的特征列表]:
data[fea]=((data[fea]-np.min(data[fea]))/ (np.max(data[fea]) - np.min(data[fea])))
https://blog.csdn.net/a8689756/article/details/115655497
特征选择技术可以精简掉误用的特征,以降低最终模型的复杂性,它的最终目的是得到一个简约的模型,在不降低预测准确率或对预测准确率影响不大的情况下提高计算速度。
特征选择不是为了减少训练时间(实际有可能增加总体的训练时间),而是为了减少模型评分时间
特征选择的方法:
(1)Filter:基于特征间的关系进行筛选
方差选择法
#导入相关包
from sklearn.feature_selection import VarianceThreshold
#其中参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(train,target_train)
相关系数法
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)
卡方检验
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
#参数k为选择的特征个数
SelectKBest(chi2, k=5).fit_transform(train,target_train)
互信息法
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: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(train,target_train)
(2)Wrapper (Recursive feature elimination,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)Embedded
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)
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT作为基模型的特征选择
SelectFromModel(GradientBoostingClassifier()).fit_transform(train,target_train)
本数据集中我们删除非如模特征后,并对缺失值进行填充,然后计算协方差的方式查看特征相关性,然后进行模型训练
#删除不需要的数据
#删除了id(没用),issueDate(贷款发放的月份)
#但是这里没有删,等着还需要自己计算
for data in [data_train,data_test_a]:
#axis=1删除的是列否则默认删除的是行
data.drop(['issueDate','id'], axis=1,inplace=True)
#纵向用缺失值上面的值替换缺失值
data_train=data_train.fillna(axis=0,method='ffill')
x_train=data_train.drop(['isDefault'],axis=1)
#计算协方差
data_corr = x_train.corrwith(data_train.isDefault) #计算相关性
data_corr
#也可以直接看图相关性
#查看数值型变量之间的
a=pd.read_csv('train.csv') #因为前面把id删了所以这里智能这样了
data_numeric=a[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)
'''
seaborn.heatmap
seaborn.heatmap(data, vmin=None, vmax=None, cmap=None, center=None,
robust=False, annot=None, fmt='.2g', annotkws=None, linewidths=0, linecolor='white', cbar=True, cbarkws=None, cbar_ax=None, square=False, ax=None, xticklabels=True, yticklabels=True, mask=None, **kwargs)
data:矩阵数据集,可以使numpy的数组(array),如果是pandas的dataframe,
则df的index/column信息会分别对应到heatmap的columns和rows
linewidths,热力图矩阵之间的间隔大小
vmax,vmin, 图例中最大值和最小值的显示值,没有该参数时默认不显示
'''
基于第二次直播整理
https://tianchi.aliyun.com/course/video?liveId=41204