基本功能
本节中,我将介绍操作Series和DataFrame中的数据的基本手段。后续章节将更加深入地挖掘pandas在数据分析和处理方面的功能。本书不是pandas库的详尽文档,主要关注的是最重要的功能,那些不大常用的内容(也就是那些更深奥的内容)就交给你自己去摸索吧。
重新索引
pandas对象的一个重要方法是reindex,其作用是创建一个适应新索引的新对象。以之前的一个简单示例来说:
- In [79]: obj = Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
- In [80]: obj
- Out[80]:
- d 4.5
- b 7.2
- a -5.3
- c 3.6
调用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值:
- In [81]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
- In [82]: obj2
- Out[82]:
- a -5.3
- b 7.2
- c 3.6
- d 4.5
- e NaN
- In [83]: obj.reindex(['a', 'b', 'c', 'd', 'e'], fill_value=0)
- Out[83]:
- a -5.3
- b 7.2
- c 3.6
- d 4.5
- e 0.0
对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method选项即可达到此目的,例如,使用ffill可以实现前向值填充:
- In [84]: obj3 = Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
- In [85]: obj3.reindex(range(6), method='ffill')
- Out[85]:
- 0 blue
- 1 blue
- 2 purple
- 3 purple
- 4 yellow
- 5 yellow
表5-4列出了可用的method选项。其实我们有时需要比前向和后向填充更为精准的插值方式。
对于DataFrame,reindex可以修改(行)索引、列,或两个都修改。如果仅传入一个序列,则会重新索引行:
- In [86]: frame = DataFrame(np.arange(9).reshape((3, 3)), index=['a', 'c', 'd'],
- ...: columns=['Ohio', 'Texas', 'California'])
- In [87]: frame
- Out[87]:
- Ohio Texas California
- a 0 1 2
- c 3 4 5
- d 6 7 8
- In [88]: frame2 = frame.reindex(['a', 'b', 'c', 'd'])
- In [89]: frame2
- Out[89]:
- Ohio Texas California
- a 0 1 2
- b NaN NaN NaN
- c 3 4 5
- d 6 7 8
使用columns关键字即可重新索引列:
- In [90]: states = ['Texas', 'Utah', 'California']
- In [91]: frame.reindex(columns=states)
- Out[91]:
- Texas Utah California
- a 1 NaN 2
- c 4 NaN 5
- d 7 NaN 8
也可以同时对行和列进行重新索引,而插值则只能按行应用(即轴0):
- In [92]: frame.reindex(index=['a', 'b', 'c', 'd'], method='ffill',
- .....: columns=states)
- Out[92]:
- Texas Utah California
- a 1 NaN 2
- b 1 NaN 2
- c 4 NaN 5
- d 7 NaN 8
利用ix的标签索引功能,重新索引任务可以变得更简洁:
- In [93]: frame.ix[['a', 'b', 'c', 'd'], states]
- Out[93]:
- Texas Utah California
- a 1 NaN 2
- b NaN NaN NaN
- c 4 NaN 5
- d 7 NaN 8
表5-5列出了reindex函数的各参数及说明。
丢弃指定轴上的项
丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以drop方法返回的是一个在指定轴上删除了指定值的新对象:
- In [94]: obj = Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
- In [95]: new_obj = obj.drop('c')
- In [96]: new_obj
- Out[96]:
- a 0
- b 1
- d 3
- e 4
- In [97]: obj.drop(['d', 'c'])
- Out[97]:
- a 0
- b 1
- e 4
对于DataFrame,可以删除任意轴上的索引值:
- In [98]: data = DataFrame(np.arange(16).reshape((4, 4)),
- ...: index=['Ohio', 'Colorado', 'Utah', 'New York'],
- ...: columns=['one', 'two', 'three', 'four'])
- In [99]: data.drop(['Colorado', 'Ohio'])
- Out[99]:
- one two three four
- Utah 8 9 10 11
- New York 12 13 14 15
- In [100]: data.drop('two', axis=1) [101]: data.drop(['two', 'four'], axis=1)
- Out[100]: Out[101]:
- one three four one three
- Ohio 0 2 3 Ohio 0 2
- Colorado 4 6 7 Colorado 4 6
- Utah 8 10 11 Utah 8 10
- New York 12 14 15 New York 12 14
索引、选取和过滤
Series索引(obj[…])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是整数。下面是几个例子:
- In [102]: obj = Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
- In [103]: obj['b'] In [104]: obj[1]
- Out[103]: 1.0 Out[104]: 1.0
- In [105]: obj[2:4] In [106]: obj[['b', 'a', 'd']]
- Out[105]: Out[106]:
- c 2 b 1
- d 3 a 0
- d 3
- In [107]: obj[[1, 3]] In [108]: obj[obj < 2]
- Out[107]: Out[108]:
- b 1 a 0
- d 3 b 1
利用标签的切片运算与普通的Python切片运算不同,其末端是包含的(inclusive)译注1:
- In [109]: obj['b':'c']
- Out[109]:
- b 1
- c 2
设置的方式也很简单:
- In [110]: obj['b':'c'] = 5
- In [111]: obj
- Out[111]:
- a 0
- b 5
- c 5
- d 3
如你所见,对DataFrame进行索引其实就是获取一个或多个列:
- In [112]: data = DataFrame(np.arange(16).reshape((4, 4)),
- ...: index=['Ohio', 'Colorado', 'Utah', 'New York'],
- ...: columns=['one', 'two', 'three', 'four'])
- In [113]: data
- Out[113]:
- one two three four
- Ohio 0 1 2 3
- Colorado 4 5 6 7
- Utah 8 9 10 11
- New York 12 13 14 15
- In [114]: data['two'] In [115]: data[['three', 'one']]
- Out[114]: Out[115]:
- Ohio 1 three one
- Colorado 5 Ohio 2 0
- Utah 9 Colorado 6 4
- New York 13 Utah 10 8
- Name: two New York 14 12
这种索引方式有几个特殊的情况。首先通过切片或布尔型数组选取行:
- In [116]: data[:2] In [117]: data[data['three'] > 5]
- Out[116]: Out[117]:
- one two three four one two three four
- Ohio 0 1 2 3 Colorado 4 5 6 7
- Colorado 4 5 6 7 Utah 8 9 10 11
- New York 12 13 14 15
有些读者可能会认为这不太合乎逻辑,但这种语法的的确确来源于实践。另一种用法是通过布尔型DataFrame(比如下面这个由标量比较运算得出的)进行索引:
- In [118]: data < 5
- Out[118]:
- one two three four
- Ohio True True True True
- Colorado True False False False
- Utah False False False False
- New York False False False False
- In [119]: data[data < 5] = 0
- In [120]: data
- Out[120]:
- one two three four
- Ohio 0 0 0 0
- Colorado 0 5 6 7
- Utah 8 9 10 11
- New York 12 13 14 15
这段代码的目的是使DataFrame在语法上更像ndarray。
为了在DataFrame的行上进行标签索引,我引入了专门的索引字段ix。它使你可以通过NumPy式的标记法以及轴标签从DataFrame中选取行和列的子集。之前曾提到过,这也是一种重新索引的简单手段:
- In [121]: data.ix['Colorado', ['two', 'three']]
- Out[121]:
- two 5
- three 6
- Name: Colorado
- In [122]: data.ix[['Colorado', 'Utah'], [3, 0, 1]]
- Out[122]:
- four one two
- Colorado 7 0 5
- Utah 11 8 9
- In [123]: data.ix[2] In [124]: data.ix[:'Utah', 'two']
- Out[123]: Out[124]:
- one 8 Ohio 0
- two 9 Colorado 5
- three 10 Utah 9
- four 11 Name: two
- Name: Utah
- In [125]: data.ix[data.three > 5, :3]
- Out[125]:
- one two three
- Colorado 0 5 6
- Utah 8 9 10
- New York 12 13 14
也就是说,对pandas对象中的数据的选取和重排方式有很多。表5-6简单总结了针对DataFrame数据的大部分选取和重排方式。在使用层次化索引时还能用到一些别的办法(稍后就会讲到)。
注意: 在设计pandas时,我觉得必须输入frame[:,col]才能选取列实在有些嗦(而且还很容易出错),因为列的选取是一种最常见的操作。于是,我就把所有的标签索引功能都放到ix中了。
译注2:get_value方法是选取,set-value方法是设置。
算术运算和数据对齐
pandas最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。我们来看一个简单的例子:
- In [126]: s1 = Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
- In [127]: s2 = Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
- In [128]: s1 In [129]: s2
- Out[128]: Out[129]:
- a 7.3 a -2.1
- c -2.5 c 3.6
- d 3.4 e -1.5
- e 1.5 f 4.0
- g 3.1
将它们相加就会产生:
- In [130]: s1 + s2
- Out[130]:
- a 5.2
- c 1.1
- d NaN
- e 0.0
- f NaN
- g NaN
自动的数据对齐操作在不重叠的索引处引入了NA值译注3。缺失值会在算术运算过程中传播。
对于DataFrame,对齐操作会同时发生在行和列上:
- In [131]: df1 = DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),
- ...: index=['Ohio', 'Texas', 'Colorado'])
- In [132]: df2 = DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
- ...: index=['Utah', 'Ohio', 'Texas', 'Oregon'])
- In [133]: df1 In [134]: df2
- Out[133]: Out[134]:
- b c d b d e
- Ohio 0 1 2 Utah 0 1 2
- Texas 3 4 5 Ohio 3 4 5
- Colorado 6 7 8 Texas 6 7 8
- Oregon 9 10 11
把它们相加后将会返回一个新的DataFrame,其索引和列为原来那两个DataFrame的并集:
- In [135]: df1 + df2
- Out[135]:
- b c d e
- Colorado NaN NaN NaN NaN
- Ohio 3 NaN 6 NaN
- Oregon NaN NaN NaN NaN
- Texas 9 NaN 12 NaN
- Utah NaN NaN NaN NaN
在算术方法中填充值
在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如0):
- In [136]: df1 = DataFrame(np.arange(12.).reshape((3, 4)), columns=list('abcd'))
- In [137]: df2 = DataFrame(np.arange(20.).reshape((4, 5)), columns=list('abcde'))
- In [138]: df1 In [139]: df2
- Out[138]: Out[139]:
- a b c d a b c d e
- 0 0 1 2 3 0 0 1 2 3 4
- 1 4 5 6 7 1 5 6 7 8 9
- 2 8 9 10 11 2 10 11 12 13 14
- 3 15 16 17 18 19
将它们相加时,没有重叠的位置就会产生NA值:
- In [140]: df1 + df2
- Out[140]:
- a b c d e
- 0 0 2 4 6 NaN
- 1 9 11 13 15 NaN
- 2 18 20 22 24 NaN
- 3 NaN NaN NaN NaN NaN
使用df1的add方法,传入df2以及一个fill_value参数:
- In [141]: df1.add(df2, fill_value=0)
- Out[141]:
- a b c d e
- 0 0 2 4 6 4
- 1 9 11 13 15 9
- 2 18 20 22 24 14
- 3 15 16 17 18 19
与此类似,在对Series或DataFrame重新索引时,也可以指定一个填充值:
- In [142]: df1.reindex(columns=df2.columns, fill_value=0)
- Out[142]:
- a b c d e
- 0 0 1 2 3 0
- 1 4 5 6 7 0
- 2 8 9 10 11 0
DataFrame和Series之间的运算
跟NumPy数组一样,DataFrame和Series之间算术运算也是有明确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间的差:
- In [143]: arr = np.arange(12.).reshape((3, 4))
- In [144]: arr
- Out[144]:
- array([[ 0., 1., 2., 3.],
- [ 4., 5., 6., 7.],
- [ 8., 9., 10., 11.]])
- In [145]: arr[0]
- Out[145]: array([ 0., 1., 2., 3.])
- In [146]: arr - arr[0]
- Out[146]:
- array([[ 0., 0., 0., 0.],
- [ 4., 4., 4., 4.],
- [ 8., 8., 8., 8.]])
这就叫做广播(broadcasting),第12章将对此进行详细讲解。DataFrame和Series之间的运算差不多也是如此:
- In [147]: frame = DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
- ...: index=['Utah', 'Ohio', 'Texas', 'Oregon'])
- In [148]: series = frame.ix[0]
- In [149]: frame In [150]: series
- Out[149]: Out[150]:
- b d e b 0
- Utah 0 1 2 d 1
- Ohio 3 4 5 e 2
- Texas 6 7 8 Name: Utah
- Oregon 9 10 11
默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:
- In [151]: frame - series
- Out[151]:
- b d e
- Utah 0 0 0
- Ohio 3 3 3
- Texas 6 6 6
- Oregon 9 9 9
如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集:
- In [152]: series2 = Series(range(3), index=['b', 'e', 'f'])
- In [153]: frame + series2
- Out[153]:
- b d e f
- Utah 0 NaN 3 NaN
- Ohio 3 NaN 6 NaN
- Texas 6 NaN 9 NaN
- Oregon 9 NaN 12 NaN
如果你希望匹配行且在列上广播,则必须使用算术运算方法。例如:
- In [154]: series3 = frame['d']
- In [155]: frame In [156]: series3
- Out[155]: Out[156]:
- b d e Utah 1
- Utah 0 1 2 Ohio 4
- Ohio 3 4 5 Texas 7
- Texas 6 7 8 Oregon 10
- Oregon 9 10 11 Name: d
- In [157]: frame.sub(series3, axis=0)
- Out[157]:
- b d e
- Utah -1 0 1
- Ohio -1 0 1
- Texas -1 0 1
- Oregon -1 0 1
传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配DataFrame的行索引并进行广播。译注4
函数应用和映射
NumPy的ufuncs(元素级数组方法)也可用于操作pandas对象:
- In [158]: frame = DataFrame(np.random.randn(4, 3), columns=list('bde'),
- ...: index=['Utah', 'Ohio', 'Texas', 'Oregon'])
- In [159]: frame In [160]: np.abs(frame)
- Out[159]: Out[160]:
- b d e b d e
- Utah -0.204708 0.478943 -0.519439 Utah 0.204708 0.478943 0.519439
- Ohio -0.555730 1.965781 1.393406 Ohio 0.555730 1.965781 1.393406
- Texas 0.092908 0.281746 0.769023 Texas 0.092908 0.281746 0.769023
- Oregon 1.246435 1.007189 -1.296221 Oregon 1.246435 1.007189 1.296221
另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能:
- In [161]: f = lambda x: x.max() - x.min()
- In [162]: frame.apply(f) In [163]: frame.apply(f, axis=1)
- Out[162]: Out[163]:
- b 1.802165 Utah 0.998382
- d 1.684034 Ohio 2.521511
- e 2.689627 Texas 0.676115
- Oregon 2.542656
许多最为常见的数组统计功能都被实现成DataFrame的方法(如sum和mean),因此无需使用apply方法。
除标量值外,传递给apply的函数还可以返回由多个值组成的Series:
- In [164]: def f(x):
- ...: return Series([x.min(), x.max()], index=['min', 'max'])
- In [165]: frame.apply(f)
- b d e
- min -0.555730 0.281746 -1.296221
- max 1.246435 1.965781 1.393406
此外,元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可:
- In [166]: format = lambda x: '%.2f' % x
- In [167]: frame.applymap(format)
- Out[167]:
- b d e
- Utah -0.20 0.48 -0.52
- Ohio -0.56 1.97 1.39
- Texas 0.09 0.28 0.77
- Oregon 1.25 1.01 -1.30
之所以叫做applymap,是因为Series有一个用于应用元素级函数的map方法:
- In [168]: frame['e'].map(format)
- Out[168]:
- Utah -0.52
- Ohio 1.39
- Texas 0.77
- Oregon -1.30
- Name: e
排序和排名
根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序的新对象:
- In [169]: obj = Series(range(4), index=['d', 'a', 'b', 'c'])
- In [170]: obj.sort_index()
- Out[170]:
- a 1
- b 2
- c 3
- d 0
而对于DataFrame,则可以根据任意一个轴上的索引进行排序:
- In [171]: frame = DataFrame(np.arange(8).reshape((2, 4)), index=['three', 'one'],
- ...: columns=['d', 'a', 'b', 'c'])
- In [172]: frame.sort_index() In [173]: frame.sort_index(axis=1)
- Out[172]: Out[173]:
- d a b c a b c d
- one 4 5 6 7 three 1 2 3 0
- three 0 1 2 3 one 5 6 7 4
数据默认是按升序排序的,但也可以降序排序:
- In [174]: frame.sort_index(axis=1, ascending=False)
- Out[174]:
- d c b a
- three 0 3 2 1
- one 4 7 6 5
若要按值对Series进行排序,可使用其order方法:
- In [175]: obj = Series([4, 7, -3, 2])
- In [176]: obj.order()
- Out[176]:
- 2 -3
- 3 2
- 0 4
- 1 7
在排序时,任何缺失值默认都会被放到Series的末尾:
- In [177]: obj = Series([4, np.nan, 7, np.nan, -3, 2])
- In [178]: obj.order()
- Out[178]:
- 4 -3
- 5 2
- 0 4
- 2 7
- 1 NaN
- 3 NaN
在DataFrame上,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给by选项即可达到该目的:
- In [179]: frame = DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
- In [180]: frame In [181]: frame.sort_index(by='b')
- Out[180]: Out[181]:
- a b a b
- 0 0 4 2 0 -3
- 1 1 7 3 1 2
- 2 0 -3 0 0 4
- 3 1 2 1 1 7
要根据多个列进行排序,传入名称的列表即可:
- In [182]: frame.sort_index(by=['a', 'b'])
- Out[182]:
- a b
- 2 0 -3
- 0 0 4
- 3 1 2
- 1 1 7
排名(ranking)跟排序关系密切,且它会增设一个排名值(从1开始,一直到数组中有效数据的数量)。它跟numpy.argsort产生的间接排序索引差不多,只不过它可以根据某种规则破坏平级关系。接下来介绍Series和DataFrame的rank方法。默认情况下,rank是通过“为各组分配一个平均排名”的方式破坏平级关系的:
- In [183]: obj = Series([7, -5, 7, 4, 2, 0, 4])
- In [184]: obj.rank()
- Out[184]:
- 0 6.5
- 1 1.0
- 2 6.5
- 3 4.5
- 4 3.0
- 5 2.0
- 6 4.5
也可以根据值在原数据中出现的顺序给出排名译注5:
- In [185]: obj.rank(method='first')
- Out[185]:
- 0 6
- 1 1
- 2 7
- 3 4
- 4 3
- 5 2
- 6 5
当然,你也可以按降序进行排名:
- In [186]: obj.rank(ascending=False, method='max')
- Out[186]:
- 0 2
- 1 7
- 2 2
- 3 4
- 4 5
- 5 6
- 6 4
表5-8列出了所有用于破坏平级关系的method选项。DataFrame可以在行或列上计算排名:
- In [187]: frame = DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1],
- ...: 'c': [-2, 5, 8, -2.5]})
- In [188]: frame In [189]: frame.rank(axis=1)
- Out[188]: Out[189]:
- a b c a b c
- 0 0 4.3 -2.0 0 2 3 1
- 1 1 7.0 5.0 1 1 3 2
- 2 0 -3.0 8.0 2 2 1 3
- 3 1 2.0 -2.5 3 2 3 1
带有重复值的轴索引
直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的Series:
- In [190]: obj = Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
- In [191]: obj
- Out[191]:
- a 0
- a 1
- b 2
- b 3
- c 4
索引的is_unique属性可以告诉你它的值是否是唯一的:
- In [192]: obj.index.is_unique
- Out[192]: False
对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个Series;而对应单个值的,则返回一个标量值。
- In [193]: obj['a'] In [194]: obj['c']
- Out[193]: Out[194]: 4
- a 0
- a 1
对DataFrame的行进行索引时也是如此:
- In [195]: df = DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
- In [196]: df
- Out[196]:
- 0 1 2
- a 0.274992 0.228913 1.352917
- a 0.886429 -2.001637 -0.371843
- b 1.669025 -0.438570 -0.539741
- b 0.476985 3.248944 -1.021228
- In [197]: df.ix['b']
- Out[197]:
- 0 1 2
- b 1.669025 -0.438570 -0.539741
- b 0.476985 3.248944 -1.021228
译注1:即封闭区间。
译注3:由于本书中多次出现“非重叠”(overlapping)这个词,所以需要简单说明一下。例如,“飞机场”跟“拖拉机”都有个“机”,于是可以认为这两个字符串是“重叠”的;“高富帅”和“矮穷挫”的情况自然就是“非重叠”了。注意,虽然这里没有任何顺序和连续的概念,但有些地方是需要考虑顺序和连续的。
译注4:这里需要补充说明一下,作者反复强调“广播”会在第12章介绍,所以如果真看不懂这里就等到12章学完再看不迟。译者已经尽量把原文扩展的描述扩展开,但是文字描述始终没有图形更具体。例如,你可以打开一个Excel,随意找一排单元格并输入一些文字(注意是一排),然后选中这些单元格,将鼠标移至选区右下角,当指针变为加号时,按住向下拉几行,这就是“沿行向下广播”。
译注5:类似于稳定排序。