层次化索引

层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引。

  1. In [261]: data = Series(np.random.randn(10),
  2. ...: index=[['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'd', 'd'],
  3. ...: [1, 2, 3, 1, 2, 3, 1, 2, 2, 3]])
  4.  
  5. In [262]: data
  6. Out[262]:
  7. a 1 0.670216
  8. 2 0.852965
  9. 3 -0.955869
  10. b 1 -0.023493
  11. 2 -2.304234
  12. 3 -0.652469
  13. c 1 -1.218302
  14. 2 -1.332610
  15. d 2 1.074623
  16. 3 0.723642

这就是带有MultiIndex索引的Series的格式化输出形式。索引之间的“间隔”表示“直接使用上面的标签”:

  1. In [263]: data.index
  2. Out[263]:
  3. MultiIndex
  4. [('a', 1) ('a', 2) ('a', 3) ('b', 1) ('b', 2) ('b', 3) ('c', 1) ('c', 2) ('d', 2) ('d', 3)]

对于一个层次化索引的对象,选取数据子集的操作很简单:

  1. In [264]: data['b']
  2. Out[264]:
  3. 1 -0.023493
  4. 2 -2.304234
  5. 3 -0.652469
  6.  
  7. In [265]: data['b':'c'] In [266]: data.ix[['b', 'd']]
  8. Out[265]: Out[266]:
  9. b 1 -0.023493 b 1 -0.023493
  10. 2 -2.304234 2 -2.304234
  11. 3 -0.652469 3 -0.652469
  12. c 1 -1.218302 d 2 1.074623
  13. 2 -1.332610 3 0.723642

有时甚至还可以在“内层”中进行选取:

  1. In [267]: data[:, 2]
  2. Out[267]:
  3. a 0.852965
  4. b -2.304234
  5. c -1.332610
  6. d 1.074623

层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。比如说,这段数据可以通过其unstack方法被重新安排到一个DataFrame中:

  1. In [268]: data.unstack()
  2. Out[268]:
  3. 1 2 3
  4. a 0.670216 0.852965 -0.955869
  5. b -0.023493 -2.304234 -0.652469
  6. c -1.218302 -1.332610 NaN
  7. d NaN 1.074623 0.723642

unstack的逆运算是stack:

  1. In [269]: data.unstack().stack()
  2. Out[269]:
  3. a 1 0.670216
  4. 2 0.852965
  5. 3 -0.955869
  6. b 1 -0.023493
  7. 2 -2.304234
  8. 3 -0.652469
  9. c 1 -1.218302
  10. 2 -1.332610
  11. d 2 1.074623
  12. 3 0.723642

stack和unstack将在第7章中详细讲解。

对于一个DataFrame,每条轴都可以有分层索引:

  1. In [270]: frame = DataFrame(np.arange(12).reshape((4, 3)),
  2. ...: index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
  3. ...: columns=[['Ohio', 'Ohio', 'Colorado'],
  4. ...: ['Green', 'Red', 'Green']])
  5.  
  6. In [271]: frame
  7. Out[271]:
  8. Ohio Colorado
  9. Green Red Green
  10. a 1 0 1 2
  11. 2 3 4 5
  12. b 1 6 7 8
  13. 2 9 10 11

各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中(不要将索引名称跟轴标签混为一谈!):

  1. In [272]: frame.index.names = ['key1', 'key2']
  2.  
  3. In [273]: frame.columns.names = ['state', 'color']
  4.  
  5. In [274]: frame
  6. Out[274]:
  7. state Ohio Colorado
  8. color Green Red Green
  9. key1 key2
  10. a 1 0 1 2
  11. 2 3 4 5
  12. b 1 6 7 8
  13. 2 9 10 11

由于有了分部的列索引,因此可以轻松选取列分组:

  1. In [275]: frame['Ohio']
  2. Out[275]:
  3. color Green Red
  4. key1 key2
  5. a 1 0 1
  6. 2 3 4
  7. b 1 6 7
  8. 2 9 10

可以单独创建MultiIndex然后复用。上面那个DataFrame中的(分级的)列可以这样创建:

  1. MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']], names=['state', 'color'])

重排分级顺序

有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):

  1. In [276]: frame.swaplevel('key1', 'key2')
  2. Out[276]:
  3. state Ohio Colorado
  4. color Green Red Green
  5. key2 key1
  6. 1 a 0 1 2
  7. 2 a 3 4 5
  8. 1 b 6 7 8
  9. 2 b 9 10 11

而sortlevel则根据单个级别中的值对数据进行排序(稳定的)。交换级别时,常常也会用到sortlevel,这样最终结果就是有序的了:

  1. In [277]: frame.sortlevel(1) In [278]: frame.swaplevel(0, 1).sortlevel(0)
  2. Out[277]: Out[278]:
  3. state Ohio Colorado state Ohio Colorado
  4. color Green Red Green color Green Red Green
  5. key1 key2 key2 key1
  6. a 1 0 1 2 1 a 0 1 2
  7. b 1 6 7 8 b 6 7 8
  8. a 2 3 4 5 2 a 3 4 5
  9. b 2 9 10 11 b 9 10 11

注意:在层次化索引的对象上,如果索引是按字典方式从外到内排序(即调用sortlevel(0)或sort_index()的结果),数据选取操作的性能要好很多。

根据级别汇总统计

许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。再以上面那个DataFrame为例,我们可以根据行或列上的级别来进行求和,如下所示:

  1. In [279]: frame.sum(level='key2')
  2. Out[279]:
  3. state Ohio Colorado
  4. color Green Red Green
  5. key2
  6. 1 6 8 10
  7. 2 12 14 16
  8.  
  9. In [280]: frame.sum(level='color', axis=1)
  10. Out[280]:
  11. color Green Red
  12. key1 key2
  13. a 1 2 1
  14. 2 8 4
  15. b 1 14 7
  16. 2 20 10

这其实是利用了pandas的groupby功能,本书稍后将对其进行详细讲解。

使用DataFrame的列

人们经常想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame的列。以下面这个DataFrame为例:

  1. In [281]: frame = DataFrame({'a': range(7), 'b': range(7, 0, -1),
  2. ...: 'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
  3. ...: 'd': [0, 1, 2, 0, 1, 2, 3]})
  4.  
  5. In [282]: frame
  6. Out[282]:
  7. a b c d
  8. 0 0 7 one 0
  9. 1 1 6 one 1
  10. 2 2 5 one 2
  11. 3 3 4 two 0
  12. 4 4 3 two 1
  13. 5 5 2 two 2
  14. 6 6 1 two 3

DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:

  1. In [283]: frame2 = frame.set_index(['c', 'd'])
  2.  
  3. In [284]: frame2
  4. Out[284]:
  5. a b
  6. c d
  7. one 0 0 7
  8. 1 1 6
  9. 2 2 5
  10. two 0 3 4
  11. 1 4 3
  12. 2 5 2
  13. 3 6 1

默认情况下,那些列会从DataFrame中移除,但也可以将其保留下来:

  1. In [285]: frame.set_index(['c', 'd'], drop=False)
  2. Out[285]:
  3. a b c d
  4. c d
  5. one 0 0 7 one 0
  6. 1 1 6 one 1
  7. 2 2 5 one 2
  8. two 0 3 4 two 0
  9. 1 4 3 two 1
  10. 2 5 2 two 2
  11. 3 6 1 two 3

reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:

  1. In [286]: frame2.reset_index()
  2. Out[286]:
  3. c d a b
  4. 0 one 0 0 7
  5. 1 one 1 1 6
  6. 2 one 2 2 5
  7. 3 two 0 3 4
  8. 4 two 1 4 3
  9. 5 two 2 5 2
  10. 6 two 3 6 1