之前自己对于numpy和pandas是要用的时候东学一点西一点,直到看到《利用Python进行数据分析·第2版》 ,觉得只看这一篇就够了。非常感谢原博主的翻译和分享。
在许多应用中,数据可能分散在许多文件或数据库中,存储的形式也不利于分析。本章关注可以聚合、合并、重塑数据的方法。
首先,我会介绍pandas的层次化索引,它广泛用于以上操作。然后,我深入介绍了一些特殊的数据操作。在第14章,你可以看到这些工具的多种应用。
8.1 层次化索引
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引:
import pandas as pd
import numpy as np
data = pd. Series( np. random. randn( 9 ) , index= [ [ 'a' , 'a' , 'a' , 'b' , 'b' , 'c' , 'c' , 'd' , 'd' ] , [ 1 , 2 , 3 , 1 , 3 , 1 , 2 , 2 , 3 ] ] )
data
a 1 -0.626605
2 -0.099608
3 1.832826
b 1 0.764571
3 1.241071
c 1 -1.656578
2 -0.837847
d 2 -1.563933
3 1.566208
dtype: float64
看到的结果是经过美化的带有MultiIndex索引的Series的格式。索引之间的“间隔”表示“直接使用上面的标签”:
data. index
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])
对于一个层次化索引的对象,可以使用所谓的部分索引,使用它选取数据子集的操作更简单:
data[ 'b' ]
1 0.764571
3 1.241071
dtype: float64
data[ 'b' : 'c' ]
b 1 0.764571
3 1.241071
c 1 -1.656578
2 -0.837847
dtype: float64
data. loc[ [ 'b' , 'c' ] ]
b 1 0.764571
3 1.241071
c 1 -1.656578
2 -0.837847
dtype: float64
有时甚至还可以在“内层”中进行选取:
data. loc[ : , 2 ]
a -0.099608
c -0.837847
d -1.563933
dtype: float64
这里是Series,[:,2]中逗号的两边都是表示行索引,前者是外层索引,后者是内层索引
层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。例如,可以通过unstack方法将这段数据重新安排到一个DataFrame中:
data. unstack( )
1 2 3 a -0.626605 -0.099608 1.832826 b 0.764571 NaN 1.241071 c -1.656578 -0.837847 NaN d NaN -1.563933 1.566208
unstack的逆运算是stack:
data. unstack( ) . stack( )
a 1 -0.626605
2 -0.099608
3 1.832826
b 1 0.764571
3 1.241071
c 1 -1.656578
2 -0.837847
d 2 -1.563933
3 1.566208
dtype: float64
stack和unstack将在本章后面详细讲解。
对于一个DataFrame,每条轴都可以有分层索引:
frame = pd. DataFrame( np. arange( 12 ) . reshape( ( 4 , 3 ) ) , index= [ [ 'a' , 'a' , 'b' , 'b' ] , [ 1 , 2 , 1 , 2 ] ] , columns= [ [ 'Ohio' , 'Ohio' , 'Colorado' ] , [ 'Green' , 'Red' , 'Green' ] ] )
frame
Ohio Colorado Green Red Green a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11
各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:
frame. index. names= [ 'key1' , 'key2' ]
frame. columns. names= [ 'state' , 'color' ]
frame
state Ohio Colorado color Green Red Green key1 key2 a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11
注意:小心区分索引名state、color与行标签。
有了部分列索引,因此可以轻松选取列分组:
frame[ 'Ohio' ]
color Green Red key1 key2 a 1 0 1 2 3 4 b 1 6 7 2 9 10
可以单独创建MultiIndex然后复用。上面那个DataFrame中的(带有分级名称)列可以这样创建:
from pandas import MultiIndex
MultiIndex. from_arrays( [ [ 'Ohio' , 'Ohio' , 'Colorado' ] , [ 'Green' , 'Red' , 'Green' ] ] , names= [ 'state' , 'color' ] )
MultiIndex(levels=[['Colorado', 'Ohio'], ['Green', 'Red']],
labels=[[1, 1, 0], [0, 1, 0]],
names=['state', 'color'])
重排与分级排序
有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):
frame. swaplevel( 'key1' , 'key2' )
state Ohio Colorado color Green Red Green key2 key1 1 a 0 1 2 2 a 3 4 5 1 b 6 7 8 2 b 9 10 11
而sort_index则根据单个级别中的值对数据进行排序。交换级别时,常常也会用到sort_index,这样最终结果就是按照指定顺序进行字母排序了:
frame. sort_index( level= 1 )
state Ohio Colorado color Green Red Green key1 key2 a 1 0 1 2 b 1 6 7 8 a 2 3 4 5 b 2 9 10 11
frame. swaplevel( 0 , 1 ) . sort_index( level= 0 )
state Ohio Colorado color Green Red Green key2 key1 1 a 0 1 2 b 6 7 8 2 a 3 4 5 b 9 10 11
根据级别汇总统计
许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。再以上面那个DataFrame为例,我们可以根据行或列上的级别来进行求和:level指的是保留的索引,按这个索引来分组
frame. sum ( )
state color
Ohio Green 18
Red 22
Colorado Green 26
dtype: int64
frame. sum ( level= 'key2' )
state Ohio Colorado color Green Red Green key2 1 6 8 10 2 12 14 16
frame. sum ( level= 'color' , axis= 1 )
color Green Red key1 key2 a 1 2 1 2 8 4 b 1 14 7 2 20 10
这其实是利用了pandas的groupby功能,本书稍后将对其进行详细讲解。
使用DataFrame的列进行索引
人们经常想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame的列。以下面这个DataFrame为例:
frame = pd. DataFrame( { 'a' : range ( 7 ) , 'b' : range ( 7 , 0 , - 1 ) , 'c' : [ 'one' , 'one' , 'one' , 'two' , 'two' , 'two' , 'two' ] , 'd' : [ 0 , 1 , 2 , 0 , 1 , 2 , 3 ] } )
frame
a b c d 0 0 7 one 0 1 1 6 one 1 2 2 5 one 2 3 3 4 two 0 4 4 3 two 1 5 5 2 two 2 6 6 1 two 3
DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:
frame2= frame. set_index( [ 'c' , 'd' ] )
frame2
a b c d one 0 0 7 1 1 6 2 2 5 two 0 3 4 1 4 3 2 5 2 3 6 1
默认情况下,那些列会从DataFrame中移除,但也可以将其保留下来:
frame. set_index( [ 'c' , 'd' ] , drop= False )
a b c d c d one 0 0 7 one 0 1 1 6 one 1 2 2 5 one 2 two 0 3 4 two 0 1 4 3 two 1 2 5 2 two 2 3 6 1 two 3
reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:
frame2. reset_index( )
c d a b 0 one 0 0 7 1 one 1 1 6 2 one 2 2 5 3 two 0 3 4 4 two 1 4 3 5 two 2 5 2 6 two 3 6 1
8.2 合并数据集
pandas对象中的数据可以通过一些方式进行合并:
pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。SQL或其他关系型数据库的用户对此应该会比较熟悉,因为它实现的就是数据库的join操作。 pandas.concat可以沿着一条轴将多个对象堆叠到一起。 实例方法combine_first可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。
我将分别对它们进行讲解,并给出一些例子。本书剩余部分的示例中将经常用到它们。
数据库风格的DataFrame合并
数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库(基于SQL)的核心。pandas的merge函数是对数据应用这些算法的主要切入点。
以一个简单的例子开始:
df1 = pd. DataFrame( { 'key' : [ 'b' , 'b' , 'a' , 'c' , 'a' , 'a' , 'b' ] , 'data1' : range ( 7 ) } )
df2 = pd. DataFrame( { 'key' : [ 'a' , 'b' , 'd' ] , 'data2' : range ( 3 ) } )
df1
data1 key 0 0 b 1 1 b 2 2 a 3 3 c 4 4 a 5 5 a 6 6 b
df2
这是一种多对一的合并。df1中的数据有多个被标记为a和b的行,而df2中key列的每个值则仅对应一行。对这些对象调用merge即可得到:
pd. merge( df1, df2)
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就会将重叠列的列名当做键。不过,最好明确指定一下:
pd. merge( df1, df2, on= 'key' )
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
如果两个对象的列名不同,也可以分别进行指定:
df3 = pd. DataFrame( { 'lkey' : [ 'b' , 'b' , 'a' , 'c' , 'a' , 'a' , 'b' ] , 'data1' : range ( 7 ) } )
df4 = pd. DataFrame( { 'rkey' : [ 'a' , 'b' , 'd' ] , 'data2' : range ( 3 ) } )
df3
data1 lkey 0 0 b 1 1 b 2 2 a 3 3 c 4 4 a 5 5 a 6 6 b
df4
pd. merge( df3, df4, left_on= 'lkey' , right_on= 'rkey' )
data1 lkey data2 rkey 0 0 b 1 b 1 1 b 1 b 2 6 b 1 b 3 2 a 0 a 4 4 a 0 a 5 5 a 0 a
可能你已经注意到了,结果里面c和d以及与之相关的数据消失了。默认情况下,merge做的是“内连接”;结果中的键是交集。其他方式还有"left"、“right"以及"outer”。外连接求取的是键的并集,组合了左连接和右连接的效果:
pd. merge( df1, df2, how= 'outer' )
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
表8-1对这些选项进行了总结。
选项 说明 inner 使用两个表都有的键 left 使用左表中所有的键 right 使用右表中所有的键 outer 使用两个表中所有的键
表8-1 不同的连接类型
多对多的合并有些不直观。看下面的例子:
df1 = pd. DataFrame( { 'key' : [ 'b' , 'b' , 'a' , 'c' , 'a' , 'b' ] , 'data1' : range ( 6 ) } )
df2 = pd. DataFrame( { 'key' : [ 'a' , 'b' , 'a' , 'b' , 'd' ] , 'data2' : range ( 5 ) } )
df1
data1 key 0 0 b 1 1 b 2 2 a 3 3 c 4 4 a 5 5 b
df2
pd. merge( df1, df2, on= 'key' , how= 'left' )
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"行。连接方式只影响出现在结果中的不同的键的值:
pd. merge( df1, df2, on= 'key' , how= 'inner' )
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
要根据多个键进行合并,传入一个由列名组成的列表即可:
left = pd. DataFrame( { 'key1' : [ 'foo' , 'foo' , 'bar' ] , 'key2' : [ 'one' , 'two' , 'one' ] , 'lval' : [ 1 , 2 , 3 ] } )
right = pd. DataFrame( { 'key1' : [ 'foo' , 'foo' , 'bar' , 'bar' ] , 'key2' : [ 'one' , 'one' , 'one' , 'two' ] , 'rval' : [ 4 , 5 , 6 , 7 ] } )
left
key1 key2 lval 0 foo one 1 1 foo two 2 2 bar one 3
right
key1 key2 rval 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' )
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
结果中会出现哪些键组合取决于所选的合并方式,你可以这样来理解:多个键形成一系列元组,并将其当做单个连接键(当然,实际上并不是这么回事)。
注意:在进行列-列连接时,DataFrame对象中的索引会被丢弃。
对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(查看前面介绍的重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:
(merge就相当于mysql中不用on用using的join,对于用来连接的列,如果是双方共有,那么会去除冗余列;如果是通过left_on和right_on将名字不同都数据一样的列作为连接列的话,那么两者都会保留;虽然双方还有其他相同名字的字段,但只要没有参与连接,那么就都会保留,只不过默认会加上_x和_y后缀;)
pd. merge( left, right, on= 'key1' )
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
pd. merge( left, right, on= 'key1' , suffixes= [ '_left' , '_right' ] )
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的参数请参见表8-2。使用DataFrame的行索引合并是下一节的主题。
表8-2 merge函数的参数
参数 说明 left 参与合并的左侧 Data Frame right 参与合并的右侧 Data Frame how "inner","outer"、"left"、 "right"其中之ー。认为inner on 用于连接的列名。必须存在于左右两个 Data Frame对象中。如果未指定,且其他连接键也未指定,则以left和right列名的交集作为连接键 left_on 左侧 Data Frame中用作连接健的列 right_on 右侧 Dataframe r中用作连接键的列 left_index 将左侧的行索引用作其连接键 right_index 类似于 left_ndex sort 根据连接健对合并后的数据进行排序,默认为True,有时在处理大数据集时,禁用该选项可获得更好的性能 suffixes 字符串值元组,用于追加到重复列名的末尾,默认为(’_x’,’_y’)。例如,如果左右两个 Dataframe对象都有"data”,则结果中就会出现’data_x’和’data_y’ copy 设置为 False,可以在某些特殊情况下避免将数据复制到结果数据结构中。默认总是复制 indicator 添加特殊的列_merge,它可以指明每个行的来源,它的值有left_only、right_only或both,根据每行的合并数据的来源。
索引上的合并
有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键:
left1 = pd. DataFrame( { 'key' : [ 'a' , 'b' , 'a' , 'a' , 'b' , 'c' ] , 'value' : range ( 6 ) } )
right1 = pd. DataFrame( { 'group_val' : [ 3.5 , 7 ] } , index= [ 'a' , 'b' ] )
left1
key value 0 a 0 1 b 1 2 a 2 3 a 3 4 b 4 5 c 5
right1
pd. merge( left1, right1, left_on= 'key' , right_index= True )
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方法是求取连接键的交集,因此你可以通过外连接的方式得到它们的并集:
pd. merge( left1, right1, left_on= 'key' , right_index= True , how= 'outer' )
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
对于层次化索引的数据,事情就有点复杂了,因为索引的合并默认是多键合并:
lefth = pd. DataFrame( { 'key1' : [ 'Ohio' , 'Ohio' , 'Ohio' , 'Nevada' , 'Nevada' ] , 'key2' : [ 2000 , 2001 , 2002 , 2001 , 2002 ] , 'data' : np. arange( 5 . ) } )
righth = pd. DataFrame( np. arange( 12 ) . reshape( ( 6 , 2 ) ) , index= [ [ 'Nevada' , 'Nevada' , 'Ohio' , 'Ohio' , 'Ohio' , 'Ohio' ] , [ 2001 , 2000 , 2000 , 2000 , 2001 , 2002 ] ] , columns= [ 'event1' , 'event2' ] )
lefth
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
righth
event1 event2 Nevada 2001 0 1 2000 2 3 Ohio 2000 4 5 2000 6 7 2001 8 9 2002 10 11
这种情况下,你必须以列表的形式指明用作合并键的多个列(注意用how='outer’对重复索引值的处理):
pd. merge( lefth, righth, left_on= [ 'key1' , 'key2' ] , right_index= True )
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
pd. merge( lefth, righth, left_on= [ 'key1' , 'key2' ] , right_index= True , how= 'outer' )
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
同时使用合并双方的索引也没问题
left2 = pd. DataFrame( [ [ 1 . , 2 . ] , [ 3 . , 4 . ] , [ 5 . , 6 . ] ] , index= [ 'a' , 'c' , 'e' ] , columns= [ 'Ohio' , 'Nevada' ] )
right2 = pd. DataFrame( [ [ 7 . , 8 . ] , [ 9 . , 10 . ] , [ 11 . , 12 . ] , [ 13 , 14 ] ] , index= [ 'b' , 'c' , 'd' , 'e' ] , columns= [ 'Missouri' , 'Alabama' ] )
left2
Ohio Nevada a 1.0 2.0 c 3.0 4.0 e 5.0 6.0
right2
Missouri Alabama b 7.0 8.0 c 9.0 10.0 d 11.0 12.0 e 13.0 14.0
pd. merge( left2, right2, left_index= True , right_index= True , how= 'outer' )
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
DataFrame还有一个便捷的join实例方法(似乎默认是通过索引来连接的,也可以用on连指明用列来连接,未指明的就用索引),它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的DataFrame对象,但要求没有重叠的列。在上面那个例子中,我们可以编写:
left2. join( right2, how= 'outer' )
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
left1. join( right1, on= 'key' )
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函数,也能实现此功能:
another = pd. DataFrame( [ [ 7 . , 8 . ] , [ 9 . , 10 . ] , [ 11 . , 12 . ] , [ 16 . , 17 . ] ] , index= [ 'a' , 'c' , 'e' , 'f' ] , columns= [ 'New York' , 'Oregon' ] )
another
New York Oregon a 7.0 8.0 c 9.0 10.0 e 11.0 12.0 f 16.0 17.0
left2. join( [ right2, another] )
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
left2. join( [ right2, another] , how= 'outer' )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\pandas\core\frame.py:6369: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
verify_integrity=True)
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的concatenation函数可以用NumPy数组来做:
arr= np. arange( 12 ) . reshape( 4 , 3 )
arr
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
np. concatenate( [ arr, arr] , axis= 1 )
array([[ 0, 1, 2, 0, 1, 2],
[ 3, 4, 5, 3, 4, 5],
[ 6, 7, 8, 6, 7, 8],
[ 9, 10, 11, 9, 10, 11]])
对于pandas对象(如Series和DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。具体点说,你还需要考虑以下这些东西:
如果对象在其它轴上的索引不同,我们应该合并这些轴的不同元素还是只使用交集? 连接的数据集是否需要在结果对象中可识别? 连接轴中保存的数据是否需要保留?许多情况下,DataFrame默认的整数标签最好在连接时删掉。
pandas的concat函数提供了一种能够解决这些问题的可靠方式。我将给出一些例子来讲解其使用方式。假设有三个没有重叠索引的Series:
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] )
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是列):
pd. concat( [ s1, s2, s3] , axis= 1 )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
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
这种情况下,另外的轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。merge默认的是内连接,concat默认的是外连接 传入join='inner’即可得到它们的交集:
s4= pd. concat( [ s1, s3] )
s4
a 0
b 1
f 5
g 6
dtype: int64
pd. concat( [ s1, s4] , axis= 1 )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
pd. concat( [ s1, s4] , axis= 1 , join= 'inner' )
在这个例子中,f和g标签消失了,是因为使用的是join='inner’选项。
你可以通过join_axes指定要在其它轴上使用的索引:
pd. concat( [ s1, s4] , axis= 1 , join_axes= [ [ 'a' , 'c' , 'b' , 'e' ] ] )
0 1 a 0.0 0.0 c NaN NaN b 1.0 1.0 e NaN NaN
不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用keys参数即可达到这个目的:
result = pd. concat( [ s1, s1, s3] , keys= [ 'one' , 'two' , 'three' ] )
result
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64
result. unstack( )
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的列头:
pd. concat( [ s1, s2, s3] , axis= 1 , keys= [ 'one' , 'two' , 'three' ] )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
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对象:
df1 = pd. DataFrame( np. arange( 6 ) . reshape( 3 , 2 ) , index= [ 'a' , 'b' , 'c' ] , columns= [ 'one' , 'two' ] )
df2 = pd. DataFrame( 5 + np. arange( 4 ) . reshape( 2 , 2 ) , index= [ 'a' , 'c' ] , columns= [ 'three' , 'four' ] )
df1
df2
pd. concat( [ df1, df2] , axis= 1 , keys= [ 'level1' , 'level2' ] )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
level1 level2 one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0
如果传入的不是列表而是一个字典,则字典的键就会被当做keys选项的值:
pd. concat( { 'level1' : df1, 'level2' : df2} , axis= 1 )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
level1 level2 one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0
此外还有两个用于管理层次化索引创建方式的参数(参见表8-3)。举个例子,我们可以用names参数命名创建的轴级别:
pd. concat( [ df1, df2] , axis= 1 , keys= [ 'upper' , 'lower' ] , names= [ 'upper' , 'lower' ] )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
upper upper lower lower one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0
最后一个关于DataFrame的问题是,DataFrame的行索引不包含任何相关数据:
df1= pd. DataFrame( np. random. randn( 3 , 4 ) , columns= [ 'a' , 'b' , 'c' , 'd' ] )
df2= pd. DataFrame( np. random. randn( 2 , 3 ) , columns= [ 'b' , 'd' , 'a' ] )
df1
a b c d 0 0.614738 0.287790 1.576790 0.400055 1 0.949942 0.680643 0.602513 -1.720876 2 1.328322 -0.555095 -1.141523 -1.677611
df2
b d a 0 -0.380397 -1.607073 0.019430 1 0.759844 0.535255 1.879705
在这种情况下,传入ignore_index=True即可:
pd. concat( [ df1, df2] )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
a b c d 0 0.614738 0.287790 1.576790 0.400055 1 0.949942 0.680643 0.602513 -1.720876 2 1.328322 -0.555095 -1.141523 -1.677611 0 0.019430 -0.380397 NaN -1.607073 1 1.879705 0.759844 NaN 0.535255
pd. concat( [ df1, df2] , ignore_index= True )
c:\users\qingt\miniconda2\envs\python35\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
a b c d 0 0.614738 0.287790 1.576790 0.400055 1 0.949942 0.680643 0.602513 -1.720876 2 1.328322 -0.555095 -1.141523 -1.677611 3 0.019430 -0.380397 NaN -1.607073 4 1.879705 0.759844 NaN 0.535255
参数 说明 objs 参与连接的 pandas对象的列表或字典。唯一必需的参数 axis 指明连接的轴向,默认为0 join Inner、outer”其中之一,默认为“ outer”。指明其他轴向上的索引是按交集( Inner)还是并集( outer)进行合并 join_axes 指明用于其他η-1条轴的索引,不执行并集/交集运算 keys 与连接对象有关的值,用于形成连接轴向上的层次化索引。可以是任意值的列表或数组、元组数组、数组列表(如果将level设置成多级数组的话) levels 指定用作层次化索引各级别上的索引,如果设置了keys的话 names 用于创建分层级别的名称,如果设置了keys和(或) levels的话 verify_integrity 检查结果对象新轴上的重复情况,如果发现则引发异常。默认( False)允许重复 ignore_index 不保留连接轴上的索引,产生一组新索引 range(total_length)
表8-3 concat函数的参数
合并重叠数据
还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,你可能有索引全部或部分重叠的两个数据集。举个有启发性的例子,我们使用NumPy的where函数,它表示一种等价于面向数组的if-else:
a = pd. Series( [ np. nan, 2.5 , np. nan, 3.5 , 4.5 , np. nan] , index= [ 'f' , 'e' , 'd' , 'c' , 'b' , 'a' ] )
b = pd. Series( np. arange( len ( a) , dtype= np. float64) , index= [ 'f' , 'e' , 'd' , 'c' , 'b' , 'a' ] )
b[ - 1 ] = np. nan
a
f NaN
e 2.5
d NaN
c 3.5
b 4.5
a NaN
dtype: float64
b
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64
np. where( pd. isnull( a) , b, a)
array([0. , 2.5, 2. , 3.5, 4.5, nan])
Series有一个combine_first方法,实现的也是一样的功能,还带有pandas的数据对齐:
b. combine_first( a)
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64
a. combine_first( b)
f 0.0
e 2.5
d 2.0
c 3.5
b 4.5
a NaN
dtype: float64
b[ : - 2 ] . combine_first( a[ 2 : ] )
a NaN
b 4.5
c 3.0
d 2.0
e 1.0
f 0.0
dtype: float64
通过对比,可以看到combine_first实现的就是如果前面这个Series中对应元素存在则有前面这个,否则用后面这个。
对于DataFrame,combine_first自然也会在列上做同样的事情,因此你可以将其看做:用传递对象中的数据为调用对象的缺失数据“打补丁”(这个比喻很形象):
df1 = pd. DataFrame( { 'a' : [ 1 . , np. nan, 5 . , np. nan] , 'b' : [ np. nan, 2 . , np. nan, 6 . ] , 'c' : range ( 2 , 18 , 4 ) } )
df2 = pd. DataFrame( { 'a' : [ 5 . , 4 . , np. nan, 3 . , 7 . ] , 'b' : [ np. nan, 3 . , 4 . , 6 . , 8 . ] } )
df1
a b c 0 1.0 NaN 2 1 NaN 2.0 6 2 5.0 NaN 10 3 NaN 6.0 14
df2
a b 0 5.0 NaN 1 4.0 3.0 2 NaN 4.0 3 3.0 6.0 4 7.0 8.0
df1. combine_first( df2)
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
8.3 重塑和轴向旋转
有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。
重塑层次化索引
层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:
stack:将数据的列“旋转”为行。 unstack:将数据的行“旋转”为列。
我将通过一系列的范例来讲解这些操作。接下来看一个简单的DataFrame,其中的行列索引均为字符串数组:
data = pd. DataFrame( np. arange( 6 ) . reshape( ( 2 , 3 ) ) , index= pd. Index( [ 'Ohio' , 'Colorado' ] , name= 'state' ) , columns= pd. Index( [ 'one' , 'two' , 'three' ] , name= 'number' ) )
data
number one two three state Ohio 0 1 2 Colorado 3 4 5
对该数据使用stack方法即可将列转换为行,得到一个Series:
result = data. stack( )
result
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int32
对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:
result. unstack( )
number one two three state Ohio 0 1 2 Colorado 3 4 5
默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其它级别进行unstack操作:
result. unstack( 0 )
state Ohio Colorado number one 0 3 two 1 4 three 2 5
result. unstack( 'state' )
state Ohio Colorado number one 0 3 two 1 4 three 2 5
如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:
s1 = pd. Series( [ 0 , 1 , 2 , 3 ] , index= [ 'a' , 'b' , 'c' , 'd' ] )
s2 = pd. Series( [ 4 , 5 , 6 ] , index= [ 'c' , 'd' , 'e' ] )
data2= pd. concat( [ s1, s2] , keys= [ 'one' , 'two' ] )
data2
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64
data2. unstack( )
a b c d e one 0.0 1.0 2.0 3.0 NaN two NaN NaN 4.0 5.0 6.0
stack默认会滤除缺失数据,因此该运算是可逆的:
data2. unstack( ) . stack( )
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
data2. unstack( ) . stack( dropna= False )
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:
df = pd. DataFrame( { 'left' : result, 'right' : result + 5 } , columns= pd. Index( [ 'left' , 'right' ] , name= 'side' ) )
df
side left right state number Ohio one 0 5 two 1 6 three 2 7 Colorado one 3 8 two 4 9 three 5 10
df. unstack( )
side left right number one two three one two three state Ohio 0 1 2 5 6 7 Colorado 3 4 5 8 9 10
当调用stack,我们可以指明轴的名字:
df. unstack( 'state' ) . stack( 'side' )
state Colorado Ohio number side one left 3 0 right 8 5 two left 4 1 right 9 6 three left 5 2 right 10 7
将“长格式”旋转为“宽格式”
data = pd. read_csv( 'examples/macrodata.csv' )
data. head( )
year quarter realgdp realcons realinv realgovt realdpi cpi m1 tbilrate unemp pop infl realint 0 1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.98 139.7 2.82 5.8 177.146 0.00 0.00 1 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.15 141.7 3.08 5.1 177.830 2.34 0.74 2 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.35 140.5 3.82 5.3 178.657 2.74 1.09 3 1959.0 4.0 2785.204 1753.7 299.356 484.052 1931.3 29.37 140.0 4.33 5.6 179.386 0.27 4.06 4 1960.0 1.0 2847.699 1770.5 331.722 462.199 1955.5 29.54 139.6 3.50 5.2 180.007 2.31 1.19
periods = pd. PeriodIndex( year= data. year, quarter= data. quarter, name= 'date' )
periods
PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
'1960Q3', '1960Q4', '1961Q1', '1961Q2',
...
'2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
'2008Q4', '2009Q1', '2009Q2', '2009Q3'],
dtype='period[Q-DEC]', name='date', length=203, freq='Q-DEC')
columns= pd. Index( [ 'realgdp' , 'infl' , 'unemp' ] , name= 'item' )
columns
Index(['realgdp', 'infl', 'unemp'], dtype='object', name='item')
data= data. reindex( columns= columns)
data. head( )
item realgdp infl unemp 0 2710.349 0.00 5.8 1 2778.801 2.34 5.1 2 2775.488 2.74 5.3 3 2785.204 0.27 5.6 4 2847.699 2.31 5.2
data. index= periods. to_timestamp( 'D' , 'end' )
data. head( )
item realgdp infl unemp date 1959-03-31 2710.349 0.00 5.8 1959-06-30 2778.801 2.34 5.1 1959-09-30 2775.488 2.74 5.3 1959-12-31 2785.204 0.27 5.6 1960-03-31 2847.699 2.31 5.2
data. stack( ) [ : 5 ]
date item
1959-03-31 realgdp 2710.349
infl 0.000
unemp 5.800
1959-06-30 realgdp 2778.801
infl 2.340
dtype: float64
data. stack( ) . reset_index( ) . head( )
date item 0 0 1959-03-31 realgdp 2710.349 1 1959-03-31 infl 0.000 2 1959-03-31 unemp 5.800 3 1959-06-30 realgdp 2778.801 4 1959-06-30 infl 2.340
data. stack( ) . reset_index( ) . rename( columns= { 0 : 'value' } ) . head( )
date item value 0 1959-03-31 realgdp 2710.349 1 1959-03-31 infl 0.000 2 1959-03-31 unemp 5.800 3 1959-06-30 realgdp 2778.801 4 1959-06-30 infl 2.340
ldata = data. stack( ) . reset_index( ) . rename( columns= { 0 : 'value' } )
这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这里,我们的键是date和item)的长格式。表中的每行代表一次观察。
关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加,item列中的值的种类能够增加。在前面的例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。有的情况下,使用这样的数据会很麻烦,你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间戳则用作索引。DataFrame的pivot方法完全可以实现这个转换:
pivoted= ldata. pivot( 'date' , 'item' , 'value' )
pivoted. head( )
item infl realgdp unemp date 1959-03-31 0.00 2710.349 5.8 1959-06-30 2.34 2778.801 5.1 1959-09-30 2.74 2775.488 5.3 1959-12-31 0.27 2785.204 5.6 1960-03-31 2.31 2847.699 5.2
前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充DataFrame的数据列。假设有两个需要同时重塑的数据列:
ldata[ 'value2' ] = np. random. randn( len ( ldata) )
ldata[ : 10 ]
date item value value2 0 1959-03-31 realgdp 2710.349 1.206744 1 1959-03-31 infl 0.000 0.595277 2 1959-03-31 unemp 5.800 -1.801465 3 1959-06-30 realgdp 2778.801 -0.547529 4 1959-06-30 infl 2.340 0.425993 5 1959-06-30 unemp 5.100 -0.061648 6 1959-09-30 realgdp 2775.488 1.832243 7 1959-09-30 infl 2.740 1.031324 8 1959-09-30 unemp 5.300 0.666682 9 1959-12-31 realgdp 2785.204 0.601897
如果忽略最后一个参数,得到的DataFrame就会带有层次化的列:
pivoted = ldata. pivot( 'date' , 'item' )
pivoted[ : 5 ]
value value2 item infl realgdp unemp infl realgdp unemp date 1959-03-31 0.00 2710.349 5.8 0.595277 1.206744 -1.801465 1959-06-30 2.34 2778.801 5.1 0.425993 -0.547529 -0.061648 1959-09-30 2.74 2775.488 5.3 1.031324 1.832243 0.666682 1959-12-31 0.27 2785.204 5.6 -0.305323 0.601897 1.072977 1960-03-31 2.31 2847.699 5.2 -0.517172 -0.682214 -0.211235
pivoted[ 'value' ] [ : 5 ]
item infl realgdp unemp date 1959-03-31 0.00 2710.349 5.8 1959-06-30 2.34 2778.801 5.1 1959-09-30 2.74 2775.488 5.3 1959-12-31 0.27 2785.204 5.6 1960-03-31 2.31 2847.699 5.2
注意,pivot其实就是用set_index创建层次化索引,再用unstack重塑:
unstacked = ldata. set_index( [ 'date' , 'item' ] ) . unstack( 'item' )
unstacked[ : 7 ]
value value2 item infl realgdp unemp infl realgdp unemp date 1959-03-31 0.00 2710.349 5.8 0.595277 1.206744 -1.801465 1959-06-30 2.34 2778.801 5.1 0.425993 -0.547529 -0.061648 1959-09-30 2.74 2775.488 5.3 1.031324 1.832243 0.666682 1959-12-31 0.27 2785.204 5.6 -0.305323 0.601897 1.072977 1960-03-31 2.31 2847.699 5.2 -0.517172 -0.682214 -0.211235 1960-06-30 0.14 2834.390 5.2 1.832529 -0.692483 1.585651 1960-09-30 2.70 2839.022 5.6 0.049498 -2.054472 -1.054945
将“宽格式”旋转为“长格式”
旋转DataFrame的逆运算是pandas.melt。它不是将一列转换到多个新的DataFrame,而是合并多个列成为一个,产生一个比输入长的DataFrame。看一个例子:
df = pd. DataFrame( { 'key' : [ 'foo' , 'bar' , 'baz' ] , 'A' : [ 1 , 2 , 3 ] , 'B' : [ 4 , 5 , 6 ] , 'C' : [ 7 , 8 , 9 ] } )
df
A B C key 0 1 4 7 foo 1 2 5 8 bar 2 3 6 9 baz
key列可能是分组指标,其它的列是数据值。当使用pandas.melt,我们必须指明哪些列是分组指标。下面使用key作为唯一的分组指标:
melted= pd. melt( df, [ 'key' ] )
melted
key variable value 0 foo A 1 1 bar A 2 2 baz A 3 3 foo B 4 4 bar B 5 5 baz B 6 6 foo C 7 7 bar C 8 8 baz C 9
使用pivot,可以重塑回原来的样子:
reshaped = melted. pivot( 'key' , 'variable' , 'value' )
reshaped
variable A B C key bar 2 5 8 baz 3 6 9 foo 1 4 7
因为pivot的结果从列创建了一个索引,用作行标签,我们可以使用reset_index将数据移回列:
reshaped. reset_index( )
variable key A B C 0 bar 2 5 8 1 baz 3 6 9 2 foo 1 4 7
你还可以指定列的子集,作为值的列:
pd. melt( df, id_vars= [ 'key' ] , value_vars= [ 'A' , 'B' ] )
key variable value 0 foo A 1 1 bar A 2 2 baz A 3 3 foo B 4 4 bar B 5 5 baz B 6
pandas.melt也可以不用分组指标:
pd. melt( df, value_vars= [ 'A' , 'B' , 'C' ] )
variable value 0 A 1 1 A 2 2 A 3 3 B 4 4 B 5 5 B 6 6 C 7 7 C 8 8 C 9
pd. melt( df, value_vars= [ 'key' , 'A' , 'B' ] )
variable value 0 key foo 1 key bar 2 key baz 3 A 1 4 A 2 5 A 3 6 B 4 7 B 5 8 B 6
parent_teacher_data[ 'address' ] = parent_teacher_data[ 'country' ] + parent_teacher_data[ 'province' ] + parent_teacher_data[ 'city' ] + parent_teacher_data[ 'county' ]
如果某一列是非str类型的数据,那么我们需要用到map(str)将那一列数据类型做转换:
dataframe[ "newColumn" ] = dataframe[ "age" ] . map ( str ) + dataframe[ "phone" ] + dataframe[ "address”]
8.4 总结
现在你已经掌握了pandas数据导入、清洗、重塑,我们可以进一步学习matplotlib数据可视化。我们在稍后会回到pandas,学习更高级的分析。