第7章 数据规整化:清理、转换、合并、重塑
合并数据集
pandas对象中的数据可以通过一些内置的方法进行合并:
- pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。它就是在数据库中实现连接的操作。
- pandas.concat可以沿着一条轴将多个对象堆叠到一起。
- 实例方法conbine_first可以将重复数据编接在一起,用一个对象中的值填充另一个对象中的缺失值。
数据库风格的DataFrame合并
数据集的合并(merge)或连接(join)运算是通过一个或多个键将行链接起来的。这些运算是关系型数据库的核心。pandas的merge函数是对数据应用这些算法的主要切入点。
以一个简单的例子开始:
In [1]: from pandas import Series, DataFrame
In [2]: import pandas as pd
In [3]: import numpy as np
In [4]: df1 = DataFrame({'key':['b', 'b', 'a', 'c', 'a', 'a', 'b'], 'data1': range(7)})
In [5]: df2 = DataFrame({'key': ['a', 'b', 'd'], 'data2': range(3)})
In [6]: df1
Out[6]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 a
6 6 b
In [7]: df2
Out[7]:
data2 key
0 0 a
1 1 b
2 2 d
这是一种多对一的合并。df1中的数据有多个被标记为a和b的行,而df2中key列的每个值则仅对应一行。对这些对象调用merge即可得到:
In [8]: pd.merge(df1, df2)
Out[8]:
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
注意,这里并没有指明要用哪个列进行连接。如果没有指定,merge就会将重叠列的列明当作键。不过,最好显示指定一下:
In [9]: pd.merge(df1, df2, on = 'key')
Out[9]:
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
如果两个对象的列名不同,也可以分别进行指定:
In [10]: df3 = DataFrame({'1key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'], 'data1': range(7)})
In [11]: df4 = DataFrame({'rkey': ['a', 'b', 'd'], 'data2': range(3)})
In [12]: pd.merge(df3, df4, left_on = '1key', right_on = 'rkey')
Out[12]:
1key data1 data2 rkey
0 b 0 1 b
1 b 1 1 b
2 b 6 1 b
3 a 2 0 a
4 a 4 0 a
5 a 5 0 a
默认情况下,merge做的是“inner”连接;结果中的键是交集。其他方式还有“left”、“right”以及“outer”。外连接求取的是键的并集,组合了左连接和右连接的效果:
In [13]: pd.merge(df1, df2, how = 'outer')
Out[13]:
data1 key data2
0 0.0 b 1.0
1 1.0 b 1.0
2 6.0 b 1.0
3 2.0 a 0.0
4 4.0 a 0.0
5 5.0 a 0.0
6 3.0 c NaN
7 NaN d 2.0
多对多的合并操作非常简单,无需额外的工作。如下所示:
In [14]: df1 = DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'], 'data1': range(6)})
In [15]: df2 = DataFrame({'key': ['a', 'b', 'a', 'b', 'd'], 'data2': range(5)})
In [16]: df1
Out[16]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 b
In [17]: df2
Out[17]:
data2 key
0 0 a
1 1 b
2 2 a
3 3 b
4 4 d
In [18]: pd.merge(df1, df2, on = 'key', how = 'left')
Out[18]:
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
多对多连接产生的是行的笛卡儿积。由于左边的DataFrame有3个“b”行,右边的有2个,所以最终结果中就有6个“b”行。连接方式只影响出现在结果中的键:
In [19]: pd.merge(df1, df2, how = 'inner')
Out[19]:
data1 key data2
0 0 b 1
1 0 b 3
2 1 b 1
3 1 b 3
4 5 b 1
5 5 b 3
6 2 a 0
7 2 a 2
8 4 a 0
9 4 a 2
要根据多个键进行合并,传入一个由列名组成的列表即可:
In [21]: right = DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
...: 'key2': ['one', 'one', 'one', 'two'],
...: 'rval': [4, 5, 6, 7]})
In [22]: left = DataFrame({'key1': ['foo', 'foo', 'bar'],
...: 'key2': ['one', 'two', 'one'],
...: 'lval': [1, 2, 3]})
In [23]: pd.merge(left, right, on = ['key1', 'key2'], how = 'outer')
Out[23]:
key1 key2 lval rval
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
对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(稍后将会介绍如何重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:
In [24]: pd.merge(left, right, on = 'key1')
Out[24]:
key1 key2_x lval key2_y rval
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
In [25]: pd.merge(left, right, on = 'key1', suffixes = ('_left', '_right'))
Out[25]:
key1 key2_left lval key2_right rval
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
merge函数的参数:
- left:参与合并的左侧DataFrame
- right:参与合并的右侧DataFrame
- how:“inner”、“outer”、“left”、“right”其中之一。默认为“inner”
- on:用于连接的列名。必须存在于左右两个DataFrame对象中。如果未指定,且其他连接键也未指定,则以left和right列名的交集作为连接键
- left_on:左侧DataFrame中用作连接键的列
- right_on:右侧DataFrame中用作连接键的列
- left_index:将左侧的行索引用作其连接键
- right_index:类似于left_index
- sort:根据连接键对合并后的数据进行排序,默认为True。有时在处理大数据集时,禁用该选项可获得更好的性能
- suffixes:字符串值元组,用于追加到重叠列名的末尾,默认为(‘_x’, ‘_y’)。例如,如果左右两个DataFrame对象都有“data”,则结果中就会出现“data_x”和“data_y”
- copy:设置为False,可以在某些特殊情况下避免将数据复制到结果数据结构中。默认总是复制
索引上的合并
有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键:
In [26]: left1 = DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'], 'value': range(6)})
In [27]: right1 = DataFrame({'group_val': [3.5, 7]}, index = ['a', 'b'])
In [28]: left1
Out[28]:
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
In [29]: right1
Out[29]:
group_val
a 3.5
b 7.0
In [30]: pd.merge(left1, right1, left_on = 'key', right_index = True)
Out[30]:
key value group_val
0 a 0 3.5
2 a 2 3.5
3 a 3 3.5
1 b 1 7.0
4 b 4 7.0
由于默认的merge方法是求取连接键的交集,因此可以通过外连接的方式得到它们的并集:
In [31]: pd.merge(left1, right1, left_on = 'key', right_index = True, how = 'outer')
Out[31]:
key value group_val
0 a 0 3.5
2 a 2 3.5
3 a 3 3.5
1 b 1 7.0
4 b 4 7.0
5 c 5 NaN
对于层次化索引的数据,事情就有点复杂了:
In [32]: lefth = DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
...: 'key2': [2000, 2001, 2002, 2001, 2002],
...: 'data': np.arange(5.)})
In [33]: righth = DataFrame(np.arange(12).reshape((6,2)),
...: index = [['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'],
...: [2001, 2000, 2000, 2000, 2001, 2002]],
...: columns = ['event1', 'event2'])
In [34]: lefth
Out[34]:
data key1 key2
0 0.0 Ohio 2000
1 1.0 Ohio 2001
2 2.0 Ohio 2002
3 3.0 Nevada 2001
4 4.0 Nevada 2002
In [35]: righth
Out[35]:
event1 event2
Nevada 2001 0 1
2000 2 3
Ohio 2000 4 5
2000 6 7
2001 8 9
2002 10 11
这种情况下,你必须以列表的形式指明用作合并键的多个列(注意对重复索引值的处理):
In [36]: pd.merge(lefth, righth, left_on = ['key1', 'key2'], right_index = True)
Out[36]:
data key1 key2 event1 event2
0 0.0 Ohio 2000 4 5
0 0.0 Ohio 2000 6 7
1 1.0 Ohio 2001 8 9
2 2.0 Ohio 2002 10 11
3 3.0 Nevada 2001 0 1
In [37]: pd.merge(lefth, righth, left_on = ['key1', 'key2'], right_index = True, how = 'outer')
Out[37]:
data key1 key2 event1 event2
0 0.0 Ohio 2000 4.0 5.0
0 0.0 Ohio 2000 6.0 7.0
1 1.0 Ohio 2001 8.0 9.0
2 2.0 Ohio 2002 10.0 11.0
3 3.0 Nevada 2001 0.0 1.0
4 4.0 Nevada 2002 NaN NaN
4 NaN Nevada 2000 2.0 3.0
同时使用合并双方的索引也没问题:
In [38]: left2 = DataFrame([[1., 2.], [3., 4.], [5., 6.]], index = ['a', 'c', 'e'], columns = ['Ohio', 'Nevada'])
In [39]: right2 = DataFrame([[7., 8.], [9., 10.], [11., 12.], [13., 14.]],
...: index = ['b', 'c', 'd', 'e'], columns = ['Missouri', 'Alabama'])
In [40]: left2
Out[40]:
Ohio Nevada
a 1.0 2.0
c 3.0 4.0
e 5.0 6.0
In [41]: right2
Out[41]:
Missouri Alabama
b 7.0 8.0
c 9.0 10.0
d 11.0 12.0
e 13.0 14.0
In [42]: pd.merge(left2, right2, left_index = True, right_index = True)
Out[42]:
Ohio Nevada Missouri Alabama
c 3.0 4.0 9.0 10.0
e 5.0 6.0 13.0 14.0
DataFrame还有一个join实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的DataFrame对象,而不管它们之间有没有重叠的列。在上面的例子中,我们可以编写:
In [43]: left2.join(right2, how = 'outer')
Out[43]:
Ohio Nevada Missouri Alabama
a 1.0 2.0 NaN NaN
b NaN NaN 7.0 8.0
c 3.0 4.0 9.0 10.0
d NaN NaN 11.0 12.0
e 5.0 6.0 13.0 14.0
由于一些历史原因(早期版本的pandas),DataFrame的join方法是在连接键上做左连接。它还支持参数DataFrame的索引跟调用者的DataFrame的某个列之间的连接:
In [44]: left1.join(right1, on = 'key')
Out[44]:
key value group_val
0 a 0 3.5
1 b 1 7.0
2 a 2 3.5
3 a 3 3.5
4 b 4 7.0
5 c 5 NaN
最后,对于简单的索引合并,你还可以向join传入一组DataFrame(后面会介绍更为通用的concat函数,它也能实现此功能):
In [45]: another = DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
...: index = ['a', 'c', 'e', 'f'], columns = ['New York', 'Oregon'])
In [46]: left2.join([right2, another])
Out[46]:
Ohio Nevada Missouri Alabama New York Oregon
a 1.0 2.0 NaN NaN 7.0 8.0
c 3.0 4.0 9.0 10.0 9.0 10.0
e 5.0 6.0 13.0 14.0 11.0 12.0
In [48]: left2.join([right2, another], how = 'outer')
Out[48]:
Ohio Nevada Missouri Alabama New York Oregon
a 1.0 2.0 NaN NaN 7.0 8.0
b NaN NaN 7.0 8.0 NaN NaN
c 3.0 4.0 9.0 10.0 9.0 10.0
d NaN NaN 11.0 12.0 NaN NaN
e 5.0 6.0 13.0 14.0 11.0 12.0
f NaN NaN NaN NaN 16.0 17.0
轴向连接
另一种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。Numpy有一个用于合并原始Numpy数组的concatenation函数:
In [49]: arr = np.arange(12).reshape((3,4))
In [50]: arr
Out[50]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [51]: np.concatenate([arr,arr], axis =1)
Out[51]:
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对象(如Series和DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。具体点说,你还需要考虑以下这些东西:
- 如果各对象其他轴上的索引不同,那些轴应该是做并集还是交集?
- 结果对象中的分组需要各不相同吗?
- 用于连接的轴重要吗?
pandas的concat函数提供了一种能够解决这些问题的可靠方式。下面将给出一些例子来讲解其使用方式。假设有三个没有重叠索引的Series:
In [52]: s1 = Series([0,1], index = ['a', 'b'])
In [53]: s2 = Series([2,3,4], index = ['c', 'd', 'e'])
In [54]: s3 = Series([5,6], index = ['f', 'g'])
对这些对象调用concat可以将值和索引粘合在一起:
In [55]: pd.concat([s1, s2, s3])
Out[55]:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
默认情况下,concat是在axis=0上工作的,最终产生一个新的Series。如果传入axis=1,则结果就会变成一个DataFrame(axis=1是列):
In [56]: pd.concat([s1, s2, s3], axis = 1)
Out[56]:
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
这种情况下,另外一条轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。传入join=’inner’即可得到它们的交集:
In [60]: s4 = pd.concat([s1 * 5, s3])
In [61]: s4
Out[61]:
a 0
b 5
f 5
g 6
dtype: int64
In [62]: pd.concat([s1, s4], axis = 1)
Out[62]:
0 1
a 0.0 0
b 1.0 5
f NaN 5
g NaN 6
In [63]: pd.concat([s1, s4], axis = 1, join = 'inner')
Out[63]:
0 1
a 0 0
b 1 5
也可通过join_axes指定要在其他轴上使用的索引:
In [64]: pd.concat([s1, s4], axis = 1, join_axes = [['a', 'c', 'b', 'e']])
Out[64]:
0 1
a 0.0 0.0
c NaN NaN
b 1.0 5.0
e NaN NaN
有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引,使用keys参数即可达到这个目的:
In [65]: result = pd.concat([s1, s1, s3], keys = ['one', 'two', 'three'])
In [66]: result
Out[66]:
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64
In [67]: result.unstack()
Out[67]:
a b f g
one 0.0 1.0 NaN NaN
two 0.0 1.0 NaN NaN
three NaN NaN 5.0 6.0
如果沿着axis=1对Series进行合并,则keys就会变成DataFrame的列头:
In [68]: pd.concat([s1,s2,s3], axis = 1, keys = ['one', 'two', 'three'])
Out[68]:
one two three
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
同样的逻辑对DataFrame对象也是一样:
In [69]: df1 = DataFrame(np.arange(6).reshape(3,2), index = ['a', 'b', 'c'], columns = ['one', 'two'])
In [70]: df2 = DataFrame(np.arange(4).reshape(2,2), index = ['a', 'c'], columns = ['three', 'four'])
In [71]: pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2'])
Out[71]:
level1 level2
one two three four
a 0 1 0.0 1.0
b 2 3 NaN NaN
c 4 5 2.0 3.0
如果传入的不是列表而是一个字典,则字典的键就会被当做keys选项的值:
In [72]: pd.concat({'level1': df1, 'level2': df2}, axis = 1)
Out[72]:
level1 level2
one two three four
a 0 1 0.0 1.0
b 2 3 NaN NaN
c 4 5 2.0 3.0
此外还有两个用于管理层次化索引创建方式的参数:
In [73]: pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2'], names = ['upper', 'lower'])
Out[73]:
upper level1 level2
lower one two three four
a 0 1 0.0 1.0
b 2 3 NaN NaN
c 4 5 2.0 3.0
最后一个需要考虑的问题是,跟当前分析工作无关的DataFrame行索引:
In [74]: df1 = DataFrame(np.random.randn(3,4), columns = ['a', 'b', 'c', 'd'])
In [75]: df2 = DataFrame(np.random.randn(2,3), columns = ['b', 'd', 'a'])
In [76]: df1
Out[76]:
a b c d
0 1.414313 0.436672 -0.288776 -0.286268
1 0.107107 -0.346125 -1.230709 0.743067
2 -1.584220 0.629817 -1.191732 -1.357533
In [77]: df2
Out[77]:
b d a
0 -0.236294 -0.440592 0.051193
1 -0.073595 -0.562664 1.288526
在这种情况下,传入ignore_index=True即可:
In [78]: pd.concat([df1, df2], ignore_index=True)
Out[78]:
a b c d
0 1.414313 0.436672 -0.288776 -0.286268
1 0.107107 -0.346125 -1.230709 0.743067
2 -1.584220 0.629817 -1.191732 -1.357533
3 0.051193 -0.236294 NaN -0.440592
4 1.288526 -0.073595 NaN -0.562664
concat函数的参数:
- objs:参与连接的pandas对象的列表或字典。唯一必须的参数
- axis:指明连接的轴向,默认为0
- join:“inner”、“outer”其中之一,默认为“outer”。指明其他轴向上的索引是按交集(inner)还是并集(outer)进行合并
- join_axes:指明用于其他n-1条轴的索引,不执行并集/交集运算
- keys:与连接对象有关的值,用于形成连接轴向上的层次化索引。可以是任意值的列表或数组、元组数组、数组列表(如果将levels设置成多级数组的话)
- levels:指定用作层次化索引各级别上的索引,如果设置了keys的话
- names:用于创建分层级别的名称,如果设置了keys和(或)levels的话
- verify_integrity:检查结果对象新轴上的重复情况,如果发现则引发异常。默认(False)允许重复
- ignore_index:不保留连接轴上的索引,产生一组新索引range(total_length)
合并重叠数据
还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,有可能索引全部或部分重叠的两个数据集。给这个例子增加一点启发性,我们使用NumPy的where函数,它用于表达一种矢量化的if-else:
In [79]: a = Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan], index = ['f', 'e', 'd', 'c', 'b', 'a'])
In [80]: b = Series(np.arange(len(a), dtype = np.float64),index = ['f' ,'e', 'd', 'c', 'b', 'a'])
In [81]: b[-1] = np.nan
In [82]: a
Out[82]:
f NaN
e 2.5
d NaN
c 3.5
b 4.5
a NaN
dtype: float64
In [83]: b
Out[83]:
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64
In [84]: np.where(pd.isnull(a), b, a)
Out[84]: array([ 0. , 2.5, 2. , 3.5, 4.5, nan])
Series有一个combine_first方法,实现的也是一样的功能,而且会进行数据对齐:
In [85]: b[:-2].combine_first(a[2:])
Out[85]:
a NaN
b 4.5
c 3.0
d 2.0
e 1.0
f 0.0
dtype: float64
对于DataFrame,combine_first自然也会在列上做同样的事情,因此可以将其看作:用参数对象的数据为调用者对象的缺失数据“打补丁”:
In [86]: df1 = DataFrame({'a': [1., np.nan, 5., np.nan],
...: 'b': [np.nan, 2., np.nan, 6.],
...: 'c': range(2, 18 ,4)})
In [87]: df2 = DataFrame({'a': [5., 4., np.nan, 3., 7.],
...: 'b': [np.nan, 3., 4., 6., 8.]})
In [88]: df1.combine_first(df2)
Out[88]:
a b c
0 1.0 NaN 2.0
1 4.0 2.0 6.0
2 5.0 4.0 10.0
3 3.0 6.0 14.0
4 7.0 8.0 NaN