层次化索引
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引。
- In [261]: data = Series(np.random.randn(10),
- ...: index=[['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'd', 'd'],
- ...: [1, 2, 3, 1, 2, 3, 1, 2, 2, 3]])
- In [262]: data
- Out[262]:
- a 1 0.670216
- 2 0.852965
- 3 -0.955869
- b 1 -0.023493
- 2 -2.304234
- 3 -0.652469
- c 1 -1.218302
- 2 -1.332610
- d 2 1.074623
- 3 0.723642
这就是带有MultiIndex索引的Series的格式化输出形式。索引之间的“间隔”表示“直接使用上面的标签”:
- In [263]: data.index
- Out[263]:
- MultiIndex
- [('a', 1) ('a', 2) ('a', 3) ('b', 1) ('b', 2) ('b', 3) ('c', 1) ('c', 2) ('d', 2) ('d', 3)]
对于一个层次化索引的对象,选取数据子集的操作很简单:
- In [264]: data['b']
- Out[264]:
- 1 -0.023493
- 2 -2.304234
- 3 -0.652469
- In [265]: data['b':'c'] In [266]: data.ix[['b', 'd']]
- Out[265]: Out[266]:
- b 1 -0.023493 b 1 -0.023493
- 2 -2.304234 2 -2.304234
- 3 -0.652469 3 -0.652469
- c 1 -1.218302 d 2 1.074623
- 2 -1.332610 3 0.723642
有时甚至还可以在“内层”中进行选取:
- In [267]: data[:, 2]
- Out[267]:
- a 0.852965
- b -2.304234
- c -1.332610
- d 1.074623
层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。比如说,这段数据可以通过其unstack方法被重新安排到一个DataFrame中:
- In [268]: data.unstack()
- Out[268]:
- 1 2 3
- a 0.670216 0.852965 -0.955869
- b -0.023493 -2.304234 -0.652469
- c -1.218302 -1.332610 NaN
- d NaN 1.074623 0.723642
unstack的逆运算是stack:
- In [269]: data.unstack().stack()
- Out[269]:
- a 1 0.670216
- 2 0.852965
- 3 -0.955869
- b 1 -0.023493
- 2 -2.304234
- 3 -0.652469
- c 1 -1.218302
- 2 -1.332610
- d 2 1.074623
- 3 0.723642
stack和unstack将在第7章中详细讲解。
对于一个DataFrame,每条轴都可以有分层索引:
- In [270]: frame = DataFrame(np.arange(12).reshape((4, 3)),
- ...: index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
- ...: columns=[['Ohio', 'Ohio', 'Colorado'],
- ...: ['Green', 'Red', 'Green']])
- In [271]: frame
- Out[271]:
- Ohio Colorado
- Green Red Green
- a 1 0 1 2
- 2 3 4 5
- b 1 6 7 8
- 2 9 10 11
各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中(不要将索引名称跟轴标签混为一谈!):
- In [272]: frame.index.names = ['key1', 'key2']
- In [273]: frame.columns.names = ['state', 'color']
- In [274]: frame
- Out[274]:
- 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
由于有了分部的列索引,因此可以轻松选取列分组:
- In [275]: frame['Ohio']
- Out[275]:
- color Green Red
- key1 key2
- a 1 0 1
- 2 3 4
- b 1 6 7
- 2 9 10
可以单独创建MultiIndex然后复用。上面那个DataFrame中的(分级的)列可以这样创建:
- MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']], names=['state', 'color'])
重排分级顺序
有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):
- In [276]: frame.swaplevel('key1', 'key2')
- Out[276]:
- 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
而sortlevel则根据单个级别中的值对数据进行排序(稳定的)。交换级别时,常常也会用到sortlevel,这样最终结果就是有序的了:
- In [277]: frame.sortlevel(1) In [278]: frame.swaplevel(0, 1).sortlevel(0)
- Out[277]: Out[278]:
- state Ohio Colorado state Ohio Colorado
- color Green Red Green color Green Red Green
- key1 key2 key2 key1
- a 1 0 1 2 1 a 0 1 2
- b 1 6 7 8 b 6 7 8
- a 2 3 4 5 2 a 3 4 5
- b 2 9 10 11 b 9 10 11
注意:在层次化索引的对象上,如果索引是按字典方式从外到内排序(即调用sortlevel(0)或sort_index()的结果),数据选取操作的性能要好很多。
根据级别汇总统计
许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。再以上面那个DataFrame为例,我们可以根据行或列上的级别来进行求和,如下所示:
- In [279]: frame.sum(level='key2')
- Out[279]:
- state Ohio Colorado
- color Green Red Green
- key2
- 1 6 8 10
- 2 12 14 16
- In [280]: frame.sum(level='color', axis=1)
- Out[280]:
- 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为例:
- In [281]: frame = 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]})
- In [282]: frame
- Out[282]:
- 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:
- In [283]: frame2 = frame.set_index(['c', 'd'])
- In [284]: frame2
- Out[284]:
- 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中移除,但也可以将其保留下来:
- In [285]: frame.set_index(['c', 'd'], drop=False)
- Out[285]:
- 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刚好相反,层次化索引的级别会被转移到列里面:
- In [286]: frame2.reset_index()
- Out[286]:
- 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