一、前言

1.1 赛题背景

赛题以金融风控中的个人信贷为背景,要求选手根据贷款申请人的数据信息预测其是否有违约的可能,以此判断是否通过此项贷款,这是一个典型的分类问题。

任务:预测用户贷款是否违约

比赛地址:【AI入门系列】金融守护者:金融风险预测学习赛_学习赛_天池大赛-阿里云天池的赛制

1.2 赛题数据

数据来自某信贷平台的贷款记录,总数据量超过120w,包含47列变量信息,其中15列为匿名变量。

为了保证比赛的公平性,将会从中抽取80万条作为训练集,20万条作为测试集A,20万条作为测试集B,同时会对employmentTitle、purpose、postCode和title等信息进行脱敏。

数据集包含三个下载文件

train.csv:训练集test.csv:测试集sample_submit.csv:提交文件样式

train.csv数据 testA.csv数据

字段表

FieldDescriptionid为贷款清单分配的唯一信用证标识loanAmnt贷款金额term贷款期限(year)interestRate贷款利率installment分期付款金额grade贷款等级subGrade贷款等级之子级employmentTitle就业职称employmentLength就业年限(年)homeOwnership借款人在登记时提供的房屋所有权状况annualIncome年收入verificationStatus验证状态issueDate贷款发放的月份purpose借款人在贷款申请时的贷款用途类别postCode借款人在贷款申请中提供的邮政编码的前3位数字regionCode地区编码dti债务收入比delinquency_2years借款人过去2年信用档案中逾期30天以上的违约事件数ficoRangeLow借款人在贷款发放时的fico所属的下限范围ficoRangeHigh借款人在贷款发放时的fico所属的上限范围openAcc借款人信用档案中未结信用额度的数量pubRec贬损公共记录的数量pubRecBankruptcies公开记录清除的数量revolBal信贷周转余额合计revolUtil循环额度利用率,或借款人使用的相对于所有可用循环信贷的信贷金额totalAcc借款人信用档案中当前的信用额度总数initialListStatus贷款的初始列表状态applicationType表明贷款是个人申请还是与两个共同借款人的联合申请earliesCreditLine借款人最早报告的信用额度开立的月份title借款人提供的贷款名称policyCode公开可用的策略_代码=1新产品不公开可用的策略_代码=2n系列匿名特征匿名特征n0-n14,为一些贷款人行为计数特征的处理

1.3 评价指标

提交结果为每个测试样本是1的概率,也就是y为1的概率。

评价方法为AUC评估模型效果(越大越好)。

注:AUC(Area Under Curve)被定义为 ROC曲线下与坐标轴围成的面积。

详细参见: 「机器学习」分类算法常见的评估指标 机器学习:评估指标

其次,除了要求的评价指标外,对于二分类问题其评价指标还有精确率、召回率、ROC、F值等

1.4 赛题整体流程

分析主要步骤如下

二、探索性的数据分析EDA

数据探索性分析是对数据进行初步分析,了解数据特征,观察数据类型,分析数据分布等等,为后续特征工程,以及建模分析都特别重要

例如

分析数据中每个字段的含义、分布、缺失情况; 字段表示什么含义、字段的类型是什么、字段的取值空间是什么、字段每个取值表示什么意义。 字段整体的分布,分析字段在训练集/测试集中的分布情况。 字段缺失值的分布比例,字段在训练集/测试集的缺失情况。分析数据中每个字段的与赛题标签的关系;分析数据字段两两之间,或者主者之间的关系;

引用图片:https://zhuanlan.zhihu.com/p/259788410

首先导入必要模块

import warnings

warnings.filterwarnings("ignore")

import numpy as np

import pandas as pd

import seaborn as sns

import matplotlib.pyplot as plt

import statsmodels.formula.api as smf

from sklearn.preprocessing import LabelEncoder

from sklearn.feature_selection import SelectKBest

from sklearn.model_selection import train_test_split

from sklearn.model_selection import StratifiedKFold, KFold

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.metrics import accuracy_score, f1_score, roc_auc_score, log_loss

plt.rcParams["font.sans-serif"]=["SimHei"]

plt.rcParams["axes.unicode_minus"]=False

AI写代码python

运行

123456789101112131415161718192021222324252627

使用pandas读入数据,包括训练集与测试集

导入数据集(数据集过大可以进行瘦身处理)

train = pd.read_csv('train.csv')

test = pd.read_csv('testA.csv')

AI写代码py

12

查看部分数据

train.head()

AI写代码python

运行

1

idloanAmntterminterestRateinstallmentgradesubGradeemploymentTitleemploymentLengthhomeOwnership…n5n6n7n8n9n10n11n12n13n140035000.0519.52917.97EE2320.02 years2…9.08.04.012.02.07.00.00.00.02.01118000.0518.49461.90DD2219843.05 years0…NaNNaNNaNNaNNaN13.0NaNNaNNaNNaN2212000.0516.99298.17DD331698.08 years0…0.021.04.05.03.011.00.00.00.04.03311000.037.26340.96AA446854.010+ years1…16.04.07.021.06.09.00.00.00.01.0443000.0312.99101.07CC254.0NaN1…4.09.010.015.07.012.00.00.00.04.0

5 rows × 47 columns

2.1 总体分布

前面提到,整个数据包括80万条训练集,20万条测试集A,20万条测试集B

另外

训练集中有47列,其中包括46个特征列,1个标签列 测试集中只有46个特征列

# 样本个数和特征维度

train.shape

# (800000, 47)

test.shape

# (200000, 46)

AI写代码py

12345

查看特征名

train.columns

'''

Index(['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'grade',

'subGrade', 'employmentTitle', 'employmentLength', 'homeOwnership',

'annualIncome', 'verificationStatus', 'issueDate', 'isDefault',

'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years',

'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec',

'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',

'initialListStatus', 'applicationType', 'earliesCreditLine', 'title',

'policyCode', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',

'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],

dtype='object')

'''

AI写代码python

运行

12345678910111213

接下来查看数据集的一些基本信息(缺失情况、类型…)

train.info()

'''

RangeIndex: 800000 entries, 0 to 799999

Data columns (total 47 columns):

id 800000 non-null int64

loanAmnt 800000 non-null float64

term 800000 non-null int64

interestRate 800000 non-null float64

installment 800000 non-null float64

grade 800000 non-null object

subGrade 800000 non-null object

employmentTitle 799999 non-null float64

employmentLength 753201 non-null object

homeOwnership 800000 non-null int64

annualIncome 800000 non-null float64

verificationStatus 800000 non-null int64

issueDate 800000 non-null object

isDefault 800000 non-null int64

purpose 800000 non-null int64

postCode 799999 non-null float64

regionCode 800000 non-null int64

dti 799761 non-null float64

delinquency_2years 800000 non-null float64

ficoRangeLow 800000 non-null float64

ficoRangeHigh 800000 non-null float64

openAcc 800000 non-null float64

pubRec 800000 non-null float64

pubRecBankruptcies 799595 non-null float64

revolBal 800000 non-null float64

revolUtil 799469 non-null float64

totalAcc 800000 non-null float64

initialListStatus 800000 non-null int64

applicationType 800000 non-null int64

earliesCreditLine 800000 non-null object

title 799999 non-null float64

policyCode 800000 non-null float64

n0 759730 non-null float64

n1 759730 non-null float64

n2 759730 non-null float64

n3 759730 non-null float64

n4 766761 non-null float64

n5 759730 non-null float64

n6 759730 non-null float64

n7 759730 non-null float64

n8 759729 non-null float64

n9 759730 non-null float64

n10 766761 non-null float64

n11 730248 non-null float64

n12 759730 non-null float64

n13 759730 non-null float64

n14 759730 non-null float64

dtypes: float64(33), int64(9), object(5)

memory usage: 286.9+ MB

'''

AI写代码python

运行

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455

可以看到,许多特征存在缺失,特征的类型有dtypes: float64(33), int64(9), object(5)

对于缺失值的处理以及类型转换将在特征工程中说明

接下来查看一下数据的描述性分析

描述性统计

加深对数据分布、数据结构等的理解

看一下数据特征之间的两两关联关系

数据中空值的个数、0的个数、正值或负值的个数,

以及均值、方差、最小值、最大值、偏度、峰度等。

AI写代码python

运行

12345

train.describe()

# train.describe().T

AI写代码python

运行

12

大致了解一下数据的分布、结构,简单的看一下特征值有没有什么异常

idloanAmntterminterestRateinstallmentemploymentTitlehomeOwnershipannualIncomeverificationStatusisDefault…n5n6n7n8n9n10n11n12n13n14count800000.000000800000.000000800000.000000800000.000000800000.000000799999.000000800000.0000008.000000e+05800000.000000800000.000000…759730.000000759730.000000759730.000000759729.000000759730.000000766761.000000730248.000000759730.000000759730.000000759730.000000mean399999.50000014416.8188753.48274513.238391437.94772372005.3517140.6142137.613391e+041.0096830.199513…8.1079378.5759948.28295314.6224885.59234511.6438960.0008150.0033840.0893662.178606std230940.2520158716.0861780.8558324.765757261.460393106585.6402040.6757496.894751e+040.7827160.399634…4.7992107.4005364.5616898.1246103.2161845.4841040.0300750.0620410.5090691.844377min0.000000500.0000003.0000005.31000015.6900000.0000000.0000000.000000e+000.0000000.000000…0.0000000.0000000.0000001.0000000.0000000.0000000.0000000.0000000.0000000.00000025%199999.7500008000.0000003.0000009.750000248.450000427.0000000.0000004.560000e+040.0000000.000000…5.0000004.0000005.0000009.0000003.0000008.0000000.0000000.0000000.0000001.00000050%399999.50000012000.0000003.00000012.740000375.1350007755.0000001.0000006.500000e+041.0000000.000000…7.0000007.0000007.00000013.0000005.00000011.0000000.0000000.0000000.0000002.00000075%599999.25000020000.0000003.00000015.990000580.710000117663.5000001.0000009.000000e+042.0000000.000000…11.00000011.00000010.00000019.0000007.00000014.0000000.0000000.0000000.0000003.000000max799999.00000040000.0000005.00000030.9900001715.420000378351.0000005.0000001.099920e+072.0000001.000000…70.000000132.00000079.000000128.00000045.00000082.0000004.0000004.00000039.00000030.000000

2.2 数据类型分析

2.2.1 数值类型(连续变量、离散型变量和单值变量)

这里引用文章观点:https://blog.csdn.net/qq_43401035/article/details/108648912

数值类型

# 数值类型

numerical_feature = list(train.select_dtypes(exclude=['object']).columns)

numerical_feature

AI写代码python

运行

123

['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'employmentTitle',

'homeOwnership', 'annualIncome', 'verificationStatus', 'isDefault', 'purpose',

'postCode', 'regionCode', 'dti', 'delinquency_2years', 'ficoRangeLow',

'ficoRangeHigh', 'openAcc', 'pubRec', 'pubRecBankruptcies', 'revolBal',

'revolUtil', 'totalAcc', 'initialListStatus', 'applicationType',

'title', 'policyCode', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',

'n9', 'n10', 'n11', 'n12', 'n13', 'n14']

AI写代码python

运行

1234567

一共有42个数值型变量(dtypes: float64(33), int64(9), object(5))

len(numerical_feature) ## 42

AI写代码python

运行

1

由于数值类型又可以分为连续变量、离散型变量和单值变量

因此接下来把数值型中的连续型变量和离散型变量区分开来:

# 连续型变量

serial_feature = []

# 离散型变量

discrete_feature = []

# 单值变量

unique_feature = []

for fea in numerical_feature:

temp = train[fea].nunique()# 返回的是唯一值的个数

if temp == 1:

unique_feature.append(fea)

# 自定义变量的值的取值个数小于10就为离散型变量

elif temp <= 10:

discrete_feature.append(fea)

else:

serial_feature.append(fea)

AI写代码python

运行

12345678910111213141516

(1)连续型变量

serial_feature

'''

['id', 'loanAmnt', 'interestRate', 'installment', 'employmentTitle',

'annualIncome', 'purpose', 'postCode', 'regionCode', 'dti',

'delinquency_2years', 'ficoRangeLow', 'ficoRangeHigh', 'openAcc',

'pubRec', 'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',

'title', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',

'n9', 'n10', 'n13', 'n14']

'''

AI写代码py

123456789

对于连续型变量

查看某一个数值型变量的分布,查看变量是否符合正态分布,如果不符合正太分布的变量可以log化后再观察下是否符合正态分布。

正态化的原因:一些情况下正态非正态可以让模型更快的收敛,一些模型要求数据正态(eg. GMM、KNN),保证数据不要过偏态即可,过于偏态可能会影响模型预测结果。

可视化呈现

#每个数字特征得分布可视化

f = pd.melt(train, value_vars=serial_feature)

g = sns.FacetGrid(f, col="variable", col_wrap=3, sharex=False, sharey=False)

g = g.map(sns.distplot, "value")

AI写代码python

运行

1234

可以单独查看一下贷款金额 loanAmnt 的分布情况

plt.figure(1 , figsize = (8 , 5))

sns.distplot(train.loanAmnt,bins=40)

plt.xlabel('loanAmnt')

AI写代码sql

123

对于违约与不违约两类样本的贷款金额分布情况

sns.kdeplot(train.loanAmnt[label[label==1].index], label='1', shade=True)#违约

sns.kdeplot(train.loanAmnt[label[label==0].index], label='0', shade=True)#没有违约

plt.xlabel('loanAmnt')

plt.ylabel('Density');

AI写代码python

运行

1234

单独查看一下年收入的分布情况

plt.figure(1 , figsize = (8 , 5))

sns.distplot(train['annualIncome'])

plt.xlabel('annualIncome')

AI写代码python

运行

123

(2)离散型变量

discrete_feature

'''

['term', 'homeOwnership', 'verificationStatus', 'isDefault',

'initialListStatus', 'applicationType', 'n11', 'n12']

'''

AI写代码py

12345

离散型变量的类型数情况

for f in discrete_feature:

print(f, '类型数:', train[f].nunique())

'''

term 类型数: 2

homeOwnership 类型数: 6

verificationStatus 类型数: 3

isDefault 类型数: 2

initialListStatus 类型数: 2

applicationType 类型数: 2

n11 类型数: 5

n12 类型数: 5

'''

AI写代码sql

123456789101112

离散型特征可视化呈现

df_ = train[discrete_feature]

sns.set_style("whitegrid") # 使用whitegrid主题

fig,axes=plt.subplots(nrows=4,ncols=2,figsize=(8,10))

for i, item in enumerate(df_):

plt.subplot(4,2,(i+1))

#ax=df[item].value_counts().plot(kind = 'bar')

ax=sns.countplot(item,data = df_,palette="Pastel1")

plt.xlabel(str(item),fontsize=14)

plt.ylabel('Count',fontsize=14)

plt.xticks(fontsize=13)

plt.yticks(fontsize=13)

#plt.title("Churn by "+ str(item))

i=i+1

plt.tight_layout()

plt.show()

AI写代码python

运行

12345678910111213141516

查看一下每个特征的分布情况

(3)单值变量

单值变量表示该特征只有一种类别,对于数值全部都一样的特征,可以考虑直接删除

unique_feature

'''

['policyCode']

'''

AI写代码py

1234

2.2.2 分类型特征

# 分类型特征

category_feature = list(filter(lambda x: x not in numerical_feature,list(train.columns)))

category_feature

['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']

AI写代码python

运行

12345

对应info结果中的 (dtypes: float64(33), int64(9), object(5))

这里 "grade"为贷款等级,"subGrade"为贷款等级之子级,"employmentLength"为就业年限,"issueDate"为贷款发放的月份,"earliesCreditLine"为借款人最早报告的信用额度开立的月份,共有5个分类型特征。

查看一下这些分类型特征的结构,后面需要对其进行特征编码

train[category_feature]

'''

grade subGrade employmentLength issueDate earliesCreditLine

0 E E2 2 years 2014-07-01 Aug-2001

1 D D2 5 years 2012-08-01 May-2002

2 D D3 8 years 2015-10-01 May-2006

3 A A4 10+ years 2015-08-01 May-1999

4 C C2 NaN 2016-03-01 Aug-1977

... ... ... ... ...

799995 C C4 7 years 2016-07-01 Aug-2011

799996 A A4 10+ years 2013-04-01 May-1989

799997 C C3 10+ years 2015-10-01 Jul-2002

799998 A A4 10+ years 2015-02-01 Jan-1994

799999 B B3 5 years 2018-08-01 Feb-2002

[800000 rows x 5 columns]

'''

AI写代码python

运行

1234567891011121314151617

分类型特征可视化呈现

df_category = train[['grade', 'subGrade']]

sns.set_style("whitegrid") # 使用whitegrid主题

color = sns.color_palette()

fig,axes=plt.subplots(nrows=2,ncols=1,figsize=(10,10))

for i, item in enumerate(df_category):

plt.subplot(2,1,(i+1))

#ax=df[item].value_counts().plot(kind = 'bar')

ax=sns.countplot(item,data = df_category)

plt.xlabel(str(item),fontsize=14)

plt.ylabel('Count',fontsize=14)

plt.xticks(fontsize=13)

plt.yticks(fontsize=13)

#plt.title("Churn by "+ str(item))

i=i+1

plt.tight_layout()

plt.show()

AI写代码python

运行

1234567891011121314151617

可以看出对于grade特征中A\B\C等级的贷款占比比较大

employmentLength就业年限可视化呈现

plt.figure(1 , figsize = (10 , 8))

sns.barplot(train["employmentLength"].value_counts(dropna=False),

train["employmentLength"].value_counts(dropna=False).keys())

plt.xticks(fontsize=13)

plt.yticks(fontsize=13)

plt.xlabel('employmentLength',fontsize=14)

plt.show()

AI写代码python

运行

1234567

可以看到,就业年限最多是 10+year 对于 issueDate 与 earliesCreditLine,统计一下每个类别的数量

for i in train[['issueDate', 'earliesCreditLine']]:

print(train[i].value_counts())

print()

'''

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

Aug-2001 5567

Aug-2002 5403

Sep-2003 5403

Oct-2001 5258

Aug-2000 5246

...

Jan-1946 1

Nov-1953 1

Aug-1958 1

Jun-1958 1

Oct-1957 1

Name: earliesCreditLine, Length: 720, dtype: int64

'''

AI写代码python

运行

12345678910111213141516171819202122232425262728293031

2.3 目标变量(标签y)的分布

查看目标变量(标签)是否平衡

若分类问题中各类别样本数量差距太大,则会造成样本不均衡的问题。样本不均衡不利于建立与训练出正确的模型,且不能做出合理的评估。

label=train.isDefault

label.value_counts()/len(label)

'''

0 0.800488

1 0.199513

Name: isDefault, dtype: float64

'''

AI写代码sql

12345678

sns.countplot(label)

AI写代码sql

1

可以看到,贷款违约与不违约的比例大约为1:4,样本较不平衡,这是金融风控模型评估的中常见的现象,大多数的人都是不会拖欠贷款的。

对于这种情况,考虑后续将进行采样等操作。

接下来,看一下目标变量和分类类别之间的分布关系

train_loan_fr = train.loc[train['isDefault'] == 1]

train_loan_nofr = train.loc[train['isDefault'] == 0]

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 8))

# 目标变量为1时候grade的分布

train_loan_fr.groupby("grade").size().plot.bar(ax=ax1)

# 目标变量为0时候grade的分布

train_loan_nofr.groupby("grade")["grade"].count().plot.bar(ax=ax2)

# 目标变量为1时候employmentLength的分布

train_loan_fr.groupby("employmentLength").size().plot.bar(ax=ax3)

# 目标变量为0时候employmentLength的分布

train_loan_nofr.groupby("employmentLength")["employmentLength"].count().plot.bar(ax=ax4)

plt.xticks(rotation=90);

AI写代码py

12345678910111213

查看一下正负样本的数据差异

把数据集按正负样本分成两份,查看变量的分布差异

train_positve = train[train['isDefault'] == 1]

train_negative = train[train['isDefault'] != 1]

f, ax = plt.subplots(len(numerical_feature),2,figsize = (10,80))

for i,col in enumerate(numerical_feature):

sns.distplot(train_positve[col],ax = ax[i,0],color = "blue")

ax[i,0].set_title("positive")

sns.distplot(train_negative[col],ax = ax[i,1],color = 'red')

ax[i,1].set_title("negative")

plt.subplots_adjust(hspace = 1)

AI写代码python

运行

123456789

2.4 缺失值查看

如果缺失值过多会对整体的模型结果产生一定的影响,因此每次在建模之前都需要对数据的缺失值情况就行查看,若有缺失情况,需要在后续特征工程中进行填补

缺省值查看

# 去掉标签

X_missing = train.drop(['isDefault'],axis=1)

# 查看缺失情况

missing = X_missing.isna().sum()

missing = pd.DataFrame(data={'特征': missing.index,'缺失值个数':missing.values})

#通过~取反,选取不包含数字0的行

missing = missing[~missing['缺失值个数'].isin([0])]

# 缺失比例

missing['缺失比例'] = missing['缺失值个数']/X_missing.shape[0]

missing

'''

特征 缺失值个数 缺失比例

7 employmentTitle 1 0.000001

8 employmentLength 46799 0.058499

14 postCode 1 0.000001

16 dti 239 0.000299

22 pubRecBankruptcies 405 0.000506

24 revolUtil 531 0.000664

29 title 1 0.000001

31 n0 40270 0.050338

32 n1 40270 0.050338

33 n2 40270 0.050338

34 n3 40270 0.050338

35 n4 33239 0.041549

36 n5 40270 0.050338

37 n6 40270 0.050338

38 n7 40270 0.050338

39 n8 40271 0.050339

40 n9 40270 0.050338

41 n10 33239 0.041549

42 n11 69752 0.087190

43 n12 40270 0.050338

44 n13 40270 0.050338

45 n14 40270 0.050338

'''

AI写代码python

运行

12345678910111213141516171819202122232425262728293031323334353637

可以看到employmentTitle、employmentLength、dti 以及匿名特征等字段存在缺省值,

从上面的结果可以看出train数据集中的47个字段有22个存在缺省值的情况。下面可视化一下缺省值数量占比。

一般对于缺失值,需要进行横纵对比

纵向(从列方向):如果nan存在的过多,说明这一列对label的影响几乎不起作用了,可以考虑删掉。如果缺失值很小一般可以选择填充。比如占到总数的50%,理论上对分析作用不大,这样就可以省略该字段。

横向(从行方向):如果在数据集中,某些样本数据的大部分列都是缺失的且样本足够的情况下可以考虑删除。

注意:对于一些模型,可以自动处理缺失值,例如 lightgbm 模型

# 可视化

(train.isnull().sum()/len(train)).plot.bar(figsize = (20,6),color=['#d6ecf0','#a3d900','#88ada6','#ffb3a7','#cca4e3','#a1afc9'])

AI写代码python

运行

12

缺失特征可视化呈现 可以看到,所有的特征缺失值都在10%以内,这里考虑全部保留。

总结

47列数据中有22列都缺少少量数据,存在一个唯一值特征 ‘policyCode’

2.5 数据相关关系

f, ax = plt.subplots(1,1, figsize = (20,20))

cor = train[numerical_feature].corr()

sns.heatmap(cor, annot = True, linewidth = 0.2, linecolor = "white", ax = ax, fmt =".1g" )

AI写代码python

运行

123

可以看到,有些变量之间的相关性还是很强的,比如贷款总额loanAmnt 和分期付款金额installment 相关性为1,ficoRangeLow he ficoRangeHigh 相关性为1…,这种情况后面再特征选择时考虑删除。

三、特征工程

基本的EDA探索完成后(还有一些可以继续探索),就可以进行特征工程啦,在数据挖掘中,大部分时间都是在做特征工程

特征工程包括数据预处理、缺失值以及异常值的处理、数据分桶处理以及特征交互、编码、选择

a) 数据清洗

数据清洗主要的目的是提取原始数据中的噪音部分。(重复数据、异常数据、缺失数据等)

b)特征预处理(Feature Prepossess)

特征预处理的目的是将数据的原始字段进行相应的编码(定性:独热编码、哈希编码等;

定量:取整、截断、二值化、分箱、放缩)、

变换(归一化、标准化、正态化),并进行缺失值的处理(插值、均值、中位数、众数、删除);

c)特征提取(Feature Extraction)

特征提取的目的是从原始数据中提取出心的特征字段,并将特征转换成特定的格式;

d)特征筛选(Feature Selection)

特征筛选的目的是筛选出较优的特征子集,以取得较好的泛化性能;

AI写代码sql

12345678910

引用 贷款违约预测3-特征工程 观点:

3.1 重复值处理

重复值

train.duplicated().sum()

AI写代码sql

1

0

3.2 缺失值填补

在比赛中数据预处理是必不可少的一部分,对于缺失值的填充往往会影响比赛的结果。

缺失值的处理,请参见 【缺失值处理】拉格朗日插值法—随机森林算法填充—sklearn填充(均值/众数/中位数)

传统地,

如果是分类型特征,采用众数进行填补。如果是连续型特征,采用均值进行填补。

还要考虑

均值一般适用于近似正态分布数据,观测值较为均匀散布均值周围;中位数一般适用于偏态分布或者有离群点数据,中位数是更好地代表数据中心趋势;众数一般用于类别变量,无大小、先后顺序之分。

所以对于连续变量

对于数据近似符合正态分布,用该变量的均值填补缺失。对于数据存在偏态分布的情况,采用中位数进行填补。

首先剔除标签列

label = 'isDefault'

Y_label = train['isDefault']

numerical_feature.remove(label)

AI写代码python

运行

123

数值型特征(连续型和离散型)用中位数填补(这里为了方便,都用中位数填补)

# 训练集

train[numerical_feature] = train[numerical_feature].fillna(train[numerical_feature].median())

# 测试集

test[numerical_feature] = test[numerical_feature].fillna(train[numerical_feature].median())

AI写代码python

运行

1234

分类型特征用众数填补

分类型特征查看

train[category_feature]

'''

grade subGrade employmentLength issueDate earliesCreditLine

0 E E2 2 years 2014-07-01 Aug-2001

1 D D2 5 years 2012-08-01 May-2002

2 D D3 8 years 2015-10-01 May-2006

3 A A4 10+ years 2015-08-01 May-1999

4 C C2 NaN 2016-03-01 Aug-1977

... ... ... ... ...

799995 C C4 7 years 2016-07-01 Aug-2011

799996 A A4 10+ years 2013-04-01 May-1989

799997 C C3 10+ years 2015-10-01 Jul-2002

799998 A A4 10+ years 2015-02-01 Jan-1994

799999 B B3 5 years 2018-08-01 Feb-2002

[800000 rows x 5 columns]

'''

AI写代码python

运行

1234567891011121314151617

填补

# 训练集

train[category_feature] = train[category_feature].fillna(train[category_feature].mode())

# 测试集

test[category_feature] = test[category_feature].fillna(train[category_feature].mode())

AI写代码python

运行

1234

填补之后,再次查看缺失值情况

train.isnull().sum()

'''

id 0

loanAmnt 0

term 0

interestRate 0

installment 0

grade 0

subGrade 0

employmentTitle 0

employmentLength 46799

homeOwnership 0

annualIncome 0

verificationStatus 0

issueDate 0

isDefault 0

purpose 0

postCode 0

regionCode 0

dti 0

delinquency_2years 0

ficoRangeLow 0

ficoRangeHigh 0

openAcc 0

pubRec 0

pubRecBankruptcies 0

revolBal 0

revolUtil 0

totalAcc 0

initialListStatus 0

applicationType 0

earliesCreditLine 0

title 0

policyCode 0

n0 0

n1 0

n2 0

n3 0

n4 0

n5 0

n6 0

n7 0

n8 0

n9 0

n10 0

n11 0

n12 0

n13 0

n14 0

issueDateDT 0

dtype: int64

'''

AI写代码python

运行

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253

可以看到,employmentLength 列还存在缺失值

特征 缺失值个数 缺失比例

8 employmentLength 46799 0.058499

AI写代码python

运行

12

train.employmentLength

'''

0 2 years

1 5 years

2 8 years

3 10+ years

4 NaN

799995 7 years

799996 10+ years

799997 10+ years

799998 10+ years

799999 5 years

Name: employmentLength, Length: 800000, dtype: object

'''

AI写代码python

运行

123456789101112131415

采用决策树来填补就业年限(employmentLength)

from sklearn.tree import DecisionTreeClassifier

empLenNotNullInd = train.employmentLength.notnull() # 不是空的行,返回True

columns = ['postCode','regionCode','employmentTitle','annualIncome'] # 用四个特征来预测employmentLength

train_empLen_X = train.loc[empLenNotNullInd,columns]

train_empLen_y = train.employmentLength[empLenNotNullInd]

DTC = DecisionTreeClassifier() # 实例化

DTC.fit(train_empLen_X ,train_empLen_y) # 训练

print(DTC.score(train_empLen_X ,train_empLen_y))# 0.9809320486828881

AI写代码sql

12345678910

# 预测

for data in [train,test]:

empLenIsNullInd = data.employmentLength.isnull()

test_empLen_X = data.loc[empLenIsNullInd,columns]

empLen_pred = DTC.predict(test_empLen_X)

data.employmentLength[empLenIsNullInd] = empLen_pred

AI写代码python

运行

123456

填补完毕

train.isnull().any().sum()

AI写代码sql

1

0

train['employmentLength'][:20]

'''

0 2 years

1 5 years

2 8 years

3 10+ years

4 5 years

5 7 years

6 9 years

7 1 year

8 5 years

9 6 years

10 10+ years

11 3 years

12 2 years

13 10+ years

14 2 years

15 2 years

16 9 years

17 < 1 year

18 10+ years

19 9 years

Name: employmentLength, dtype: object

'''

AI写代码python

运行

123456789101112131415161718192021222324

train['employmentLength'].value_counts(dropna=False).sort_index()

'''

1 year 55034

10+ years 276853

2 years 76435

3 years 68888

4 years 50893

5 years 54038

6 years 39517

7 years 37175

8 years 37903

9 years 31463

< 1 year 71801

Name: employmentLength, dtype: int64

'''

AI写代码py

123456789101112131415

总结

如果采用 XGBoost(LightGBM) 模型,能够自动处理缺失值的模型,这样就无需处理缺失值;如果缺失值较多,可以直接删除如果发现某列特征信息可能是因为某种特定原因导致的,而不是随机缺失,可以考虑在特征工程时利用该假设来构架新特征。

3.3 异常值处理

异常值的存在很可能会影响模型的最终结果,但是当我们发现异常值的时候也不能马上就删除,应该先看看这个异常值是不是有特殊原因造成的,特别是在金融风控问题中,异常值的出现往往是存在意义的。

如果不是因为特殊原因造成的,可以先观察这个异常值出现的频率

若异常值只出现了一次,多半是输入错误,直接把异常值删除即可

若异常值出现了多次,可以和业务人员沟通,可能这是某种特殊表示,如果是人为造成的错误,留着是没有用,只要数据量不是太大,都可以删除

若异常值占到总数据量的10%以上,不能轻易删除。可以考虑把异常值替换成非异常但是非干扰的项,比如说用0来进行替换,或者把异常当缺失值,用均值或者众数来进行替换

通常,在进行EDA的时候会利用描述统计的方法,查看特征的均值、极大值、极小值等信息,结合常识来判断是否存在异常值。

比如,年龄值出现负数,人的身高出现非常大的值等等

除此之外,还有其他判断异常值的方法,例如

3.3.1 方法一:均方差 3σ

在统计学中,如果一个数据分布近似正态,那么大约 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

AI写代码python

运行

12345678

得到特征的异常值后可以进一步分析变量异常值和目标变量的关系

data_train = train.copy()

for fea in numerical_feature:

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)

AI写代码python

运行

123456

3.3.2 方法二:箱型图

箱型图包括上四

这里,没有对异常值进行处理…

3.4 时间数据处理

对于时间数据

对于本赛题,时间数据有 issueDate ,可以将其转化成时间格式(issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数)

首先看一下这个issueDate

train['issueDate']

'''

0 2014-07-01

1 2012-08-01

2 2015-10-01

3 2015-08-01

4 2016-03-01

...

799995 2016-07-01

799996 2013-04-01

799997 2015-10-01

799998 2015-02-01

799999 2018-08-01

Name: issueDate, Length: 800000, dtype: object

'''

AI写代码python

运行

123456789101112131415

train.shape # (800000, 47)

AI写代码python

运行

1

训练集时间数据处理

import datetime

# 转化成时间格式 issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数

train['issueDate'] = pd.to_datetime(train['issueDate'],format='%Y-%m-%d')

startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')

train['issueDateDT'] = train['issueDate'].apply(lambda x: x-startdate).dt.days

train.shape # (800000, 48)

AI写代码python

运行

1234567

查看一下处理效果

train[['issueDate','issueDateDT']]

AI写代码python

运行

1

测试集时间数据处理

#转化成时间格式

test['issueDate'] = pd.to_datetime(train['issueDate'],format='%Y-%m-%d')

startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')

test['issueDateDT'] = test['issueDate'].apply(lambda x: x-startdate).dt.days

AI写代码python

运行

1234

可视化

plt.figure(1 , figsize = (10 , 8))

plt.hist(train['issueDateDT'], label='train');

plt.hist(test['issueDateDT'], label='test');

plt.legend();

plt.title('Distribution of issueDateDT dates');

#train 和 test issueDateDT 日期有重叠 所以使用基于时间的分割进行验证是不明智的

AI写代码python

运行

123456

注:这里issueDate特征先暂时不删除

3.5 特征交叉

这里处理 earliesCreditLine,将利用到 issueDate

这两个特征含义如下

FieldDescriptionissueDate贷款发放的月份earliesCreditLine借款人最早报告的信用额度开立的月份

将issueDate贷款发放时的年份减去借款人最早报告的信用额度开立的年份,得到新的特征,即开卡年限CreditLine

train[['issueDate','earliesCreditLine']]

'''

issueDate earliesCreditLine

0 2014-07-01 Aug-2001

1 2012-08-01 May-2002

2 2015-10-01 May-2006

3 2015-08-01 May-1999

4 2016-03-01 Aug-1977

... ...

799995 2016-07-01 Aug-2011

799996 2013-04-01 May-1989

799997 2015-10-01 Jul-2002

799998 2015-02-01 Jan-1994

799999 2018-08-01 Feb-2002

[800000 rows x 2 columns]

'''

AI写代码python

运行

1234567891011121314151617

参考:https://zhuanlan.zhihu.com/p/255105477

train_earliesCreditLine_year = train['earliesCreditLine'].apply(lambda x:x[-4:]).astype('int64')

test_earliesCreditLine_year = test['earliesCreditLine'].apply(lambda x:x[-4:]).astype('int64')

train_issueDate_year = train['issueDate'].astype('str').apply(lambda x:x[:4]).astype('int64')

test_issueDate_year = test['issueDate'].astype('str').apply(lambda x:x[:4]).astype('int64')

train['CreditLine'] = train_issueDate_year - train_earliesCreditLine_year

test['CreditLine'] = test_issueDate_year - test_earliesCreditLine_year

train = train.drop(['earliesCreditLine','issueDate'],axis=1)

test = test.drop(['earliesCreditLine','issueDate'],axis=1)

AI写代码python

运行

1234567891011

查看处理结果

train['CreditLine']

'''

0 13

1 10

2 9

3 16

4 39

..

799995 5

799996 24

799997 13

799998 21

799999 16

Name: CreditLine, Length: 800000, dtype: int64

'''

AI写代码python

运行

123456789101112131415

train.shape ## (800000, 47)

AI写代码python

运行

1

目前新增两个特征 issueDateDT、CreditLine

earliesCreditLine和issueDate 已经删除

3.6 特征编码

对类别型特征进行转换,使其变为数值特征。具体有以下几种方法:

序号编码:适用于类别间存在大小关系的特征。比如级别高中低,可以对应 321。oneHot 编码:适用于不具有大小关系的特征。比如地名。二进制编码:先给每个类别赋予一个序号 ID,然后对 ID 进行二进制编码,最终得到和 OneHot 类似的 0-1 向量,但是维度更小。

首先将 employmentLength 进行简单的处理,再进行编码

这里将就业年限特征转换为数值(把数字后面的years去掉并且把10+改成10,<1改成0)

def employmentLength_to_int(s):

if pd.isnull(s):

return s

else:

return np.int8(s.split()[0])

for data in [train, test]:

data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)

data['employmentLength'].replace('< 1 year', '0 years', inplace=True)

data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)

AI写代码python

运行

123456789

处理前效果

train['employmentLength'][:20]

'''

0 2 years

1 5 years

2 8 years

3 10+ years

4 5 years

5 7 years

6 9 years

7 1 year

8 5 years

9 6 years

10 10+ years

11 3 years

12 2 years

13 10+ years

14 2 years

15 2 years

16 9 years

17 < 1 year

18 10+ years

19 9 years

Name: employmentLength, dtype: object

'''

AI写代码python

运行

123456789101112131415161718192021222324

处理后效果

train['employmentLength']

'''

0 2

1 5

2 8

3 10

4 5

..

799995 7

799996 10

799997 10

799998 10

799999 5

Name: employmentLength, Length: 800000, dtype: int64

'''

AI写代码python

运行

123456789101112131415

接下来,对其余分类型特征进行编码

像等级grade、subGrade这种类别特征,虽然是表示类别的数据,但是信用评级是有高低大小之分的,是有优先级的,所以可以直接自映射,转化为数值类型。(也可以使用labelencode编码)

a2z = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

a2z_code = np.arange(1,27)

a2z_mapping = dict(zip(a2z, a2z_code))

for data in [train,test]:

data.loc[:,['grade','subGrade']] = data.loc[:,['grade','subGrade']].applymap(lambda g:g.replace(g[0], str(a2z.index(g[0])+1))).astype('int')

AI写代码python

运行

123456

编码前结果

train[['grade','subGrade']]

AI写代码python

运行

1

编码后结果

train[['grade','subGrade']]

AI写代码python

运行

1

对于离散型特征,可以使用OneHotEncoder独热编码

要处理的特征有

train[['homeOwnership','verificationStatus','purpose']]

'''

homeOwnership verificationStatus purpose

0 2 2 1

1 0 2 0

2 0 2 0

3 1 1 4

4 1 2 10

... ... ...

799995 1 0 0

799996 0 2 4

799997 1 2 0

799998 0 2 4

799999 0 0 4

[800000 rows x 3 columns]

'''

AI写代码python

运行

1234567891011121314151617

编码之前确定一下特征数

train.shape# (800000, 47)

AI写代码python

运行

1

独热编码

from sklearn.preprocessing import OneHotEncoder

oh = OneHotEncoder(sparse=False)

oh.fit(train[['homeOwnership','verificationStatus','purpose']])

OneHot1 = oh.transform(train[['homeOwnership','verificationStatus','purpose']])

OneHot2 = oh.transform(test[['homeOwnership','verificationStatus','purpose']])

OneHot1.shape# (800000, 23)

'''

array([[0., 0., 1., ..., 0., 0., 0.],

[1., 0., 0., ..., 0., 0., 0.],

[1., 0., 0., ..., 0., 0., 0.],

...,

[0., 1., 0., ..., 0., 0., 0.],

[1., 0., 0., ..., 0., 0., 0.],

[1., 0., 0., ..., 0., 0., 0.]])

'''

train = pd.concat([train, pd.DataFrame(OneHot1)], axis=1)

test = pd.concat([test, pd.DataFrame(OneHot2)], axis=1)

train = train.drop(['homeOwnership','verificationStatus','purpose'],axis=1)

test = test.drop(['homeOwnership','verificationStatus','purpose'],axis=1)

train.shape# (800000, 67)

AI写代码python

运行

123456789101112131415161718192021222324

3.7 数据分桶

引用 Datawhale零基础入门金融风控 Task3 特征工程 观点

常见的分箱方法有:

固定宽度分箱

# 通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000

train['loanAmnt_bin1'] = np.floor_divide(train['loanAmnt'], 1000)

## 通过对数函数映射到指数宽度分箱

train['loanAmnt_bin2'] = np.floor(np.log10(train['loanAmnt']))

AI写代码python

运行

1234

分位数分箱

train['loanAmnt_bin3'] = pd.qcut(train['loanAmnt'], 10, labels=False)

AI写代码python

运行

1

方分箱及其他分箱方法的尝试

注:这里没有进行分箱

3.8 特征交互

参见引用文章

引用 Datawhale零基础入门金融风控 Task3 特征工程 观点

额,这里也懒得弄

3.9 特征选择

引用图片:https://blog.csdn.net/qq_38366112/article/details/114996847

1.人工判断与目标无关联特征为"id",需删除

train=train.drop(["id"],axis=1)

train.shape # (800000, 66)

AI写代码python

运行

12

test=test.drop(["id"],axis=1)

test.shape # (200000, 65)

AI写代码python

运行

12

2.求出各个特征与目标的相关系数,综合考虑排除corr小于0.01的特征

train.corr()["isDefault"].sort_values()

AI写代码sql

1

train=train.drop(["initialListStatus","n5","n11","n12","n8","postCode","policyCode"],axis=1)

test=test.drop(["initialListStatus","n5","n11","n12","n8","postCode","policyCode"],axis=1)

train.shape# (800000, 59)

AI写代码python

运行

1234

删除特征"initialListStatus",“n5”,“n11”,“n12”,“n8”,“postCode”,“policyCode”,剩余59个特征

3.特征间高相关过滤

两两特征之间高于0.6的

# 显示相关性高于0.6的变量

def getHighRelatedFeatureDf(corr_matrix, corr_threshold):

highRelatedFeatureDf = pd.DataFrame(corr_matrix[corr_matrix>corr_threshold].stack().reset_index())

highRelatedFeatureDf.rename({'level_0':'feature_x', 'level_1':'feature_y', 0:'corr'}, axis=1, inplace=True)

highRelatedFeatureDf = highRelatedFeatureDf[highRelatedFeatureDf.feature_x != highRelatedFeatureDf.feature_y]

highRelatedFeatureDf['feature_pair_key'] = highRelatedFeatureDf.loc[:,['feature_x', 'feature_y']].apply(lambda r:'#'.join(np.sort(r.values)), axis=1)

highRelatedFeatureDf.drop_duplicates(subset=['feature_pair_key'],inplace=True)

highRelatedFeatureDf.drop(['feature_pair_key'], axis=1, inplace=True)

return highRelatedFeatureDf

getHighRelatedFeatureDf(train.corr(),0.6)

'''

feature_x feature_y corr

2 loanAmnt installment 0.953369

5 interestRate grade 0.953269

6 interestRate subGrade 0.970847

11 grade subGrade 0.993907

22 delinquency_2years n13 0.658946

24 ficoRangeLow ficoRangeHigh 1.000000

28 openAcc totalAcc 0.700796

29 openAcc n2 0.658807

30 openAcc n3 0.658807

31 openAcc n4 0.618207

32 openAcc n7 0.830624

33 openAcc n8 0.646342

34 openAcc n9 0.660917

35 openAcc n10 0.998717

37 pubRec pubRecBankruptcies 0.644402

44 totalAcc n5 0.623639

45 totalAcc n6 0.678482

46 totalAcc n8 0.761854

47 totalAcc n10 0.697192

53 n1 n2 0.807789

54 n1 n3 0.807789

55 n1 n4 0.829016

56 n1 n7 0.651852

57 n1 n9 0.800925

61 n2 n3 1.000000

62 n2 n4 0.663186

63 n2 n7 0.790337

64 n2 n9 0.982015

65 n2 n10 0.655296

70 n3 n4 0.663186

71 n3 n7 0.790337

72 n3 n9 0.982015

73 n3 n10 0.655296

79 n4 n5 0.717936

80 n4 n7 0.742157

81 n4 n9 0.639867

82 n4 n10 0.614658

86 n5 n7 0.618970

87 n5 n8 0.838066

97 n7 n8 0.774955

98 n7 n9 0.794465

99 n7 n10 0.829799

105 n8 n10 0.640729

113 n9 n10 0.660395

'''

AI写代码python

运行

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859

结果分析:

1) "loanAmnt"贷款金额,"installment"分期付款金额两个特征间相关系数为0.95

2)"ficoRangeLow"fico所属的下限范围,"ficoRangeHigh"fico所属的上限范围两个特征间相关系数为1

3)"openAcc"未结信用额度的数量,“n10” 两个特征间相关系数为0.93

4)“n3”,"n2"两个特征间相关系数为1;“n3”,“n9” 两个特征间相关系数为0.98

根据高相关特征,综合考虑他们与目标的相关性,删除特征"installment",“ficoRangeHigh”,“openAcc”,“n3”,“n9”

col = ['installment','ficoRangeHigh','openAcc','n3','n9']

for data in [train,test]:

data.drop(col,axis=1,inplace=True)

AI写代码python

运行

123

train.shape # (800000, 54)

AI写代码python

运行

1

其余高相关的特征可以使用PCA进行降维处理 (参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session)

注:这里不处理了

4.低方差过滤

train.var().sort_values()

AI写代码python

运行

1

结合相关性过滤方差小于0.1的特征"applicationType"

col = ['applicationType']

for data in [train,test]:

data.drop(col,axis=1,inplace=True)

AI写代码python

运行

123

train.shape # (800000, 53)

AI写代码python

运行

1

总结

特征选择中对高相关性的特征进行删除、PCA降维,处理的可能不太合适,可尝试使用过滤法、包装法、嵌入法等特征选择方法进行特征的筛选

3.10 样本不平衡处理

若分类问题中各类别样本数量差距太大,则会造成样本不均衡的问题。样本不均衡不利于建立与训练出正确的模型,且不能做出合理的评估。

label.value_counts()/len(label)

'''

0 0.800488

1 0.199513

Name: isDefault, dtype: float64

'''

AI写代码python

运行

123456

1.上采样

过抽样(也叫上采样、over-sampling)方法通过增加分类中少数类样本的数量来实现样本均衡,最直接的方法是简单复制少数类样本形成多条记录

参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session

import imblearn

from imblearn.over_sampling import SMOTE

over_samples = SMOTE(random_state=1234)

train_over,label_over = over_samples.fit_sample(train, label)

train_over.to_csv('train_over.csv',index=False)

label_over.to_csv('label_over.csv',index=False)

print(label_over.value_counts()/len(label_over))

print(train_over.shape)

AI写代码python

运行

12345678910

2.下采样

欠抽样(也叫下采样、under-sampling)方法通过减少分类中多数类样本的样本数量来实现样本均衡,最直接的方法是随机地去掉一些多数类样本来减小多数类的规模

参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session

from imblearn.under_sampling import RandomUnderSampler

under_samples = RandomUnderSampler(random_state=1234)

train_under, label_under = under_samples.fit_sample(train,label)

train_under.to_csv('train_under.csv',index=False)

label_under.to_csv('label_under.csv',index=False)

print(label_under.value_counts()/len(label_under))

print(train_under.shape)

AI写代码python

运行

123456789

注:这里没有进行采样,如果做了可以分别利用上采样后的数据跑模型和下采样后的数据跑模型

四、建模分析

在完成相关的特征处理后,接下来进行建模分析,通过调节参数得到性能更强的模型

4.1 LightGBM

参考:https://zhuanlan.zhihu.com/p/256310383

X = train.drop(['isDefault'], axis=1)

y = train.loc[:,'isDefault']

kf = KFold(n_splits=5, shuffle=True, random_state=525)

X_train_split, X_val, y_train_split, y_val = train_test_split(X, y, test_size=0.2)

AI写代码python

运行

12345

使用5折交叉验证法对数据进行验证和训练

import lightgbm as lgb

cv_scores = []

for i, (train_index, val_index) in enumerate(kf.split(X, y)):

X_train, y_train, X_val, y_val = X.iloc[train_index], y.iloc[train_index], X.iloc[val_index], y.iloc[val_index]

train_matrix = lgb.Dataset(X_train, label=y_train)

valid_matrix = lgb.Dataset(X_val, label=y_val)

params = {

'boosting_type': 'gbdt',

'objective': 'binary',

'learning_rate': 0.1,

'metric': 'auc',

'min_child_weight': 1e-3,

'num_leaves': 31,

'max_depth': -1,

'seed': 525,

'nthread': 8,

'silent': True,

}

model = lgb.train(params, train_set=train_matrix, num_boost_round=20000, valid_sets=valid_matrix, verbose_eval=1000, early_stopping_rounds=200)

val_pred = model.predict(X_val, num_iteration=model.best_iteration)

cv_scores.append(roc_auc_score(y_val, val_pred))

print(cv_scores)

print("lgb_scotrainre_list:{}".format(cv_scores))

print("lgb_score_mean:{}".format(np.mean(cv_scores)))

print("lgb_score_std:{}".format(np.std(cv_scores)))

AI写代码python

运行

123456789101112131415161718192021222324252627282930

lgb_scotrainre_list:[0.7303837315833632, 0.7258692125145638, 0.7305149209921737, 0.7296117869375041, 0.7294438695369077] lgb_score_mean:0.7291647043129024 lgb_score_std:0.0016998349834934656

ROC曲线

from sklearn import metrics

from sklearn.metrics import roc_auc_score

al_pre_lgb = model.predict(X_val, num_iteration=model.best_iteration)

fpr, tpr, threshold = metrics.roc_curve(y_val, val_pred)

roc_auc = metrics.auc(fpr, tpr)

print('AUC:{}'.format(roc_auc))

plt.figure(figsize=(8, 8))

plt.title('Validation ROC')

plt.plot(fpr, tpr, 'b', label = 'Val AUC = %0.4f' % roc_auc)

plt.ylim(0,1)

plt.xlim(0,1)

plt.legend(loc='best')

plt.title('ROC')

plt.ylabel('True Positive Rate')

plt.xlabel('False Positive Rate')

# 画出对角线

plt.plot([0,1],[0,1],'r--')

plt.show()

AI写代码python

运行

1234567891011121314151617181920

AUC得分为0.7338

4.2 XGBoost

X = train.drop(['isDefault'], axis=1)

y = train.loc[:,'isDefault']

Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3)

AI写代码python

运行

1234

用XGBClassifier模型跑一下(具体的xgboost 参数设置可以参考官网)

from xgboost.sklearn import XGBClassifier

clf1 = XGBClassifier(n_jobs=-1)

clf1.fit(Xtrain,Ytrain)

clf1.score(Xtest,Ytest)

AI写代码python

运行

1234

0.8068791666666667

计算模型结构的AUC面积

from sklearn.metrics import roc_curve, auc

predict_proba = clf1.predict_proba(Xtest)

false_positive_rate, true_positive_rate, thresholds = roc_curve(Ytest, predict_proba[:,1])

auc(false_positive_rate, true_positive_rate)

AI写代码python

运行

12345

0.7326304866618416

4.3 三个模型比较

gra=GradientBoostingClassifier()

xgb=XGBClassifier()

lgb=LGBMClassifier()

models=[gra,xgb,lgb]

model_names=["gra","xgb","lgb"]

#交叉验证看看上述3个算法评分

for i,model in enumerate(models):

score=cross_val_score(model,X,y,cv=5,scoring="accuracy",n_jobs=-1)

print(model_names[i],np.array(score).round(3),round(score.mean(),3))

AI写代码python

运行

12345678910

其他建模方法

参见:

金融风控-贷款违约预测 数据竞赛入门-金融风控(贷款违约预测)四、建模与调参

以及

数据挖掘实践(金融风控-贷款违约预测)(四):建模与调参

尝试多种模型

五、模型调参

5.1 调参方法

(1)贪心调参

参考:https://www.jianshu.com/p/cdf0a9ffec6f

(2)网格搜索

参考:https://www.jianshu.com/p/cdf0a9ffec6f

sklearn 提供GridSearchCV用于进行网格搜索,只需要把模型的参数输进去,就能给出最优化的结果和参数。相比起贪心调参,网格搜索的结果会更优,但是网格搜索只适合于小数据集,一旦数据的量级上去了,很难得出结果。

(3)贝叶斯调参

参考:https://www.jianshu.com/p/cdf0a9ffec6f

贝叶斯调参的主要思想是:给定优化的目标函数(广义的函数,只需指定输入和输出即可,无需知道内部结构以及数学性质),通过不断地添加样本点来更新目标函数的后验分布(高斯过程,直到后验分布基本贴合于真实分布)。简单的说,就是考虑了上一次参数的信息,从而更好的调整当前的参数。

5.2 XGboost调参

参考:https://zhuanlan.zhihu.com/p/255105477?utm_source=wechat_session

1.优化max_depth,min_child_weight

from xgboost.sklearn import XGBClassifier

from sklearn.model_selection import GridSearchCV

# 其余参数

other_params = {'learning_rate': 0.1,

'n_estimators': 100,

'max_depth': 5,

'min_child_weight': 1,

'seed': 0,

'subsample': 0.8,

'colsample_bytree': 0.8,

'gamma': 0,

'reg_alpha': 0,

'reg_lambda': 1}

# 待调参数

param_test1 = {

'max_depth':list(range(4,9,2)),

'min_child_weight':list(range(1,6,2))

}

xgb1 = XGBClassifier(**other_params)

# 网格搜索

gs1 = GridSearchCV(xgb1,param_test1,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)

best_model1=gs1.fit(Xtrain,Ytrain)

print('最优参数:',best_model1.best_params_)

print('最佳模型得分:',best_model1.best_score_)

AI写代码python

运行

123456789101112131415161718192021222324252627

最优参数:{‘max_depth’:4,,‘min-childweight’:5} 最佳模型得分:0.7185495198261862

2.优化gamma参数

other_params = {'learning_rate': 0.1,

'n_estimators': 100,

'max_depth': 4,

'min_child_weight': 5,

'seed': 0,

'subsample': 0.8,

'colsample_bytree': 0.8,

'gamma': 0,

'reg_alpha': 0,

'reg_lambda': 1}

param_test = {

'gaama':[0,0.05,0.1,0.2,0.3]

}

xgb = XGBClassifier(**other_params)

gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)

best_model=gs.fit(Xtrain,Ytrain)

print('最优参数:',best_model.best_params_)

print('最佳模型得分:',best_model.best_score_)

AI写代码python

运行

123456789101112131415161718192021

最优参数:{‘gaama’:0} 最模得分:0.7185495198261862

3.subsample和colsample_bytree

other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0,

'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}

param_test = {

'subsample':[0.6,0.7,0.8,0.9],

'colsample_bytree':[0.6,0.7,0.8,0.9]

}

xgb = XGBClassifier(**other_params)

gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)

best_model=gs.fit(Xtrain,Ytrain)

print('最优参数:',best_model.best_params_)

print('最佳模型得分:',best_model.best_score_)

AI写代码python

运行

12345678910111213

最优参数:{‘colsample-bytree’:0.7,‘subsample’:0.7} 最佳模得分:0.7187964885978947

4.reg_alpha和reg_lambda

other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0,

'subsample': 0.7, 'colsample_bytree': 0.7, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}

param_test = {

'reg_alpha': [4,5,6,7],

'reg_lambda': [0,0.01,0.05, 0.1]

}

xgb = XGBClassifier(**other_params)

gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)

best_model=gs.fit(Xtrain,Ytrain)

print('最优参数:',best_model.best_params_)

print('最佳模型得分:',best_model.best_score_)

AI写代码python

运行

12345678910111213

最优参数:{‘reg-alpha’:5,‘reg-lambda’:0.01} 最佳模型得分:0.7194153615536154

5. learning_rate和n_estimators

other_params = {'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 4, 'min_child_weight': 5, 'seed': 0,

'subsample': 0.7, 'colsample_bytree': 0.7, 'gamma': 0, 'reg_alpha': 5, 'reg_lambda': 0.01}

param_test = {

'learning_rate': [0.01, 0.05, 0.07, 0.1, 0.2],

'n_estimators': [100,200,300,400,500]

}

xgb = XGBClassifier(**other_params)

gs = GridSearchCV(xgb,param_test,cv = 5,scoring = 'roc_auc',n_jobs = -1,verbose=2)

best_model=gs.fit(Xtrain,Ytrain)

print('最优参数:',best_model.best_params_)

print('最佳模型得分:',best_model.best_score_)

AI写代码python

运行

12345678910111213

最优参数:{‘learning-rate’:0.05,‘n-estimators’:400} 最佳模型得分:0.7207082359918353

通过调参后的最终模型

from xgboost.sklearn import XGBClassifier

clf = XGBClassifier(

learning_rate= 0.05,

n_estimators= 400,

max_depth= 4,

min_child_weight= 5,

seed= 0,

subsample= 0.7,

colsample_bytree= 0.7,

gamma= 0,

reg_alpha= 5,

reg_lambda=0.01,

n_jobs = -1)

clf.fit(Xtrain,Ytrain)

clf.score(Xtest,Ytest)

AI写代码python

运行

12345678910111213141516

0.80934521

AUC面积

from sklearn.metrics import roc_curve, auc

predict_proba = clf.predict_proba(Xtest)

false_positive_rate, true_positive_rate, thresholds = roc_curve(Ytest, predict_proba[:,1])

auc(false_positive_rate, true_positive_rate)

AI写代码python

运行

12345

0.74512067

这里做完后,还可以得出特征重要性

from xgboost import plot_importance

plot_importance(clf)

plt.show()

AI写代码python

运行

123

总结

调参过程综合要点:

(1)"n_estimators"基分类器数量越大,偏差越小,但时间有限,这里初步可选30

(2)"max_depth"越大偏差越小,方差越大,需综合考虑时间及拟合性

(3)"learning_rate"学习速率一般越小越好,只是耗时会更长

(4)"subsample"采样比例一般在[0.5,0.8]之间比较好

六、模型融合

模型融合是比赛后期上分的重要手段,模型融合后结果会有大幅提升,以下是模型融合的方式。

【机器学习】模型融合方法概述

1)平均法(Averaging)-针对回归问题

2)投票法(Voting)- 针对分类问题

简单投票法

加权投票法

硬投票法

模型 1:A - 99%、B - 1%,表示模型 1 认为该样本是 A 类型的概率为 99%,为 B 类型的概率为 1%

python实现

from xgboost import XGBClassifier

from sklearn.linear_model import LogisticRegression

from sklearn.ensemble import RandomForestClassifier

from sklearn.ensemble import VotingClassifier

from sklearn.model_selection import train_test_split,cross_val_score #划分数据 交叉验证

clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7,

colsample_bytree=0.6, objective='binary:logistic')

clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4,

min_samples_leaf=63,oob_score=True)

clf3 = SVC(C=0.1)

# 硬投票

eclf = VotingClassifier(estimators=[

('xgb', clf1),

('rf', clf2),

('svc', clf3)], voting='hard')

# 比较模型融合效果

for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Ensemble']):

scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy')

print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

AI写代码python

运行

123456789101112131415161718192021

软投票法

将所有模型预测样本为某一类别的概率的平均值作为标准

from xgboost import XGBClassifier

from sklearn.linear_model import LogisticRegression

from sklearn.ensemble import RandomForestClassifier

from sklearn.ensemble import VotingClassifier

from sklearn.model_selection import train_test_split,cross_val_score #划分数据 交叉验证

clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7,

colsample_bytree=0.6, objective='binary:logistic')

clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4,

min_samples_leaf=63,oob_score=True)

clf3 = SVC(C=0.1)

# 软投票

eclf = VotingClassifier(estimators=[

('xgb', clf1),

('rf', clf2),

('svc', clf3)],

voting='soft', weights=[2, 1, 1])

# 比较模型融合效果

for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Ensemble']):

scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy')

print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

AI写代码python

运行

12345678910111213141516171819202122

3)综合法

排序融合log融合

4)stacking/blending:

stacking(构建多层模型,并利用预测结果再拟合预测)blending(选取部分数据预测训练得到预测结果作为新特征,带入剩下的数据中预测。Blending只有一层,而Stacking有多层)

5) boosting/bagging

多树的提升方法,在xgboost,Adaboost,GBDT中已经用到

介绍完上述方法之后,回到赛题

这里使用之前的训练的lgb和xgb模型作为基分类器,逻辑回归作为目标分类器做stacking

from mlxtend.classifier import StackingClassifier

gra=GradientBoostingClassifier()

xgb=XGBClassifier()

lgb=LGBMClassifier()

lr = LogisticRegression()

sclf = StackingClassifier(classifiers=[gra, xgb, lgb],

use_probas=True,

meta_classifier=lr)

sclf.fit(Xtrain,Ytrain)

pre =sclf.predict_proba(Xtest)[:,1]

fpr, tpr, thresholds = roc_curve(Ytest, pre)

score = auc(fpr, tpr)

print(score)

AI写代码python

运行

123456789101112131415

总结

简单平均和加权平均是常用的两种比赛中模型融合的方式。其优点是快速、简单。stacking融合速度非常慢,同时stacking多层提升幅度并不能抵消其带来的时间和内存消耗,所以实际环境中应用还是有一定的难度。

七 结果部署

a) 预测评估数据集(通过验证数据集来验证被优化过的模型) b) 利用整个数据集生产模型(通过整个数据集来生成模型) c) 序列化模型(将模型序列化,以便于预测新数据)

当有新数据产生时,就可以采用这个模型来预测新数据。

至此 2021.3.29

Copyright © 2088 世界杯美洲预选赛赛程|世界杯转播|阿利亚昆坦西世界杯财经视角站|ahliakuntansi.com All Rights Reserved.
友情链接
top