8.1 层次化索引

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

  1. In [9]: data = pd.Series(np.random.randn(9),
  2. ...: index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],
  3. ...: [1, 2, 3, 1, 3, 1, 2, 2, 3]])
  4. In [10]: data
  5. Out[10]:
  6. a 1 -0.204708
  7. 2 0.478943
  8. 3 -0.519439
  9. b 1 -0.555730
  10. 3 1.965781
  11. c 1 1.393406
  12. 2 0.092908
  13. d 2 0.281746
  14. 3 0.769023
  15. dtype: float64

看到的结果是经过美化的带有MultiIndex索引的Series的格式。索引之间的“间隔”表示“直接使用上面的标签”:

  1. In [11]: data.index
  2. Out[11]:
  3. MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
  4. labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])

对于一个层次化索引的对象,可以使用所谓的部分索引,使用它选取数据子集的操作更简单:

  1. In [12]: data['b']
  2. Out[12]:
  3. 1 -0.555730
  4. 3 1.965781
  5. dtype: float64
  6. In [13]: data['b':'c']
  7. Out[13]:
  8. b 1 -0.555730
  9. 3 1.965781
  10. c 1 1.393406
  11. 2 0.092908
  12. dtype: float64
  13. In [14]: data.loc[['b', 'd']]
  14. Out[14]:
  15. b 1 -0.555730
  16. 3 1.965781
  17. d 2 0.281746
  18. 3 0.769023
  19. dtype: float64

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

  1. In [15]: data.loc[:, 2]
  2. Out[15]:
  3. a 0.478943
  4. c 0.092908
  5. d 0.281746
  6. dtype: float64

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

  1. In [16]: data.unstack()
  2. Out[16]:
  3. 1 2 3
  4. a -0.204708 0.478943 -0.519439
  5. b -0.555730 NaN 1.965781
  6. c 1.393406 0.092908 NaN
  7. d NaN 0.281746 0.769023

unstack的逆运算是stack:

  1. In [17]: data.unstack().stack()
  2. Out[17]:
  3. a 1 -0.204708
  4. 2 0.478943
  5. 3 -0.519439
  6. b 1 -0.555730
  7. 3 1.965781
  8. c 1 1.393406
  9. 2 0.092908
  10. d 2 0.281746
  11. 3 0.769023
  12. dtype: float64

stack和unstack将在本章后面详细讲解。

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

  1. In [18]: frame = pd.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. In [19]: frame
  6. Out[19]:
  7. Ohio Colorado
  8. Green Red Green
  9. a 1 0 1 2
  10. 2 3 4 5
  11. b 1 6 7 8
  12. 2 9 10 11

各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:

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

注意:小心区分索引名state、color与行标签。

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

  1. In [23]: frame['Ohio']
  2. Out[23]:
  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']],
  2. names=['state', 'color'])

重排与分级排序

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

  1. In [24]: frame.swaplevel('key1', 'key2')
  2. Out[24]:
  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

而sort_index则根据单个级别中的值对数据进行排序。交换级别时,常常也会用到sort_index,这样最终结果就是按照指定顺序进行字母排序了:

  1. In [25]: frame.sort_index(level=1)
  2. Out[25]:
  3. state Ohio Colorado
  4. color Green Red Green
  5. key1 key2
  6. a 1 0 1 2
  7. b 1 6 7 8
  8. a 2 3 4 5
  9. b 2 9 10 11
  10. In [26]: frame.swaplevel(0, 1).sort_index(level=0)
  11. Out[26]:
  12. state Ohio Colorado
  13. color Green Red Green
  14. key2 key1
  15. 1 a 0 1 2
  16. b 6 7 8
  17. 2 a 3 4 5
  18. b 9 10 11

根据级别汇总统计

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

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

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

使用DataFrame的列进行索引

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

  1. In [29]: frame = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),
  2. ....: 'c': ['one', 'one', 'one', 'two', 'two',
  3. ....: 'two', 'two'],
  4. ....: 'd': [0, 1, 2, 0, 1, 2, 3]})
  5. In [30]: frame
  6. Out[30]:
  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 [31]: frame2 = frame.set_index(['c', 'd'])
  2. In [32]: frame2
  3. Out[32]:
  4. a b
  5. c d
  6. one 0 0 7
  7. 1 1 6
  8. 2 2 5
  9. two 0 3 4
  10. 1 4 3
  11. 2 5 2
  12. 3 6 1

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

  1. In [33]: frame.set_index(['c', 'd'], drop=False)
  2. Out[33]:
  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 [34]: frame2.reset_index()
  2. Out[34]:
  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