三、索引的常用方法

1. 索引层的交换和删除

为了方便理解交换的过程,这里构造一个三级索引的例子:

In [123]: np.random.seed(0)
In [124]: L1,L2,L3 = ['A','B'],['a','b'],['alpha','beta']
In [125]: mul_index1 = pd.MultiIndex.from_product([L1,L2,L3],
   .....:              names=('Upper', 'Lower','Extra'))
   .....: 
In [126]: L4,L5,L6 = ['C','D'],['c','d'],['cat','dog']
In [127]: mul_index2 = pd.MultiIndex.from_product([L4,L5,L6],
   .....:              names=('Big', 'Small', 'Other'))
   .....: 
In [128]: df_ex = pd.DataFrame(np.random.randint(-9,10,(8,8)),
   .....:                         index=mul_index1,
   .....:                         columns=mul_index2)
   .....: 
In [129]: df_ex
Out[129]: 
Big                 C               D  
Small               c       d       c       d  
Other             cat dog cat dog cat dog cat dog
Upper Lower Extra                      
A     a     alpha   3   6  -9  -6  -6  -2   0   9
            beta   -5  -3   3  -8  -3  -2   5   8
      b     alpha  -4   4  -1   0   7  -4   6   6
            beta   -9   9  -6   8   5  -2  -9  -8
B     a     alpha   0  -9   1  -6   2   9  -7  -9
            beta   -9  -5  -4  -3  -1   8   6  -5
      b     alpha   0   1  -8  -8  -2   0  -6  -3
            beta    2   5   9  -9   5  -6   3   1

索引层的交换由 swaplevelreorder_levels 完成,前者只能交换两个层,而后者可以交换任意层,两者都可以指定交换的是轴是哪一个,即行索引或列索引:

In [130]: df_ex.swaplevel(0,2,axis=1).head() # 列索引的第一层和第三层交换
Out[130]: 
Other             cat dog cat dog cat dog cat dog
Small               c   c   d   d   c   c   d   d
Big                 C   C   C   C   D   D   D   D
Upper Lower Extra                      
A     a     alpha   3   6  -9  -6  -6  -2   0   9
            beta   -5  -3   3  -8  -3  -2   5   8
      b     alpha  -4   4  -1   0   7  -4   6   6
            beta   -9   9  -6   8   5  -2  -9  -8
B     a     alpha   0  -9   1  -6   2   9  -7  -9
In [131]: df_ex.reorder_levels([2,0,1],axis=0).head() # 列表数字指代原来索引中的层
Out[131]: 
Big                 C               D  
Small               c       d       c       d  
Other             cat dog cat dog cat dog cat dog
Extra Upper Lower                      
alpha A     a       3   6  -9  -6  -6  -2   0   9
beta  A     a      -5  -3   3  -8  -3  -2   5   8
alpha A     b      -4   4  -1   0   7  -4   6   6
beta  A     b      -9   9  -6   8   5  -2  -9  -8
alpha B     a       0  -9   1  -6   2   9  -7  -9

轴之间的索引交换

这里只涉及行或列索引内部的交换,不同方向索引之间的交换将在第五章中被讨论。

若想要删除某一层的索引,可以使用 droplevel 方法:

In [132]: df_ex.droplevel(1,axis=1)
Out[132]: 
Big                 C               D    
Other             cat dog cat dog cat dog cat dog
Upper Lower Extra                        
A     a     alpha   3   6  -9  -6  -6  -2   0   9
            beta   -5  -3   3  -8  -3  -2   5   8
      b     alpha  -4   4  -1   0   7  -4   6   6
            beta   -9   9  -6   8   5  -2  -9  -8
B     a     alpha   0  -9   1  -6   2   9  -7  -9
            beta   -9  -5  -4  -3  -1   8   6  -5
      b     alpha   0   1  -8  -8  -2   0  -6  -3
            beta    2   5   9  -9   5  -6   3   1
In [133]: df_ex.droplevel([0,1],axis=0)
Out[133]: 
Big     C               D    
Small   c       d       c       d  
Other cat dog cat dog cat dog cat dog
Extra                        
alpha   3   6  -9  -6  -6  -2   0   9
beta   -5  -3   3  -8  -3  -2   5   8
alpha  -4   4  -1   0   7  -4   6   6
beta   -9   9  -6   8   5  -2  -9  -8
alpha   0  -9   1  -6   2   9  -7  -9
beta   -9  -5  -4  -3  -1   8   6  -5
alpha   0   1  -8  -8  -2   0  -6  -3
beta    2   5   9  -9   5  -6   3   1

2. 索引属性的修改

通过 rename_axis 可以对索引层的名字进行修改,常用的修改方式是传入字典的映射:

In [134]: df_ex.rename_axis(index={'Upper':'Changed_row'},
   .....:                   columns={'Other':'Changed_Col'}).head()
   .....: 
Out[134]: 
Big                       C               D    
Small                     c       d       c       d  
Changed_Col             cat dog cat dog cat dog cat dog
Changed_row Lower Extra                        
A           a     alpha   3   6  -9  -6  -6  -2   0   9
                  beta   -5  -3   3  -8  -3  -2   5   8
            b     alpha  -4   4  -1   0   7  -4   6   6
                  beta   -9   9  -6   8   5  -2  -9  -8
B           a     alpha   0  -9   1  -6   2   9  -7  -9

通过 rename 可以对索引的值进行修改,如果是多级索引需要指定修改的层号 level

In [135]: df_ex.rename(columns={'cat':'not_cat'},
   .....:              level=2).head()
   .....: 
Out[135]: 
Big                     C                       D        
Small                   c           d           c           d  
Other             not_cat dog not_cat dog not_cat dog not_cat dog
Upper Lower Extra                                        
A     a     alpha       3   6      -9  -6      -6  -2       0   9
            beta       -5  -3       3  -8      -3  -2       5   8
      b     alpha      -4   4      -1   0       7  -4       6   6
            beta       -9   9      -6   8       5  -2      -9  -8
B     a     alpha       0  -9       1  -6       2   9      -7  -9

传入参数也可以是函数,其输入值就是索引元素:

In [136]: df_ex.rename(index=lambda x:str.upper(x),
   .....:              level=2).head()
   .....: 
Out[136]: 
Big                 C               D  
Small               c       d       c       d  
Other             cat dog cat dog cat dog cat dog
Upper Lower Extra                      
A     a     ALPHA   3   6  -9  -6  -6  -2   0   9
            BETA   -5  -3   3  -8  -3  -2   5   8
      b     ALPHA  -4   4  -1   0   7  -4   6   6
            BETA   -9   9  -6   8   5  -2  -9  -8
B     a     ALPHA   0  -9   1  -6   2   9  -7  -9

练一练

尝试在 rename_axis 中使用函数完成与例子中一样的功能,即把 UpperOther 分别替换为 Changed_rowChanged_col

对于整个索引的元素替换,可以利用迭代器实现:

In [137]: new_values = iter(list('abcdefgh'))
In [138]: df_ex.rename(index=lambda x:next(new_values),
   .....:              level=2)
   .....: 
Out[138]: 
Big                 C               D    
Small               c       d       c       d  
Other             cat dog cat dog cat dog cat dog
Upper Lower Extra                        
A     a     a       3   6  -9  -6  -6  -2   0   9
            b      -5  -3   3  -8  -3  -2   5   8
      b     c      -4   4  -1   0   7  -4   6   6
            d      -9   9  -6   8   5  -2  -9  -8
B     a     e       0  -9   1  -6   2   9  -7  -9
            f      -9  -5  -4  -3  -1   8   6  -5
      b     g       0   1  -8  -8  -2   0  -6  -3
            h       2   5   9  -9   5  -6   3   1

若想要对某个位置的元素进行修改,在单层索引时容易实现,即先取出索引的 values 属性,再给对得到的列表进行修改,最后再对 index 对象重新赋值。但是如果是多级索引的话就有些麻烦,一个解决的方案是先把某一层索引临时转为表的元素,然后再进行修改,最后重新设定为索引,下面一节将介绍这些操作。

另外一个需要介绍的函数是 map ,它是定义在 Index 上的方法,与前面 rename 方法中层的函数式用法是类似的,只不过它传入的不是层的标量值,而是直接传入索引的元组,这为用户进行跨层的修改提供了遍历。例如,可以等价地写出上面的字符串转大写的操作:

In [139]: df_temp = df_ex.copy()
In [140]: new_idx = df_temp.index.map(lambda x: (x[0],
   .....:                                        x[1],
   .....:                                        str.upper(x[2])))
   .....: 
In [141]: df_temp.index = new_idx
In [142]: df_temp.head()
Out[142]: 
Big                 C               D    
Small               c       d       c       d  
Other             cat dog cat dog cat dog cat dog
Upper Lower Extra                        
A     a     ALPHA   3   6  -9  -6  -6  -2   0   9
            BETA   -5  -3   3  -8  -3  -2   5   8
      b     ALPHA  -4   4  -1   0   7  -4   6   6
            BETA   -9   9  -6   8   5  -2  -9  -8
B     a     ALPHA   0  -9   1  -6   2   9  -7  -9

关于 map 的另一个使用方法是对多级索引的压缩,这在第四章和第五章的一些操作中是有用的:

In [143]: df_temp = df_ex.copy()
In [144]: new_idx = df_temp.index.map(lambda x: (x[0]+'-'+
   .....:                                        x[1]+'-'+
   .....:                                        x[2]))
   .....: 
In [145]: df_temp.index = new_idx
In [146]: df_temp.head() # 单层索引
Out[146]: 
Big         C               D    
Small       c       d       c       d  
Other     cat dog cat dog cat dog cat dog
A-a-alpha   3   6  -9  -6  -6  -2   0   9
A-a-beta   -5  -3   3  -8  -3  -2   5   8
A-b-alpha  -4   4  -1   0   7  -4   6   6
A-b-beta   -9   9  -6   8   5  -2  -9  -8
B-a-alpha   0  -9   1  -6   2   9  -7  -9

同时,也可以反向地展开:

In [147]: new_idx = df_temp.index.map(lambda x:tuple(x.split('-')))
In [148]: df_temp.index = new_idx
In [149]: df_temp.head() # 三层索引
Out[149]: 
Big         C               D    
Small       c       d       c       d  
Other     cat dog cat dog cat dog cat dog
A a alpha   3   6  -9  -6  -6  -2   0   9
    beta   -5  -3   3  -8  -3  -2   5   8
  b alpha  -4   4  -1   0   7  -4   6   6
    beta   -9   9  -6   8   5  -2  -9  -8
B a alpha   0  -9   1  -6   2   9  -7  -9

3. 索引的设置与重置

为了说明本节的函数,下面构造一个新表:

In [150]: df_new = pd.DataFrame({'A':list('aacd'),
   .....:                        'B':list('PQRT'),
   .....:                        'C':[1,2,3,4]})
   .....: 
In [151]: df_new
Out[151]: 
   A  B  C
0  a  P  1
1  a  Q  2
2  c  R  3
3  d  T  4

索引的设置可以使用 set_index 完成,这里的主要参数是 append ,表示是否来保留原来的索引,直接把新设定的添加到原索引的内层:

In [152]: df_new.set_index('A')
Out[152]: 
   B  C
A  
a  P  1
a  Q  2
c  R  3
d  T  4
In [153]: df_new.set_index('A', append=True)
Out[153]: 
     B  C
  A  
0 a  P  1
1 a  Q  2
2 c  R  3
3 d  T  4

可以同时指定多个列作为索引

In [154]: df_new.set_index(['A', 'B'])
Out[154]: 
     C
A B   
a P  1
  Q  2
c R  3
d T  4

如果想要添加索引的列没有出现在其中,那么可以直接在参数中传入相应的 Series

In [155]: my_index = pd.Series(list('WXYZ'), name='D')
In [156]: df_new = df_new.set_index(['A', my_index])
In [157]: df_new
Out[157]: 
     B  C
A D  
a W  P  1
  X  Q  2
c Y  R  3
d Z  T  4

reset_indexset_index 的逆函数,其主要参数是 drop ,表示是否要把去掉的索引层丢弃,而不是添加到列中:

In [158]: df_new.reset_index(['D'])
Out[158]: 
   D  B  C
A   
a  W  P  1
a  X  Q  2
c  Y  R  3
d  Z  T  4
In [159]: df_new.reset_index(['D'], drop=True)
Out[159]: 
   B  C
A  
a  P  1
a  Q  2
c  R  3
d  T  4

如果重置了所有的索引,那么 pandas 会直接重新生成一个默认索引:

In [160]: df_new.reset_index()
Out[160]: 
   A  D  B  C
0  a  W  P  1
1  a  X  Q  2
2  c  Y  R  3
3  d  Z  T  4

4. 索引的变形

在某些场合下,需要对索引做一些扩充或者剔除,更具体地要求是给定一个新的索引,把原表中相应的索引对应元素填充到新索引构成的表中。例如,下面的表中给出了员工信息,需要重新制作一张新的表,要求增加一名员工的同时去掉身高列并增加性别列:

In [161]: df_reindex = pd.DataFrame({"Weight":[60,70,80],
   .....:                            "Height":[176,180,179]},
   .....:                            index=['1001','1003','1002'])
   .....: 
In [162]: df_reindex
Out[162]: 
      Weight  Height
1001      60     176
1003      70     180
1002      80     179
In [163]: df_reindex.reindex(index=['1001','1002','1003','1004'],
   .....:                    columns=['Weight','Gender'])
   .....: 
Out[163]: 
      Weight  Gender
1001    60.0     NaN
1002    80.0     NaN
1003    70.0     NaN
1004     NaN     NaN

这种需求常出现在时间序列索引的时间点填充以及 ID 编号的扩充。另外,需要注意的是原来表中的数据和新表中会根据索引自动对齐,例如原先的1002号位置在1003号之后,而新表中相反,那么 reindex 中会根据元素对齐,与位置无关。

还有一个与 reindex 功能类似的函数是 reindex_like ,其功能是仿照传入的表索引来进行被调用表索引的变形。例如,现在已经存在一张表具备了目标索引的条件,那么上述功能可采用下述代码得到:

In [164]: df_existed = pd.DataFrame(index=['1001','1002','1003','1004'],
   .....:                           columns=['Weight','Gender'])
   .....: 
In [165]: df_reindex.reindex_like(df_existed)
Out[165]: 
      Weight  Gender
1001    60.0     NaN
1002    80.0     NaN
1003    70.0     NaN
1004     NaN     NaN