6.Pandas的层级索引

Pandas的层级索引

到目前为止,我们接触到的都是一位数据和二维数据,分别可以用Pandas的Series和DataFrame对象储存
但我们也有可能遇到存储多维数据的情况,对此Pandas分别提供了Panel和Panel4D对象来解决三维数据和思维数据

我们通过之前的讲解,可以发现Pandas的DataFrame对象很像Excel表格,每一个数据都有Index和Column,对应Excel表格中的行名和列名

而层级索引则对应Excel中的合并行与合并列,这样,我们通过Pandas所展现的数据就更加直观

此外,我们可以使用层级索引来让高维数组降维,从而使用DataFrame对象来直观的表示高维数据.

这一节将主要讲解创建层级索引(MultiIndex对象)的方法,多级索引数据的取值和切片,统计值的计算以及普通索引与层级索引的转换方法


多级索引

首先讲解为什么要引入层级索引,即如何用DataFrame对象表示高维数据

假如我们需要用Series对象来储存不同省份在不同年份的人口数,我们可以使用如下一个抖机灵的方法

我们把索引改变为省份与年份的元组,来实现存储

Index=[('PlaceA',2020),('PlaceB',2021),\
    ('PlaceB',2020),('PlaceB',2021),\
    ('PlaceC',2020),('PlaceC',2021)]
index_multi=pd.MultiIndex.from_tuples(Index)
print(pd.Series([100,200,300,400,500,600],index=Index))
>>>
(PlaceA, 2020)    100
(PlaceB, 2021)    200
(PlaceB, 2020)    300
(PlaceB, 2021)    400
(PlaceC, 2020)    500
(PlaceC, 2021)    600
dtype: int64

但是这样却比较笨,因为我们想要通过索引来取值的时候,无法做到取出某个年份所有的人口数据,因为我们单独的年份或者省份都不是索引

下面我们就用多级索引来对比一下

Index=[('PlaceA',2020),('PlaceB',2021),\
    ('PlaceB',2020),('PlaceB',2021),\
    ('PlaceC',2020),('PlaceC',2021)]
index_multi=pd.MultiIndex.from_tuples(Index)
print(pd.Series([100,200,300,400,500,600],index=Index))
print(pd.Series([100,200,300,400,500,600],index=Index).reindex(index_multi))
>>>
(PlaceA, 2020)    100
(PlaceB, 2021)    200
(PlaceB, 2020)    300
(PlaceB, 2021)    400
(PlaceC, 2020)    500
(PlaceC, 2021)    600
dtype: int64
PlaceA  2020    100
PlaceB  2021    200
        2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64

注意,这个时候地区和年份都是有效的索引我们可以直接使用来索取各地的数据

Index=[('PlaceA',2020),('PlaceA',2021),\
    ('PlaceB',2020),('PlaceB',2021),\
    ('PlaceC',2020),('PlaceC',2021)]
index_multi=pd.MultiIndex.from_tuples(Index)
Series_1=pd.Series([100,200,300,400,500,600],index=Index)
Series_1=Series_1.reindex(index_multi)
print(Series_1)
print(Series_1['PlaceA'])
print(Series_1[:,2020])
>>>
PlaceA  2020    100
        2021    200
PlaceB  2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64
2020    100
2021    200
dtype: int64
PlaceA    100
PlaceB    300
PlaceC    500
dtype: int64

事实上,这本应是一个三维数组,即(地区,年份,人口),但是我们通过多级索引就成功地让本该储存一维数据的Series对象储存了用二维数组才能储存的数据,我们也可以再加一级索引,从而做到更高维度的压缩

压堆与解压堆

在正式开始讲解创建MultiIndex对象之前,补充讲解一个内容,上面讲到我们通过创建多级索引的方式实现了用Series对象储存用DataFrame对象储存的数据

换而言之,可以理解为将二维DataFrame对象压堆为一维Series对象,对应的,我们可以使用解压堆来让被压堆的一维对象扩展为二维数组

Index=[('PlaceA',2020),('PlaceA',2021),\
    ('PlaceB',2020),('PlaceB',2021),\
    ('PlaceC',2020),('PlaceC',2021)]
index_multi=pd.MultiIndex.from_tuples(Index)
Series_1=pd.Series([100,200,300,400,500,600],index=Index)
Series_1=Series_1.reindex(index_multi)
Series_1_unstack=Series_1.unstack()
print(Series_1)
print(type(Series_1))
print('')
print(Series_1_unstack)
print(type(Series_1_unstack))
print('')
print(Series_1_unstack.stack())
print(type(Series_1_unstack.stack()))
>>>
PlaceA  2020    100
        2021    200
PlaceB  2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64
<class 'pandas.core.series.Series'>

        2020  2021
PlaceA   100   200
PlaceB   300   400
PlaceC   500   600
<class 'pandas.core.frame.DataFrame'>

PlaceA  2020    100
        2021    200
PlaceB  2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64
<class 'pandas.core.series.Series'>

多级索引的创建

我们创建具有多级索引的Series或DataFrame对象,注意是具有多级索引的Series对象总的来说有两种方法

  1. 直接创建具有多级索引的Series或DataFrame对象
  2. 创建普通Series或DataFrame对象,然后调用reindex()方法赋予多级索引

直接创建具有多级索引的Series对象

直接创建具有多级索引的Series或DataFrame对象有如下的两种途径

指定二维index参数

我们只需要在创建DataFrame或者Series对象时候指定index参数为二维数组即可

Series_1=pd.Series([1,2,3,4],index=[['a','a','b','b'],['a_1','a_2','b_1','b_2']])
DataFrame_1=pd.DataFrame(np.arange(0,16,1).reshape(4,4),\
    index=[['a','a','b','b'],['a_1','a_2','b_1','b_2']],\
    columns=list('abcd'))
print(Series_1)
print(DataFrame_1)
>>>
a  a_1    1
   a_2    2
b  b_1    3
   b_2    4
dtype: int64
        a   b   c   d
a a_1   0   1   2   3
  a_2   4   5   6   7
b b_1   8   9  10  11
  b_2  12  13  14  15

指定二维index对象的关键就在于属于高级索引的低级索引与高级索引的位置对应,即上例中的a_1与a_2,他们都属于高级索引a,所以在高级索引中对应为a,a

实际上可以指定多维数组来创建多级索引

利用键为元素的字典创建

前面讲过,Series对象和DataFrame对象的创建都可以通过字典来创建

只不过Series对象的键名将作为索引名,DataFrame对象的键名将作为列名

例如

dict_1={'a':1,'b':2,'c':3,'d':4}
Series_1=pd.Series(dict_1)
DataFrame_1=pd.DataFrame({'a':Series_1,'b':Series_1,'c':Series_1})
print(Series_1)
print(DataFrame_1)
>>>
a    1
b    2
c    3
d    4
dtype: int64
   a  b  c
a  1  1  1
b  2  2  2
c  3  3  3
d  4  4  4

这里,我们只需要把键名改为元组即可

tuple_1=('a','a_1')
tuple_2=('a','a_2')
tuple_3=('b','b_1')
tuple_4=('b','b_2')
dict_1={tuple_1:1,tuple_2:2,tuple_3:3,tuple_4:4}
Series_1=pd.Series(dict_1)
DataFrame_1=pd.DataFrame({tuple_1:Series_1,tuple_2:Series_1,tuple_3:Series_1,tuple_4:Series_1})
print(Series_1)
print(DataFrame_1)
>>>
a  a_1    1
   a_2    2
b  b_1    3
   b_2    4
dtype: int64
        a       b    
      a_1 a_2 b_1 b_2
a a_1   1   1   1   1
  a_2   2   2   2   2
b b_1   3   3   3   3
  b_2   4   4   4   4

创建MultiIndex对象

就像前面所说,我们可以创建MultiIndex对象,然后调用DataFrame对象和Series对象的reindex方法来让MultiIndex对象替换掉原有的Index对象,或者直接在创建对象的到时候指定Index参数

下面就将讲解MultiIndex对象的创建方式,主要是调用MultiIndex这个类下的方法

from_arrays方法

from_array方法类似于直接创建具有多级索引的Series和DataFrame对象一样,通过给定二维数组或者多维数组来创建具有多级索引

MultiIndex_1=pd.MultiIndex.from_arrays([['a','a','a','b','b','b'],['a_1','a_1','a_2','b_1','b_1','b_2'],[1,2,1,1,2,1]])
print(MultiIndex_1)
print(pd.Series(np.arange(10,16,1),index=MultiIndex_1))
>>>
MultiIndex([('a', 'a_1', 1),
            ('a', 'a_1', 2),
            ('a', 'a_2', 1),
            ('b', 'b_1', 1),
            ('b', 'b_1', 2),
            ('b', 'b_2', 1)],
           )
a  a_1  1    10
        2    11
   a_2  1    12
b  b_1  1    13
        2    14
   b_2  1    15
dtype: int64
from_tuple方法

from_tuple方法用于从元组中生成MultiIndex对象,就像前面的不同地方不同年份的人口信息的例子一样,通过给定元组,调用from_tuple方法来创建

Index=[('PlaceA',2020),('PlaceA',2021),\
    ('PlaceB',2020),('PlaceB',2021),\
    ('PlaceC',2020),('PlaceC',2021)]
MultiIndex_1=pd.MultiIndex.from_tuples(Index)
print(MultiIndex_1)
print(pd.Series([100,200,300,400,500,600],index=MultiIndex_1))
>>>
MultiIndex([('PlaceA', 2020),
            ('PlaceA', 2021),
            ('PlaceB', 2020),
            ('PlaceB', 2021),
            ('PlaceC', 2020),
            ('PlaceC', 2021)],
           )
PlaceA  2020    100
		2021	200
PlaceB  2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64
from_product方法

from_product方法用于生成两个索引的笛卡尔内积,这个是最常用的

MultiIndex_1=pd.MultiIndex.from_product([['a','b'],[1,2]])
print(MultiIndex_1)
print(pd.Series([2,4,6,8],index=MultiIndex_1))
>>>
MultiIndex([('a', 1),
            ('a', 2),
            ('b', 1),
            ('b', 2)],
           )
a  1    2
   2    4
b  1    6
   2    8
dtype: int64

多级索引的等级名称

前面我们成功地创建了具有多级索引的DataFrame对象和Series对象,如果在多级索引的基础上,我们能够对于多级索引的每一级索引设置名称,那么无疑将会使数据结构更加的清晰

设置多级索引的等级名称主要是对MultiIndex对象的names属性进行设置,因此可以在创建MuitiIndex对象的时候指定names属性,也可以在完成整个DataFrame或Index对象后调用Index对象的names属性进行设置

MultiIndex_1=pd.MultiIndex.from_product([['a','b'],[1,2]],names=['l_1','l_2'])
Series_1=pd.Series(np.arange(1,5,1),index=list('abcd'))
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(4,4)),index=MultiIndex_1)

print(DataFrame_1)
print(Series_1)
Series_1.index.names=['l_1']
print(Series_1)
>>>
         0  1  2  3
l_1 l_2            
a   1    9  5  9  7
    2    2  5  5  9
b   1    1  6  3  1
    2    5  1  6  4
    
a    1
b    2
c    3
d    4
dtype: int64
    
l_1
a    1
b    2
c    3
d    4
dtype: int64

多级列索引

通过前面的讲述,我们其实不难发现Pandas中各种对象的关系.

Pandas自带Index和MultiIndex对象,以Numpy的数组对象为底层,根据数组对象的维度不同,添加不同数量的Index或者MultiIndex对象,最终构成Series,DataFrame,Panel,Panel4D对象

所以本质上,DataFrame对象的列索引和前面讲解的行索引其实是一样的,都是Index或者MultiIndex对象,所以我们实际上也能够创建多级列索引,并且为多级列索引命名

为DataFrame对象指定多级列索引很简单,只需要指定columns参数为MultiIndex对象即可

Columns=pd.MultiIndex.from_product([['a','b'],[1,2]],names=['le_1','le_2'])
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(4,4)),index=list('abcd'),columns=Columns)
print(DataFrame_1)
>>>
le_1  a     b   
le_2  1  2  1  2
a     0  4  2  6
b     5  5  1  9
c     3  4  9  3
d     8  8  1  5

接下来我们利用多级索引来模拟一份医学报告

ndex=pd.MultiIndex.from_product([[2019,2020],[1,2]],names=['YEAR','VISIT'])
Columns=pd.MultiIndex.from_product([['Jack','Sarah','Sue'],['HR','TEMP']],names=['NAME','TYPE'])

data=np.round(np.random.randn(4,6),1)
data[:,::2]*=10
data+=37

HealthData=pd.DataFrame(data,index=Index,columns=Columns)
print(HealthData)
>>>
NAME        Jack       Sarah         Sue      
TYPE          HR  TEMP    HR  TEMP    HR  TEMP
YEAR VISIT                                    
2019 1      43.0  37.0  23.0  36.6  19.0  38.1
     2      37.0  37.6  33.0  35.1  28.0  37.2
2020 1      35.0  35.7  47.0  38.9  28.0  38.8
     2      44.0  38.5  37.0  34.3  47.0  38.4

实际上,上面这个数据表具有四个维度,名字,年份,访问次数,体检项目,但是我们通过增加了两个多级索引,实现了降维,将一个四维数据实现了压堆

多级索引的取值与切片

由于Series对象和DataFrame对象的多级索引的取值与切片本质上是一样的,只不过默认状态下DataFrame对象是对列索引进行索引,所以将放在一起讲,

取单个值

我们可以通过逐个指定多级索引每一级的值,来获取单个元素

MultiIndex_1=pd.MultiIndex.from_product([['a','b'],[1,2]],names=['Le_1','Le_2'])
Series_1=pd.Series(np.random.randint(0,10,4),index=MultiIndex_1)
print(Series_1)
print(Series_1['b',2])
>>>
Le_1  Le_2
a     1       6
      2       7
b     1       4
      2       2
dtype: int64
2

取局部值

我们可以不指定低级的索引,只指定高级索引,即支取索引的某一个层次,这样将会返回一个新的Series对象

MultiIndex_1=pd.MultiIndex.from_product([['a','b','c'],[1,2]],names=['Le_1','Le_2'])
Series_1=pd.Series(np.arange(1,7,1),index=MultiIndex_1)
print(Series_1)
print(Series_1['a'])
print(Series_1['a':'b'])
>>>
Le_1  Le_2
a     1       1
      2       2
b     1       3
      2       4
c     1       5
      2       6
dtype: int64
Le_2
1    1
2    2
dtype: int64
Le_1  Le_2
a     1       1
      2       2
b     1       3
      2       4
dtype: int64

我们也可以只指定低级索引而不指定高级索引来获得一个新的Series对象.注意,高级索引要用切片

MultiIndex_1=pd.MultiIndex.from_product([['a','b','c'],[1,2]],names=['Le_1','Le_2'])
Series_1=pd.Series(np.arange(1,7,1),index=MultiIndex_1)
print(Series_1)
print(Series_1[:,1])
>>>
Le_1  Le_2
a     1       1
      2       2
b     1       3
      2       4
c     1       5
      2       6
dtype: int64
Le_1
a    1
b    3
c    5
dtype: int64

花哨的索引

对于DataFrame或者Series对象,我们可以使用花哨的索引来获取值

Index=pd.MultiIndex.from_product([['a','b','c'],[1,2,3]],names=['Le_1','Le_2'])
Columns=pd.MultiIndex.from_product([['A','B','C'],[1,2,3]],names=['Le_1','Le_2'])
Series_1=pd.Series(np.random.randint(0,10,9),index=Index)
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(9,9)),index=Index,columns=Columns)
print(Series_1)
print(Series_1[Series_1 >= 3])
print(DataFrame_1)
print(DataFrame_1[DataFrame_1 >=3])
>>>
Le_1  Le_2
a     1       5
      2       7
      3       5
b     1       9
      2       3
      3       0
c     1       7
      2       3
      3       8
dtype: int64
Le_1  Le_2
a     1       5
      2       7
      3       5
b     1       9
      2       3
c     1       7
      2       3
      3       8
dtype: int64
Le_1       A        B        C      
Le_2       1  2  3  1  2  3  1  2  3
Le_1 Le_2                           
a    1     2  5  9  8  1  5  4  2  6
     2     6  0  0  2  0  6  7  3  7
     3     0  8  5  9  7  9  4  3  0
b    1     2  9  4  0  0  1  3  3  7
     2     9  1  7  6  7  2  0  9  1
     3     1  0  9  1  9  9  6  2  9
c    1     8  6  1  3  5  1  6  8  7
     2     0  3  3  3  2  6  7  5  3
     3     3  5  2  4  7  2  1  9  2
Le_1         A              B              C          
Le_2         1    2    3    1    2    3    1    2    3
Le_1 Le_2                                             
a    1     NaN  5.0  9.0  8.0  NaN  5.0  4.0  NaN  6.0
     2     6.0  NaN  NaN  NaN  NaN  6.0  7.0  3.0  7.0
     3     NaN  8.0  5.0  9.0  7.0  9.0  4.0  3.0  NaN
b    1     NaN  9.0  4.0  NaN  NaN  NaN  3.0  3.0  7.0
     2     9.0  NaN  7.0  6.0  7.0  NaN  NaN  9.0  NaN
     3     NaN  NaN  9.0  NaN  9.0  9.0  6.0  NaN  9.0
c    1     8.0  6.0  NaN  3.0  5.0  NaN  6.0  8.0  7.0
     2     NaN  3.0  3.0  3.0  NaN  6.0  7.0  5.0  3.0
     3     3.0  5.0  NaN  4.0  7.0  NaN  NaN  9.0  NaN

索引器

具有层级索引的Series和DataFrame对象底层依旧是Numpy数组,因此我们依旧可以使用索引器来暴露底层的Numpy数组接口属性来获取值

iloc索引器

iloc索引器索引的依旧是隐式索引,所以没有什么变化

Index=pd.MultiIndex.from_product([['a','b','c'],[1,2,3]],names=['Le_1','Le_2'])
Columns=pd.MultiIndex.from_product([['A','B','C'],[1,2,3]],names=['Le_1','Le_2'])
Series_1=pd.Series(np.random.randint(0,10,9),index=Index)
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(9,9)),index=Index,columns=Columns)
print(Series_1)
print(Series_1.iloc[1:4])
print(DataFrame_1)
print(DataFrame_1.iloc[:,1:3])
>>>
Le_1  Le_2
a     1       1
      2       9
      3       2
b     1       5
      2       8
      3       8
c     1       7
      2       6
      3       2
dtype: int64
    
Le_1  Le_2
a     2       9
      3       2
b     1       5
dtype: int64
    
Le_1       A        B        C      
Le_2       1  2  3  1  2  3  1  2  3
Le_1 Le_2                           
a    1     6  4  9  7  3  7  1  9  6
     2     5  1  4  7  3  1  6  3  6
     3     5  6  5  8  3  7  6  0  0
b    1     1  3  4  8  4  2  4  4  4
     2     5  7  4  8  1  0  1  7  6
     3     1  8  9  1  1  2  8  3  0
c    1     7  1  3  0  9  9  0  8  8
     2     0  3  2  8  9  5  9  2  9
     3     8  1  9  8  9  0  6  6  3
        
Le_1       A   
Le_2       2  3
Le_1 Le_2      
a    1     4  9
     2     1  4
     3     6  5
b    1     3  4
     2     7  4
     3     8  9
c    1     1  3
     2     3  2
     3     1  9
loc索引器

loc索引器索引的显示索引,因此由于原始的Index对象被替换为MultiIndex对象,所以在显示索引的索引上会出现变化

和普通的索引不同,对于具有多级索引的Series或者DataFrame对象索引我们需要传递多级索引构成的索引元组

Index=pd.MultiIndex.from_product([['a','b','c'],[1,2,3]],names=['Le_1','Le_2'])
Series_1=pd.Series(np.random.randint(0,10,9),index=Index)
print(Series_1)
print(Series_1.loc[('a',2):('c',1)])
>>>
Le_1  Le_2
a     1       4
      2       0
      3       0
b     1       3
      2       9
      3       8
c     1       7
      2       1
      3       0
dtype: int64
Le_1  Le_2
a     2       0
      3       0
b     1       3
      2       9
      3       8
c     1       7
dtype: int64

但实际上,使用loc索引器非常少见,第一个原因就是传递的元组很容易搞混,在具有多个层级的索引时,其次对元组时候用切片操作会报错,再者,loc索引器对DataFrame对象的支持不是很好,因此一般使用下面的方法

IndexSlice对象

为了解决我们使用loc索引器索引显示索引时的不便,Pandas内置了另外一个对象IndexSlice,来帮助我们索引显示索引

我们只需要对创建的IndexSlice对象使用切片,并将获取的切片传递给loc索引器即可

Index=pd.MultiIndex.from_product([['a','b','c'],[1,2,3]],names=['Le_1','Le_2'])
Columns=pd.MultiIndex.from_product([['A','B','C'],[1,2,3]],names=['Le_1','Le_2'])
Idx=pd.IndexSlice
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(9,9)),index=Index,columns=Columns)
print(DataFrame_1)
print(DataFrame_1.loc[Idx[:,1],Idx[:,2:3]])
>>>
Le_1       A        B        C      
Le_2       1  2  3  1  2  3  1  2  3
Le_1 Le_2                           
a    1     8  7  4  8  4  4  5  0  4
     2     6  4  1  6  2  9  0  9  3
     3     9  8  8  8  7  5  2  7  2
b    1     5  2  8  3  0  5  3  5  1
     2     8  8  4  4  2  5  5  4  1
     3     9  8  3  0  8  0  7  0  7
c    1     9  5  2  5  9  6  4  7  2
     2     3  4  7  1  7  5  2  2  2
     3     3  3  5  7  7  6  7  3  3
        
Le_1       A     B     C   
Le_2       2  3  2  3  2  3
Le_1 Le_2                  
a    1     7  4  4  4  0  4
b    1     2  8  0  5  5  1
c    1     5  2  9  6  7  2

如上面的代码,我们只需要给IndexSlice对象进行切片即可

多级索引行列转换

使用多级索引的关键就是掌握有效的数据转换方法,为此Pandas提供了许多操作,可以让数据在内容保持不变的同时按照需求进行行列转换

前面讲过的stack和unstack就是可以用于行列索引转换的

有序索引和无序索引

前面我们讲解过对MultiIndex对象进行切片这类取值操作,但是对MultiIndex对象进行切片操作的基础是MultiIndex中的索引是有序的,即符合字典序

如果MultiIndex对象不满足字典序,那么对MultiIndex对象的大多数切片操作都会失效

MultiIndex_1=pd.MultiIndex.from_product([['a','b','c'],[1,2]],names=['Le_1','Le_2'])
MultiIndex_2=pd.MultiIndex.from_product([['a','c','b'],[1,2]],names=['Le_1','Le_2'])
Series_1=pd.Series([1,2,3,4,5,6],index=MultiIndex_1)
Series_2=pd.Series([1,2,3,4,5,6],index=MultiIndex_2)
print(Series_1)
print(Series_1['a':'b'])
print(Series_2['a':'b'])
>>>
Le_1  Le_2
a     1       1
      2       2
b     1       3
      2       4
c     1       5
      2       6
dtype: int64
Le_1  Le_2
a     1       1
      2       2
b     1       3
      2       4
dtype: int64
Traceback (most recent call last):
  File "TryPandas.py", line 410, in <module>
    print(Series_2['a':'b'])
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/series.py", line 910, in __getitem__
    return self._get_with(key)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/series.py", line 915, in _get_with
    return self._slice(key)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/series.py", line 865, in _slice
    slobj = self.index._convert_slice_indexer(slobj, kind=kind or "getitem")
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/indexes/base.py", line 2963, in _convert_slice_indexer
    indexer = self.slice_indexer(start, stop, step, kind=kind)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/indexes/base.py", line 4713, in slice_indexer
    start_slice, end_slice = self.slice_locs(start, end, step=step, kind=kind)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/indexes/multi.py", line 2543, in slice_locs
    return super().slice_locs(start, end, step, kind=kind)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/indexes/base.py", line 4926, in slice_locs
    start_slice = self.get_slice_bound(start, "left", kind)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/indexes/multi.py", line 2487, in get_slice_bound
    return self._partial_tup_index(label, side=side)
  File "/home/jackwang/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/core/indexes/multi.py", line 2548, in _partial_tup_index
    f"Key length ({len(tup)}) was greater than MultiIndex lexsort depth"
pandas.errors.UnsortedIndexError: 'Key length (1) was greater than MultiIndex lexsort depth (0)'

虽然报错信息非常不明显,但是我们前面讲过了这是因为我们的MultiIndex对象是无序的

为此,Pandas提供了许多便捷的操作来帮助我们完成排序,例如sort_index()和sortlevel()

这里只讲解用于索引排序的sort_index()方法

sort_index()方法是Series对象和DataFrame对象内置的方法,直接调用即可

MultiIndex_1=pd.MultiIndex.from_product([['a','c','b'],[1,2]],names=['Le_1','Le_2'])
MultiIndex_2=pd.MultiIndex.from_product([['A','C','B'],[1,2]],names=['Le_1','Le_2'])
Series_1=pd.Series([1,2,3,4,5,6],index=MultiIndex_1)
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(6,6)),index=MultiIndex_1,columns=MultiIndex_2)
print(Series_1)
print(Series_1.sort_index())
print(DataFrame_1)
print(DataFrame_1.sort_index())
>>>
Le_1  Le_2
a     1       1
      2       2
c     1       3
      2       4
b     1       5
      2       6
dtype: int64
    
Le_1  Le_2
a     1       1
      2       2
b     1       5
      2       6
c     1       3
      2       4
dtype: int64
    
Le_1       A     C     B   
Le_2       1  2  1  2  1  2
Le_1 Le_2                  
a    1     9  7  8  3  3  4
     2     5  0  5  5  8  5
c    1     1  8  8  8  1  1
     2     0  4  8  0  7  1
b    1     9  1  4  8  8  6
     2     6  8  7  6  8  7
    
Le_1       A     C     B   
Le_2       1  2  1  2  1  2
Le_1 Le_2                  
a    1     9  7  8  3  3  4
     2     5  0  5  5  8  5
b    1     9  1  4  8  8  6
     2     6  8  7  6  8  7
c    1     1  8  8  8  1  1
     2     0  4  8  0  7  1

索引的stack与unstack

前面简单的讲解过了stack与unstack方法,这里补充讲解一下,我们实际上可以通过制定level参数设置转换的索引层级

索引层级从高到低对应从0递增

MultiIndex_1=pd.MultiIndex.from_product([['PlaceA','PlaceB','PlaceC'],[2020,2021]],names=['PLACE','YEAR'])
Series_1=pd.Series([100,200,300,400,500,600],index=MultiIndex_1)
print(Series_1)
print(Series_1.unstack(0))
print(Series_1.unstack(1))
>>>
PLACE   YEAR
PlaceA  2020    100
        2021    200
PlaceB  2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64
    
PLACE  PlaceA  PlaceB  PlaceC
YEAR                         
2020      100     300     500
2021      200     400     600

YEAR    2020  2021
PLACE             
PlaceA   100   200
PlaceB   300   400
PlaceC   500   600

索引的设置与重置

我们对索引数据维度转换的另一种方法是行列标签转换为数据内容,可以通过reset_index方法实现

reset_index方法实际上是重新设置行列索引,不过一个额外的作用就是将原有的行列索引转换为数据内容,同时原有的索引名将作为列索引名

我们可以指定name属性来为列设置名称

MultiIndex_1=pd.MultiIndex.from_product([['PlaceA','PlaceB','PlaceC'],[2020,2021]],names=['PLACE','YEAR'])
Series_1=pd.Series([100,200,300,400,500,600],index=MultiIndex_1) 
print(Series_1)
print(Series_1.reset_index())
print(Series_1.reset_index(name='population'))
>>>
PLACE   YEAR
PlaceA  2020    100
        2021    200
PlaceB  2020    300
        2021    400
PlaceC  2020    500
        2021    600
dtype: int64
    
    PLACE  YEAR    0
0  PlaceA  2020  100
1  PlaceA  2021  200
2  PlaceB  2020  300
3  PlaceB  2021  400
4  PlaceC  2020  500
5  PlaceC  2021  600

    PLACE  YEAR  population
0  PlaceA  2020         100
1  PlaceA  2021         200
2  PlaceB  2020         300
3  PlaceB  2021         400
4  PlaceC  2020         500
5  PlaceC  2021         600

在解决实际问题时,我们通常是将原始输入的列转换成MultiIndex对象,然后使用Pandas的方法来处理处理数据

将列转换为MultiIndex对象,通常使用reset_index()方法的逆方法set_index()方法

MultiIndex_1=pd.MultiIndex.from_product([['PlaceA','PlaceB','PlaceC'],[2020,2021]],names=['PLACE','YEAR'])
Series_1=pd.Series([100,200,300,400,500,600],index=MultiIndex_1) 
Series_2=Series_1.reset_index()
print(Series_2)
print(Series_2.set_index(['PLACE','YEAR'])
>>>
    PLACE  YEAR    0
0  PlaceA  2020  100
1  PlaceA  2021  200
2  PlaceB  2020  300
3  PlaceB  2021  400
4  PlaceC  2020  500
5  PlaceC  2021  600
      
               0
PLACE  YEAR     
PlaceA 2020  100
       2021  200
PlaceB 2020  300
       2021  400
PlaceC 2020  500
       2021  600

多级索引的数据累计方法

对于Series对象和DataFrame对象,Pandas内置了对数据进行统计的方法,,结合多级索引,我们可以给这些方法指定level参数来指定统计的行

如果需要统计列的话指定不仅需要指定列索引,还要指定axis参数

MultiIndex_1=pd.MultiIndex.from_product([['a','b','c'],[1,2]],names=['Le_1','Le_2'])
MultiIndex_2=pd.MultiIndex.from_product([['A','B','C'],[1,2]],names=['Row_1','Row_2'])
DataFrame_1=pd.DataFrame(np.random.randint(0,10,(6,6)),index=MultiIndex_1,columns=MultiIndex_2)
print(DataFrame_1)
print(DataFrame_1.sum(level='Le_1',axis=0))
print(DataFrame_1.sum(level='Row_2',axis=1))
>>>
Row_1      A     B     C   
Row_2      1  2  1  2  1  2
Le_1 Le_2                  
a    1     5  2  1  0  1  2
     2     0  4  4  2  1  1
b    1     3  8  6  5  3  2
     2     3  8  0  0  9  6
c    1     5  5  7  7  4  3
     2     6  6  2  8  3  4
Row_1   A      B       C   
Row_2   1   2  1   2   1  2
Le_1                       
a       5   6  5   2   2  3
b       6  16  6   5  12  8
c      11  11  9  15   7  7
Row_2       1   2
Le_1 Le_2        
a    1      7   4
     2      5   7
b    1     12  15
     2     12  14
c    1     16  15
     2     11  18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值