本案例是Pandas数据分析课程【第四章】数据转换与融合配套案例。案例使用Lending Club融资平台2015年公开数据集中一小部分进行分析,着重展示数据转换与融合方法的应用。
Lending Club是一家美国P2P公司,投资者在平台中为融资方提供资金,并以此获得贷款利率收益。Lending Club为投资者与融资方提供平台。本案例中,我们使用Lending Club2015年公开数据集中截取的一小部分进行分析。数据分为三个部分:
(1)用户信息数据
变量名称 | 含义说明 |
---|---|
user_id | 用户编号 |
emp_length | 工作年限 |
home_ownership | 房屋所有情况 |
annual_inc | 年收入 |
Verification_status | 信息验证情况 |
(2)用户历史数据
变量名称 | 含义说明 |
---|---|
user | 用户编号 |
acc_open_past_24mths | 用户过去24月开立账户数 |
avg_cur_bal | 账户平均存款 |
(3)贷款交易数据
变量名称 | 含义说明 |
---|---|
user | 用户编号 |
term | 贷款期限 |
int_rate | 贷款利率 |
grade | 贷款评级 |
loan_status | 贷款状态 |
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
导入三个数据。
user = pd.read_csv("./input/user.csv")
loan = pd.read_csv("./input/loan.csv")
history = pd.read_csv("./input/history.csv")
查看用户信息数据前五行。
user.head()
查看用户历史数据前五行。
history.head()
我们也可以通过随机采样的方式查看数据的初步情况。我们可以使用sample
方法。随机采样的量可以通过参数n
或者frac
调整,n
按指定数量进行采样,frac
按指定比例进行采样。
随机查看贷款交易数据中的5行。
loan.sample(n=5)
随机查看贷款交易数据中的1%。
loan.sample(frac=0.01)
sample
默认的是不放回采样(每个样本只可能出现一次),我们也可以调整replace
参数为True
改为有放回采样。
loan.sample(n=10,replace=True)
有时,我们希望重复调用某次采样的结果,我们可以设定random_state
参数为同一个数来实现。
loan.sample(n=5,random_state=1)
loan.sample(n=5,random_state=1)
两次采样的结果完全一致。这里random_state
的数值大小没有实际意义,只是用来指定某一次的随机数生成结果,可以取任意的自然数。
除了行采样,sample
也可以实现列采样,只需要调整axis
参数为1即可。
loan.sample(n=3,axis=1)
merge
与join
进行数据融合¶可以看到,三个数据集都有共同的变量:用户编号(但在不同的数据表中命名略有不同),我们可以基于这一公共变量对数据进行合并。
这里我们先提取出一部分数据详细介绍merge
与join
的联系与区别。
test_user = user.loc[[1,3,5,7,8]]
test_user
test_loan = loan[loan.user.isin([2,3,4,5,6,7])]
test_loan
merge
和join
作为Pandas中常用的数据融合方法,目的都是将两个数据表通过共同变量进行连接。我们先来看一看merge
的各个参数:
left.merge(right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), indicator=False)
on
, left_on
, right_on
, 用来指定连接的变量。若这一变量在两个数据框中命名相同,直接使用on
指定即可,否则通过left_on
和right_on
分别指定左表变量名和右表变量名。left_index
和right_index
的参数为True
来实现。how
为连接方式,有'inner', 'left', 'right', 'outer'四种,我们通过几个例子来对比一下。基于用户信息数据的'user_id'变量和贷款交易数据的'user'变量进行内连接(inner)。这种方式下,只有所选定列在左表与右表能匹配的行会被保留。
test_user.merge(test_loan,how="inner",left_on="user_id",right_on="user")
基于用户信息数据的'user_id'变量和贷款交易数据的'user'变量进行左连接(left)。这种方式下,左表所有行都被保留,不能匹配的部分用缺失值填充。
test_user.merge(test_loan,how="left",left_on="user_id",right_on="user")
基于用户信息数据的'user_id'变量和贷款交易数据的'user'变量进行右连接(right)。这种方式下,右表所有行都被保留,不能匹配的部分用缺失值填充。
test_user.merge(test_loan,how="right",left_on="user_id",right_on="user")
基于用户信息数据的'user_id'变量和贷款交易数据的'user'变量进行外连接(outer)。这种方式下,左表和右表所有行都会被保留,不能匹配的部分用缺失值填充。
test_user.merge(test_loan,how="outer",left_on="user_id",right_on="user")
merge
中的indicator
参数能很好地帮助我们找到返回结果的来源:
test_user.merge(test_loan,how="outer",left_on="user_id",right_on="user",indicator=True)
设定indicator
参数为Ture
后,返回结果中多了一列"_merge",取值有"both", "left_only", "right_only"三种。分别代表左右表匹配成功,左表有而右表没有,右表有而左表没有三种情况。
当左表与右表中变量同名时,我们可以通过suffixes
参数为左表变量与右表变量附加不同字段,便于后续区分。
test_loan.merge(test_loan,on='user',suffixes=('_l','_r'))
我们再来看一下join
的各个参数:
left.join(right, on=None, how='left', lsuffix='', rsuffix='', sort=False)
join
的参数比merge
要简单很多,且各参数merge
中含义基本一致,这里不再赘述。事实上,join
就是merge
的简化版本,所有join
能实现的操作,我们都可以使用merge
实现,但一些情况下,使用join
可以使我们的代码更简洁。(后文我们将结合get_dummies使用)。但需要注意:
join
时,右表只能基于索引进行连接;on
参数,可以指定左表进行连接的变量(可以是索引也可以是任意列)。merge
和join
中还有一个参数sort
,指定为True
会让返回的结果按连接变量进行升序排列。
test_user.merge(test_loan,how="outer",left_on="user_id",right_on="user", sort=True)
sort_index
与sort_values
进行排序¶Pandas中的sort_index
和sort_values
也可以对DataFrame进行排序,sort_index
是按照索引进行排序,sort_values
是按照指定变量排序。例如我们想将用户历史数据按账户平均存款排序。
history.sort_values(by='avg_cur_bal')[:5]
若要降序排列,可以指定ascending
参数为False
。
history.sort_values(by='avg_cur_bal',ascending=False)[:5]
也可以指定对缺失值的排序方式,默认缺失值将排在最后,我们可以设定na_position
为first
将缺失值排在最前面。
history.sort_values(by='avg_cur_bal',na_position='first')[:5]
下面我们对三个表进行连接,由于我们的最终目的是对每个贷款的最终违约情况进行分析,所以我们基于表"loan"进行两次左连接:
combine = loan.merge(user,how="left",left_on="user",right_on="user_id")
combine = combine.merge(history,how="left",on="user")
combine.head()
删去用于合并的列名user_id
,使用info
查看数据缺失情况。
combine.drop(columns="user_id",inplace=True)
combine.info()
emp_length
工作年限和avr_cur_bal
账户平均存款中存在缺失值,我们将这部分缺失数据删去。
combine.dropna(axis=0,how="any",inplace=True)
combine.info()
我们得到了1216条完整的贷款数据,注意到int_rate
利率是object类型,需要对其进行转换。我们使用str.strip
方法,str.strip
方法是向量化的字符串处理方法,会对Series的每一个元素应用strip
。输入参数'%',将会移除字符串中的'%'。
int_rate = combine.int_rate.str.strip('%')
int_rate[:3]
去除'%'后,我们就可以直接使用to_numeric
将字符串转化为数值型。
int_rate = pd.to_numeric(int_rate)
int_rate.dtype
combine.int_rate = int_rate
下面使用describe
初步查看数据的基本情况。
combine.describe()
cut
与qcut
将收入变量离散化¶数据中,年收入大部分都在100,000元以下,存在高收入异常值,针对这一情况,我们可以尝试将年收入这一连续变量离散化,分割为分类变量。我们使用cut
函数按照指定的分割点对数据进行划分。
annual_inc = pd.cut(combine.annual_inc,bins=[np.min(combine.annual_inc)-1,np.percentile(combine.annual_inc,50),np.max(combine.annual_inc)+1],labels=['low','high'])
annual_inc[:3]
annual_inc.value_counts()
这里我们通过设定bin
参数设定了分割点,将数据按照中位数进行了划分。同时设定了参数labels
,使用这个参数可以方便地为新的划分区间命名。
cut
也可以直接指定划分份数,将数据等距划分。例如,我们想将数据等距分为五份:
pd.cut(combine.annual_inc,5).value_counts()
理论上,数据应被等距分为了五份,每一个区间的长度都相同,但我们计算可以发现,第一个区间的长度为113364,而其他几个区间的长度都为112800。这并不是cut
分割错误,只是为了包含最小值或最大值,cut
的左右端会拓展0.1%。
Pandas中与cut
相似的另一个函数是qcut
,它将按照每个区间中频数相同的原则进行划分,当我们指定划分份数后,就会用相应的分位数进行划分。例如,当我们使用qcut
将数据分为两份时,分割点就是中位数,四份时分割点就是四分位数。
pd.qcut(combine.annual_inc,2).value_counts()
combine.annual_inc = annual_inc
这与我们直接使用cut
并设定分割点的方式得到的结果一致,但qcut
更为简洁。
replace
与map
对变量进行值替换¶下面我们查看贷款的履约情况
combine.loan_status.value_counts()
我们认为状态为"Charged Off","In Grace Period", "Late (31-120 days)"的贷款有违约风险,视为不良贷款,将其值标记为1,其他贷款标记为0。我们使用replace
进行值替换。
combine['loan_status'].replace(to_replace=['Fully Paid','Current','Charged Off','In Grace Period','Late (31-120 days)'],
value=[0,0,1,1,1],
inplace=True)
combine['loan_status'][:8]
这里,to_replace
指需要替换的值,value
指要替换成的新值。replace
作为数值替换的方法,适用范围非常之广,可以实现多种操作。我们使用之前的test_loan数据表进行介绍。
test_loan
除了像之前那样将需要替换的值与替换的新值分别用列表输入外,我们也可以使用字典进行指定。
test_loan.replace(to_replace={'loan_status':{'Fully Paid':0,'Charged Off':0,'In Grace Period':1}})
也可以同时指定不同变量的不同值替换为相同新值。
test_loan.replace(to_replace={'loan_status':'Fully Paid','grade':'A'},value='Good')
也可以指定正则表达式进行替换,这时需要设定参数regex
为True
,代表to_replace
部分输入的是正则表达式。如查找所有以C开头的字段并替换为Bad。
test_loan.replace(to_replace='C+.*$', value='Bad', regex=True)
在Pandas中,如果只是针对某一个Series进行数值替代,我们也可以使用map
方法。
test_loan['loan_status'].map({'Fully Paid':0,'Charged Off':0,'In Grace Period':1})
这同样实现了将贷款状态进行替换的效果,但map
不能像replace
一样直接对DataFrame进行操作。不过map
不仅仅可以像上面一样输入字典作为参数,也可以直接输入一个函数进行映射。例如,我们想将数据中利率低于12%的映射为'Low',高于12%的映射为'High'。
combine['int_rate'][:5]
def f(x):
if x < 12:
return 'Low'
else:
return 'High'
combine['int_rate'][:5].map(f)
这样的自定义函数进行映射是replace
方法无法实现的。
get_dummies
进行哑变量处理¶为了进行建模分析,我们常常还需要对分类变量进行哑变量处理。
cat_vars=['term','grade','emp_length','annual_inc','home_ownership','verification_status']
for var in cat_vars:
cat_list = pd.get_dummies(combine[var], prefix=var,drop_first=True)
combine=combine.join(cat_list)
get_dummies
函数中我们使用了两个参数。prefix
可以为新生成的哑变量添加前缀,这方便我们识别新生成的变量是从原来哪一个变量中得来的。drop_first
设置为True
将删去所获得哑变量的第一个,这是因为在建模中,有k类的分类变量我们只需要k-1个变量就可以将其描述,如果使用k个变量则会出现完全共线性的问题。
此外,在这里我们选择了使用join
而不是merge
,这是因为get_dummies
返回的结果与原始数据有相同的索引,使用join直接基于索引进行连接更简洁。
pd.get_dummies(combine['grade'], prefix='grade',drop_first=True)[:5]
如果我们使用merge
将需要这样写:combine=combine.merge(cat_list,left_index=True,right_index=True)
最后,将原始的变量删除,并查看处理后的数据:
combine.drop(columns=cat_vars,inplace=True)
combine.head()
concat
添加常数项列¶下面,我们提取贷款状态为因变量,其他变量为自变量。
X = combine.drop(columns=['user','loan_status'])
y = combine.loan_status
在回归分析中,我们往往还需要为自变量添加常数项列,值全为1。
首先创建一个长度为X的行数,值全为1的列表。再将其转化为Series,并命名"const"。
const = pd.Series([1] * combine.shape[0],name="const")
使用reset_index
重设X的索引,drop
会将原来的索引删除。
X.reset_index(drop=True,inplace=True)
使用concat
对数据进行合并,并指定方向为列。
X = pd.concat([const,X],axis=1)
X.head()
这里之所以要先重新设置X的索引,是因为concat
是基于索引进行拼接的。这么看来,对于列的拼接其实直接使用join
就可以了,不过目前join
只能作为DataFrame的方法,我们想拼接DataFrame和Series就必须把DataFrame写在前面:X.join(const)
。
此外,concat
更常用的是进行行的连接。我们看一下concat
的参数:pandas.concat(objs, axis=0, join='outer')
可以看到,concat
的对象必须是一个list,因此在实际操作中必须先使用[]
将要进行连接的对象转化为list。我们简单生成两个DataFrame进行介绍。
df1 = pd.DataFrame([['a','a', 1], ['b','b', 2]],columns=['letter','letter1','number'])
df1
df2 = pd.DataFrame([['c','c', 3], ['d','d', 4]],columns=['letter','letter2', 'number'])
df2
使用inner
方式进行连接,只有能够匹配的变量才会保留。
pd.concat([df1,df2],axis=0,join='inner')
使用outer
方式进行连接,所有变量都会保留,不能匹配的部分用缺失值填充。
pd.concat([df1,df2],axis=0,join='outer')
ignore_index
参数为Ture
将忽略原来的索引,从0开始重建索引。
pd.concat([df1,df2],ignore_index=True)
通过key
参数可以建立多层索引,方便识别数据来自于哪个数据源。
pd.concat([df1,df2],keys=['df1', 'df2'])
经过以上的处理,我们将三份数据进行了融合,并进行了相应的数据转换,最终得到了因变量y和自变量X,这是后续建模分析的基础。