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
索引层的交换由 swaplevel
和 reorder_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
中使用函数完成与例子中一样的功能,即把Upper
和Other
分别替换为Changed_row
和Changed_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_index
是 set_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