承接上文,上次说到我们要开始做一个完整的数据竞赛项目,并且在前文中具体阐述了该赛题的具体任务和数据基本预览。(小伙伴可以通过该链接直达:https://www.jianshu.com/p/ae4f917788e5)
接下来,我们就来到了该项目的第二部分——探索性数据分析,即EDA,Exploratory Data Analysis。
一般来说,数据描述性统计分析是一个数据挖掘/机器学习项目的第一步,有时候也被称为探索性数据分析。但两者有一些细微的区别:
- 数据描述统计强调方法,即如何从数据中获取信息。比如用平均数/中位数/众数/方差等描述城市的人员收入水平。
- 数据探索强调过程,即通过对数据进行研究发现其规律,对研究的对象有更加深入的认识。例如通过对不同人员的工作年限行业教育水平和薪资的关系,找到影响收入的因素等
好了,接下来就开展的工作,主要是探索赛题提供的数据,并总结一些分析中的套路。
主要包括:
- 查看数据基本情况,包括缺失值和唯一值等
- 深入数据-查看数据类型,包括:
- 类别型数据
- 数值型数据
- 离散数值型数据
- 连续数值型数据
- 数据间相关关系
- 特征和特征之间关系
- 特征和目标变量之间关系
1. 缺省值查看
首先还是万年不变的导包+导数据!
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
import warnings
warnings.filterwarnings('ignore')
train = pd.read_csv('./train.csv')
test = pd.read_csv('./testA.csv')
多学一招
我们知道python在运行的时候有时会因为版本等问题抛出警告,虽然不影响程序运行,但是影响心情!可以用“warnings.filterwarnings('ignore')”忽略掉警告。
然后就是导入数据,并使用shape/info/describe等查看数据的基本情况。这个在这里不在赘述。在这里我们主要看一下缺省值的情况
先看一下数据的前五行和最后五行
#查看前五行和后五行
train.head(5).append(train.tail(5))
小技巧
将head和tail用append方法拼接起来一句代码就可以打印出来,省事!
下面查看一下哪些字段有缺省值
# 查看缺省值
d = (train.isnull().sum()/len(train)).to_dict()
print(d)
print('*'*30)
print(f'There are {train.isnull().any().sum()} columns in train dataset with missing values.')
>>>{'id': 0.0, 'loanAmnt': 0.0, 'term': 0.0, 'interestRate': 0.0, 'installment': 0.0, 'grade': 0.0, 'subGrade': 0.0, 'employmentTitle': 1.25e-06, 'employmentLength': 0.05849875, 'homeOwnership': 0.0, 'annualIncome': 0.0, 'verificationStatus': 0.0, 'issueDate': 0.0, 'isDefault': 0.0, 'purpose': 0.0, 'postCode': 1.25e-06, 'regionCode': 0.0, 'dti': 0.00029875, 'delinquency_2years': 0.0, 'ficoRangeLow': 0.0, 'ficoRangeHigh': 0.0, 'openAcc': 0.0, 'pubRec': 0.0, 'pubRecBankruptcies': 0.00050625, 'revolBal': 0.0, 'revolUtil': 0.00066375, 'totalAcc': 0.0, 'initialListStatus': 0.0, 'applicationType': 0.0, 'earliesCreditLine': 0.0, 'title': 1.25e-06, 'policyCode': 0.0, 'n0': 0.0503375, 'n1': 0.0503375, 'n2': 0.0503375, 'n3': 0.0503375, 'n4': 0.04154875, 'n5': 0.0503375, 'n6': 0.0503375, 'n7': 0.0503375, 'n8': 0.05033875, 'n9': 0.0503375, 'n10': 0.04154875, 'n11': 0.08719, 'n12': 0.0503375, 'n13': 0.0503375, 'n14': 0.0503375}
******************************
>>>There are 22 columns in train dataset with missing values.
上面的代码逻辑:
- train.isnull()返回一个bool类型,判断哪些值为缺省值。若该处为缺失值,返回True,否则不为缺失值,则返回False
- 在上面的代码上叠加sum,即train.isnull().sum(),可以统计每一列缺省值的个数(True相当于1)
- 在和长度比较,得出每一列缺省值的占比,在之后按照key-value存入一个字典
上面的代码一气呵成,充分体现的python的简洁和优雅
从上面的结果可以看出train数据集中的47个字段有22个存在缺省值的情况。下面可视化一下缺省值数量占比。做这一步是因为如果一个字段存在大量的缺省值,比如占到总数的50%,理论上对分析作用不大,这样就可以省略该字段。
# 可视化
(train.isnull().sum()/len(train)).plot.bar(figsize = (20,6))

可以看到所有的特征缺失值都在10%以内,全部保留。
下面在查看是否有只有一个值的字段
# 查看是否有只有一个值的字段
one_value_fea = [col for col in train.columns if train[col].nunique() <= 1]
one_value_fea_test = [col for col in test.columns if test[col].nunique() <= 1]
print(one_value_fea)
print('---------')
print(one_value_fea_test)
>>>['policyCode']
---------
>>>['policyCode']
总结以上
47列数据中有22列都缺少少量数据,这在现实世界中很正常。‘policyCode’具有一个唯一值(或全部缺失),这项在后期的特征工程中可以干掉,不作为后期机器学习建模的变量。另外有很多连续变量和一些分类变量。下面我们就具体看一下这些变量
2. 特征的数值类型分析和可视化
- 特征一般都是由类别型特征和数值型特征组成,而数值型特征又分为连续型和离散型。
- 类别型特征有时具有非数值关系,有时也具有数值关系。比如‘grade’中的等级A,B,C等,是否只是单纯的分类,还是A优于其他要结合业务判断。
- 数值型特征本是可以直接入模的,但往往风控人员要对其做分箱,转化为WOE编码进而做标准评分卡等操作。从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度。从而使模型更加稳定。
在开始展开分析工作前,我们要在心中明确一下分析的目的: 查找挖掘目标变量贷款违约(isDefault)和其他变量的关系。因而有必要先知道目标值的分布情况。
# 查看目标变量isDefault的分布,做到心中有数
train.isDefault.value_counts()
Out[32]:
0 640390
1 159610
Name: isDefault, dtype: int64
取值为0为否,即没有违约,为1是违约客户
也可以加入normalize参数,查看百分比:
train.isDefault.value_counts(normalize=True)
0 0.800488
1 0.199513
Name: isDefault, dtype: float64
即80.05%的客户没有出现违约,而剩下19.95%(共159610)个客户违约。
2.1 首先统计数值型的字段(特征)
#统计数值型类别的特征(连续型和离散型)
def get_numerical_continus_fea(data,feas):
numerical_continus_fea = []
numerical_discrete_fea = []
for fea in feas:
temp = data[fea].nunique()
# 自定义变量的值的取值个数小于10为离散型变量
if temp <= 10:
numerical_discrete_fea.append(fea)
continue
numerical_continus_fea.append(fea)
return numerical_continus_fea,numerical_discrete_fea
numerical_continus_fea,numerical_discrete_fea = get_numerical_continus_fea(train,numerical_fea)
print('Following features are continous features:')
print(numerical_continus_fea)
print('*' * 30)
print('Following features are discrete features:')
print(numerical_discrete_fea)
>>>Following features are continous features:
['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']
******************************
>>>Following features are discrete features:
['term', 'homeOwnership', 'verificationStatus', 'isDefault', 'initialListStatus', 'applicationType', 'policyCode', 'n11', 'n12']
2.1.1 离散型数值变量
对于离散型的数值型变量,我们可以简单的用value_counts函数统计相应取值的总个数,例如:
train.homeOwnership.value_counts()
>>>
0 395732
1 317660
2 86309
3 185
5 81
4 33
Name: homeOwnership, dtype: int64
加入可视化,可以更加直观。下面用homeOwnership和employmentLength两个特征用柱状图来展示
# 加入可视化,更加直观
plt.style.use('bmh')
plt.figure(figsize=(15, 8))
plt.subplot(121)
sns.barplot(train["homeOwnership"].value_counts(dropna=False).keys(),
train["homeOwnership"].value_counts(dropna=False)
)
plt.title('feature homeOwnership value overview')
plt.subplot(122)
sns.barplot(train["employmentLength"].value_counts(dropna=False)[:20],
train["employmentLength"].value_counts(dropna=False).keys()[:20])
plt.title('feature employmentLength value overview')

2.1.2 连续型数值变量
对于连续型变量,可以通过变量的概率密度分布图查看变量的大体分布情况
# 数值连续型特征概率分布可视化
f = pd.melt(train, value_vars=numerical_continus_fea)
g = sns.FacetGrid(f, col="variable", col_wrap=4, sharex=False, sharey=False)
g = g.map(sns.distplot, "value")

做这一步的目的是查看某一个数值型变量的分布,观察该变量是否符合正态分布,如果不符合正太分布的变量可以log化后再观察下是否符合正态分布。
正态化的原因:一些情况下正态非正态可以让模型更快的收敛,一些模型要求数据正态(eg. GMM、KNN),保证数据不要过偏态即可,过于偏态可能会影响模型预测结果。
下面就loanAmnt这个变量举例,看看log后的分布:
#loanAmount Values Distribution and after log
plt.figure(figsize=(20,12))
plt.suptitle('loanAmount Values Distribution', fontsize=22)
plt.subplot(221)
sub_plot_1 = sns.distplot(train['loanAmnt'])
sub_plot_1.set_title("loanAmnt Distribuition", fontsize=18)
sub_plot_1.set_xlabel("")
sub_plot_1.set_ylabel("Probability", fontsize=15)
plt.subplot(222)
sub_plot_2 = sns.distplot(np.log(train['loanAmnt']))
sub_plot_2.set_title("loanAmnt (Log) Distribuition", fontsize=18)
sub_plot_2.set_xlabel("")
sub_plot_2.set_ylabel("Probability", fontsize=15)

另外,在实际操作中,除了可以用概率密度分布来查看某一个变量的数据分布外,还常用箱线图来查看数据分布集中度。下面从上面挑选几个变量举例。
通过观察,可以看到 'loanAmnt', 'interestRate', 'installment', 'postCode', 'regionCode', ' 'openAcc', 'totalAcc', 'n2', 'n3', 等变量分布类似于正态分布,选择这些变量进一步用箱线图查看分布
# 单特征箱线图,区分是否违约
box_fea = [ 'loanAmnt', 'interestRate', 'installment', 'postCode', 'regionCode', 'openAcc', 'totalAcc', 'n2', 'n3']
f, ax = plt.subplots(3,3, figsize = (20,15))
for i, col in enumerate(box_fea):
sns.boxplot(x = 'isDefault', y = col, saturation=0.5, palette='pastel', data = train, ax = ax[i//3][i%3])

大体观察一下,可以发现贷款金额(loanAmt)和贷款利率('interestRate)较高时出现贷款违约的风险更高,即isDefault(目标变量)值为1。其他特征未发现明显区别。
2.2 下面在考察目标Y(是否违约欺诈)和单一变量X的统计关系
2.2.1 观察类别型变量
# 类别型变量X和y
train_loan_isDefault = train.loc[train['isDefault'] == 1]
train_loan_nonDefault= train.loc[train['isDefault'] == 0]
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 10))
train_loan_isDefault.groupby('grade')['grade'].count().plot(kind='barh', ax=ax1, title='Count of grade Default')
train_loan_nonDefault.groupby('grade')['grade'].count().plot(kind='barh', ax=ax2, title='Count of grade non-Default')
train_loan_isDefault.groupby('employmentLength')['employmentLength'].count().plot(kind='barh', ax=ax3, title='Count of employmentLength Default')
train_loan_nonDefault.groupby('employmentLength')['employmentLength'].count().plot(kind='barh', ax=ax4, title='Count of employmentLength non-Default')

2.2.2 观察数值连续型变量
这里我们就不在描述,留给读者自己验证
2.3 时间序列处理
因为train和test数据中都有时间特征issueDate, (猜测为贷款发放日期),可以简单验证一下
#转化成时间格式 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
#测试数据集转化成时间格式
test['issueDate'] = pd.to_datetime(test['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
plt.figure(figsize=(10,6))
plt.hist(train['issueDateDT'], label='train', bins = 20);
plt.hist(test['issueDateDT'], label='test',bins = 20)
plt.legend();
plt.title('Distribution of issueDateDT dates');
#train 和 test issueDateDT 日期有重叠 所以使用基于时间的分割进行验证是不明智的

train 和 test issueDateDT 日期有重叠 ,所以使用基于时间的分割进行验证是不明智的。
好了,本次的数据分析任务结束。我们通过各个简单的统计量来对数据整体的了解,分析各个类型变量相互之间的关系,以及用合适的图形可视化出来直观观察。通过对数据进行探索性分析,我们初步了解数据的分布情况,熟悉数据。这项工作为特征工程做准备的阶段。数据分析之所以重要,是因为很多时候该阶段提取出来的特征可以直接当作规则来用。下一期我们就进入重头戏,特征工程!敬请期待!
网友评论