之前自己对于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
data2
key
0
0
a
1
1
b
2
2
d
这是一种多对一的合并。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
data2
rkey
0
0
a
1
1
b
2
2
d
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
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' )
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对象的重叠列名上的字符串:
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实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的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.
0
1
a
0.0
0
b
1.0
1
f
NaN
5
g
NaN
6
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
one
two
a
0
1
b
2
3
c
4
5
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
8.4 总结
现在你已经掌握了pandas数据导入、清洗、重塑,我们可以进一步学习matplotlib数据可视化。我们在稍后会回到pandas,学习更高级的分析。