数据清理、合并、重塑、转换
1. 合并数据集
pandas.merge:
根据一个或多个键将不同DataFrame的行连接起来,它实现的是数据库的连接操作。具体如下:
- 多对一的合并:
df1
Out[4]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 a
6 6 b
df2
Out[5]:
data2 key
0 0 a
1 1 b
2 2 d
pd.merge(df1,df2)
Out[6]:
data1 key data2
0 0 b 1
1 1 b 1
2 6 b 1
3 2 a 0
4 4 a 0
5 5 a 0
在上述代码中,df1中的数据有多个被标记为a和b的行,而df2中key列的每个值仅对应一行,因此为多对一的合并。还可以显示的指定用哪个列进行连接,如on=’key’,如果进行连接的两个DataFrame没有相同的列名,则可以这样显示指定:
pd.merge(df1,df2,left_on='lkey',right_on='rkey')
我们观察结果集可以发现,c和d以及与子相关的数据都不见了,所以merge默认情况下做的是“inner”连接(即结果中的键是交集),根据已知SQL知识,自然还有其他方式,如left,right和outer(并集)。
- 多对多连接
多对多产生的是行的笛卡尔积,如下所示,左边的DataFrame有3个’b’行,右边有两个,那么最终结果中就有6个’b’行:
df1
Out[10]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 b
df2
Out[11]:
data2 key
0 0 a
1 1 b
2 2 a
3 3 b
4 4 d
pd.merge(df1,df2,on='key',how='left')
Out[12]:
data1 key data2
0 0 b 1.0
1 0 b 3.0
2 1 b 1.0
3 1 b 3.0
4 2 a 0.0
5 2 a 2.0
6 3 c NaN
7 4 a 0.0
8 4 a 2.0
9 5 b 1.0
10 5 b 3.0
- 还可以通过多个键进行连接,只需传入一个由列名组成的列表即可,可以做如下理解:多个键形成一系列元组,并将其当做单个键连接(事实并非如此)。
left
Out[19]:
key1 key2 lval
0 foo one 1
1 foo two 2
2 bar one 3
right
Out[20]:
key1 key2 lval
0 foo one 4
1 foo one 5
2 bar one 6
3 bar two 7
pd.merge(left,right,on=['key1','key2'],how='outer')
Out[21]:
key1 key2 lval_x lval_y
0 foo one 1.0 4.0
1 foo one 1.0 5.0
2 foo two 2.0 NaN
3 bar one 3.0 6.0
4 bar two NaN 7.0
- 对重复列名的处理
suffixes选项:用于指定附加到左右两个DataFrame对象的重叠列名上的字符串。
pd.merge(left,right,on='key1')
Out[22]:
key1 key2_x lval_x key2_y lval_y
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
pd.merge(left,right,on='key1',suffixes=('_left','_right'))
Out[23]:
key1 key2_left lval_left key2_right lval_right
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
- 轴向连接
- 首先来看numpy中用于合并原始Numpy数组的concatenation函数:
arr
Out[30]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
np.concatenate([arr,arr])
Out[31]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
np.concatenate([arr,arr],axis=1)
Out[32]:
array([[ 0, 1, 2, 3, 0, 1, 2, 3],
[ 4, 5, 6, 7, 4, 5, 6, 7],
[ 8, 9, 10, 11, 8, 9, 10, 11]])
- 在pandas中为concat函数
s1=pd.Series([0,1],index=['a','b'])
s2=pd.Series([2,3,4],index=['c','d','e'])
s3=pd.Series([5,6],index=['f','g'])
pd.concat([s1,s2,s3])
Out[36]:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
pd.concat([s1,s2,s3],axis=1)
Out[37]:
0 1 2
a 0.0 NaN NaN
b 1.0 NaN NaN
c NaN 2.0 NaN
d NaN 3.0 NaN
e NaN 4.0 NaN
f NaN NaN 5.0
g NaN NaN 6.0
默认情况下,concat是在axis=0上工作的,最终产生一个新的Series,如果传入axis=1,则结果就会变成一个DataFrame。
- 合并重叠数据
首先使用numpy的where函数
a
Out[41]:
f NaN
e 2.5
d NaN
c 3.5
b 4.5
a NaN
dtype: float64
b
Out[42]:
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a 5.0
dtype: float64
np.where(pd.isnull(a),b,a)
Out[43]: array([0. , 2.5, 2. , 3.5, 4.5, 5. ])
b[-1]=np.nan
np.where(pd.isnull(a),b,a)
Out[45]: array([0. , 2.5, 2. , 3.5, 4.5, nan])
Series的comnine_first方法有同样的功能,并且可以数据对齐
b.combine_first(a)
Out[46]:
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64
同样对于DataFrame,这个函数也会做同样的事情,因此你可以将此看做,用参数对象中的数据为调用者对象的缺失数据“打补丁”
1. 数据转换
- 移除重复值
DataFrame的duplicated方法会返回一个布尔型的Series,表示各行是否是重复行
data
Out[48]:
k1 k2
0 one 1
1 one 1
2 one 2
3 two 3
4 two 3
5 two 4
6 two 4
data.duplicated()
Out[49]:
0 False
1 True
2 False
3 False
4 True
5 False
6 True
dtype: bool
drop_duplicates方法,返回一个移除了重复行的DataFrame
data.drop_duplicates()
Out[50]:
k1 k2
0 one 1
2 one 2
3 two 3
5 two 4
这两个方法默认保留的是第一个出现的值组合。传入take_last=True则保留最后一个。
- 利用函数或映射进行数据转换(map)
我们来看下面有关肉类的数据
data
Out[52]:
food ounces
0 bacon 4.0
1 pulled pork 3.0
2 bacon 12.0
3 Pastrami 6.0
4 corned beef 7.5
5 Bacon 8.0
6 pastrami 3.0
7 honey ham 5.0
8 nova lox 6.0
假设想要添加一列表示该肉类食物的来源,具体映射为:
meat_to_animal
Out[54]:
{'bacon': 'pig',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon',
'pastrami': 'cow',
'pulled pock': 'pig'}
为了保持一致,先进行大小写转换,然后进行关联(添加列):
data['animal']=data['food'].map(str.lower).map(meat_to_animal)
data
Out[56]:
food ounces animal
0 bacon 4.0 pig
1 pulled pork 3.0 NaN
2 bacon 12.0 pig
3 Pastrami 6.0 cow
4 corned beef 7.5 cow
5 Bacon 8.0 pig
6 pastrami 3.0 cow
7 honey ham 5.0 pig
8 nova lox 6.0 salmon
- 离散化和面元划分
为了便于分析,常常将连续数据离散化为面元。
假设有一组人员数据,我们希望把它划分为不同的年龄组。
ages
Out[61]: [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
bins
Out[62]: [18, 25, 35, 60, 100]
cats=pd.cut(ages,bins)
cats
Out[64]:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
这里采用pandas的cut函数,它返回的是一个特殊的Categorical对象,它含有一个表示不同分类名称的categories(之前为levels)数组以及一个为年龄数据进行标号的codes(之前是labels)属性:
cats.codes
Out[70]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
cats.categories
Out[71]:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
pd.value_counts(cats)
Out[72]:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
还可以设置属性labels设置为一个列表或元组即可:
group_names=['Youth','YouthAdult','MiddleAged','Senior']
pd.cut(ages,bins,labels=group_names)
Out[74]:
[Youth, Youth, Youth, YouthAdult, Youth, ..., YouthAdult, Senior, MiddleAged, MiddleAged, YouthAdult]
Length: 12
Categories (4, object): [Youth < YouthAdult < MiddleAged < Senior]
qcut可以根据样板分位数对数据进行面元划分,得到大小基本相等的面元。
data=np.random.randn(1000) # 正态分布
cats=pd.qcut(data,4) #按四分位数进行分割
cats
Out[78]:
[(-2.893, -0.64], (0.0127, 0.739], (0.739, 3.357], (0.0127, 0.739], (-0.64, 0.0127], ..., (-0.64, 0.0127], (0.0127, 0.739], (-0.64, 0.0127], (0.0127, 0.739], (0.739, 3.357]]
Length: 1000
Categories (4, interval[float64]): [(-2.893, -0.64] < (-0.64, 0.0127] < (0.0127, 0.739] < (0.739, 3.357]]
pd.value_counts(cats)
Out[79]:
(0.739, 3.357] 250
(0.0127, 0.739] 250
(-0.64, 0.0127] 250
(-2.893, -0.64] 250
dtype: int64
跟cut一样,也可以设置自定义的分位数:
pd.qcut(data,[0,0.1,0.5,0.9,1.])
Out[80]:
[(-2.893, -1.288], (0.0127, 1.316], (0.0127, 1.316], (0.0127, 1.316], (-1.288, 0.0127], ..., (-1.288, 0.0127], (0.0127, 1.316], (-1.288, 0.0127], (0.0127, 1.316], (0.0127, 1.316]]
Length: 1000
Categories (4, interval[float64]): [(-2.893, -1.288] < (-1.288, 0.0127] < (0.0127, 1.316] < (1.316, 3.357]]