Python: pandas中多级索引的高级操作讲解

Pandas库十分强大,在之前的文章中我已经介绍过了切片操作iloc, loc和ix,本篇文章主要介绍针对多级索引的高级操作。本质上与单级索引的操作相同,但是要注意一些语法的格式。

一、在Multiindex中使用loc

我们先建立一个多级索引的Dataframe:

import numpy as np
import pandas as pd
arrays = [np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux']),
       np.array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'])]
df = pd.DataFrame(np.random.randn(8, 3), index=arrays, columns=['A', 'B', 'C'])

df
Out[367]: 
                A         B         C
bar one -0.511892  0.572914  0.366185
    two  2.138491  0.110122  0.031379
baz one  1.838735  0.105448  0.802577
    two -0.583949 -0.393809  1.888609
foo one -0.822290  0.140792  0.566117
    two  1.105572 -0.385834  0.569369
qux one  0.913009  0.265028 -0.827504
    two -0.541688 -0.392419 -1.781416

使用loc索引其中一行:

df.loc[('bar', 'two'),]
Out[368]: 
A    2.138491
B    0.110122
C    0.031379
Name: (bar, two), dtype: float64

 如果想要某一具体的列:

df.loc[('bar', 'two'), 'A']
Out[369]: 2.1384908501995468

可以通过只索引部分索引得到全部级别的索引:

df.loc['baz':'foo']
Out[370]: 
                A         B         C
baz one  1.838735  0.105448  0.802577
    two -0.583949 -0.393809  1.888609
foo one -0.822290  0.140792  0.566117
    two  1.105572 -0.385834  0.569369

 通过提供元组的切片实现一个”范围“:

df.loc[('baz', 'two'):('qux', 'one')]
Out[371]: 
                A         B         C
baz two -0.583949 -0.393809  1.888609
foo one -0.822290  0.140792  0.566117
    two  1.105572 -0.385834  0.569369
qux one  0.913009  0.265028 -0.827504

也可以这样:

df.loc[('baz', 'two'):'foo']
Out[372]: 
                A         B         C
baz two -0.583949 -0.393809  1.888609
foo one -0.822290  0.140792  0.566117
    two  1.105572 -0.385834  0.569369

如果我们使用一个由元组组成的list来索引,就类似于reindex:

df.loc[[('bar', 'two'), ('qux', 'one')]]
Out[373]: 
                A         B         C
bar two  2.138491  0.110122  0.031379
qux one  0.913009  0.265028 -0.827504

注:在pandas的索引方面,元组和列表的处理方式并不相同。元组被解释为一个多级密钥,而列表用于指定多个密钥。或者换句话说,元组水平移动(遍历级别),列表垂直移动(扫描级别)​​。

即,由tuple组成的list会索引完整的MultiIndex键,而由list组成的tuple会引用一个级别中的多个值。举例说明:

s = pd.Series([1, 2, 3, 4, 5, 6],
 index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]))

s
Out[375]: 
A  c    1
   d    2
   e    3
B  c    4
   d    5
   e    6
dtype: int64

由tuple组成的list:

s.loc[[("A", "c"), ("B", "d")]]
Out[378]: 
A  c    1
B  d    5
dtype: int64

由list组成的tuple:


s.loc[(["A", "B"], ["c", "d"])]
Out[376]: 
A  c    1
   d    2
B  c    4
   d    5
dtype: int64

我们可以清晰看出这二者之间的区别。

二、使用切片器

我们先构造一个多级索引的数据集:

def mklbl(prefix,n):
     return ["%s%s" % (prefix,i)  for i in range(n)]


miindex = pd.MultiIndex.from_product([mklbl('A',4),
                                      mklbl('B',2),
                                      mklbl('C',4)])
 

micolumns = pd.MultiIndex.from_tuples([('a','foo'),('a','bar'),
                                        ('b','foo'),('b','bah')],
                                      names=['lvl0', 'lvl1'])


dfmi = pd.DataFrame(np.arange(len(miindex)*len(micolumns)).reshape((len(miindex),len(micolumns))),
                   index=miindex,
                   columns=micolumns).sort_index().sort_index(axis=1)

dfmi
Out[382]: 
lvl0        a         b     
lvl1      bar  foo  bah  foo
A0 B0 C0    1    0    3    2
      C1    5    4    7    6
      C2    9    8   11   10
      C3   13   12   15   14
   B1 C0   17   16   19   18
      C1   21   20   23   22
      C2   25   24   27   26
      C3   29   28   31   30
A1 B0 C0   33   32   35   34
      C1   37   36   39   38
      C2   41   40   43   42
      C3   45   44   47   46
   B1 C0   49   48   51   50
      C1   53   52   55   54
      C2   57   56   59   58
      C3   61   60   63   62
A2 B0 C0   65   64   67   66
      C1   69   68   71   70
      C2   73   72   75   74
      C3   77   76   79   78
   B1 C0   81   80   83   82
      C1   85   84   87   86
      C2   89   88   91   90
      C3   93   92   95   94
A3 B0 C0   97   96   99   98
      C1  101  100  103  102
      C2  105  104  107  106
      C3  109  108  111  110
   B1 C0  113  112  115  114
      C1  117  116  119  118
      C2  121  120  123  122
      C3  125  124  127  126

首先需要说明的是:slice(None)代表索引了那一级的全部内容。

使用了标签,列表和切分的基本的多级标签索引:

dfmi.loc[(slice('A1','A3'), slice(None), ['C1', 'C3']), :]
Out[402]: 
lvl0        a         b     
lvl1      bar  foo  bah  foo
A1 B0 C1   37   36   39   38
      C3   45   44   47   46
   B1 C1   53   52   55   54
      C3   61   60   63   62
A2 B0 C1   69   68   71   70
      C3   77   76   79   78
   B1 C1   85   84   87   86
      C3   93   92   95   94
A3 B0 C1  101  100  103  102
      C3  109  108  111  110
   B1 C1  117  116  119  118
      C3  125  124  127  126

可以看到,与二级索引区别不大,只不过是:前面的元组变成了3级。

也可以使用pandas.IndexSlice来替代slice(None)实现更自然的语法:

idx = pd.IndexSlice

dfmi.loc[idx[:, :, ['C1', 'C3']], idx[:, 'foo']]
Out[386]: 
lvl0        a    b
lvl1      foo  foo
A0 B0 C1    4    6
      C3   12   14
   B1 C1   20   22
      C3   28   30
A1 B0 C1   36   38
      C3   44   46
   B1 C1   52   54
      C3   60   62
A2 B0 C1   68   70
      C3   76   78
   B1 C1   84   86
      C3   92   94
A3 B0 C1  100  102
      C3  108  110
   B1 C1  116  118
      C3  124  126

可以在多个轴上同时使用此方法执行非常复杂的选择:

dfmi.loc['A1', (slice(None), 'foo')]
Out[387]: 
lvl0    a   b
lvl1  foo foo
B0 C0  32  34
   C1  36  38
   C2  40  42
   C3  44  46
B1 C0  48  50
   C1  52  54
   C2  56  58
   C3  60  62

dfmi.loc[idx[:, :, ['C1', 'C3']], idx[:, 'foo']]
Out[388]: 
lvl0        a    b
lvl1      foo  foo
A0 B0 C1    4    6
      C3   12   14
   B1 C1   20   22
      C3   28   30
A1 B0 C1   36   38
      C3   44   46
   B1 C1   52   54
      C3   60   62
A2 B0 C1   68   70
      C3   76   78
   B1 C1   84   86
      C3   92   94
A3 B0 C1  100  102
      C3  108  110
   B1 C1  116  118
      C3  124  126

也可以使用BOOL操作选取特定的行列:

mask = dfmi[('a', 'foo')] > 100

dfmi.loc[idx[mask, :, ['C1', 'C3']], idx[:, 'foo']]
Out[393]: 
lvl0        a    b
lvl1      foo  foo
A3 B0 C3  108  110
   B1 C1  116  118
      C3  124  126

还可以指定.loc的axis参数来解释单个轴上的切片器:

dfmi.loc(axis=0)[:, :, ['C1', 'C3']]
Out[394]: 
lvl0        a         b     
lvl1      bar  foo  bah  foo
A0 B0 C1    5    4    7    6
      C3   13   12   15   14
   B1 C1   21   20   23   22
      C3   29   28   31   30
A1 B0 C1   37   36   39   38
      C3   45   44   47   46
   B1 C1   53   52   55   54
      C3   61   60   63   62
A2 B0 C1   69   68   71   70
      C3   77   76   79   78
   B1 C1   85   84   87   86
      C3   93   92   95   94
A3 B0 C1  101  100  103  102
      C3  109  108  111  110
   B1 C1  117  116  119  118
      C3  125  124  127  126

使用下面的方法可以设置值:

df2 = dfmi.copy()
df2.loc(axis=0)[:, :, ['C1', 'C3']] = -10

Out[396]: 
lvl0        a         b     
lvl1      bar  foo  bah  foo
A0 B0 C0    1    0    3    2
      C1  -10  -10  -10  -10
      C2    9    8   11   10
      C3  -10  -10  -10  -10
   B1 C0   17   16   19   18
      C1  -10  -10  -10  -10
      C2   25   24   27   26
      C3  -10  -10  -10  -10
A1 B0 C0   33   32   35   34
      C1  -10  -10  -10  -10
      C2   41   40   43   42
      C3  -10  -10  -10  -10
   B1 C0   49   48   51   50
      C1  -10  -10  -10  -10
      C2   57   56   59   58
      C3  -10  -10  -10  -10
A2 B0 C0   65   64   67   66
      C1  -10  -10  -10  -10
      C2   73   72   75   74
      C3  -10  -10  -10  -10
   B1 C0   81   80   83   82
      C1  -10  -10  -10  -10
      C2   89   88   91   90
      C3  -10  -10  -10  -10
A3 B0 C0   97   96   99   98
      C1  -10  -10  -10  -10
      C2  105  104  107  106
      C3  -10  -10  -10  -10
   B1 C0  113  112  115  114
      C1  -10  -10  -10  -10
      C2  121  120  123  122
      C3  -10  -10  -10  -10

也可以使用可对齐对象的right-hand-side赋值:

df2.loc[idx[:, :, ['C1', 'C3']], :] = df2 * 1000

df2
Out[399]: 
lvl0           a               b        
lvl1         bar     foo     bah     foo
A0 B0 C0       1       0       3       2
      C1    5000    4000    7000    6000
      C2       9       8      11      10
      C3   13000   12000   15000   14000
   B1 C0      17      16      19      18
      C1   21000   20000   23000   22000
      C2      25      24      27      26
      C3   29000   28000   31000   30000
A1 B0 C0      33      32      35      34
      C1   37000   36000   39000   38000
      C2      41      40      43      42
      C3   45000   44000   47000   46000
   B1 C0      49      48      51      50
      C1   53000   52000   55000   54000
      C2      57      56      59      58
      C3   61000   60000   63000   62000
A2 B0 C0      65      64      67      66
      C1   69000   68000   71000   70000
      C2      73      72      75      74
      C3   77000   76000   79000   78000
   B1 C0      81      80      83      82
      C1   85000   84000   87000   86000
      C2      89      88      91      90
      C3   93000   92000   95000   94000
A3 B0 C0      97      96      99      98
      C1  101000  100000  103000  102000
      C2     105     104     107     106
      C3  109000  108000  111000  110000
   B1 C0     113     112     115     114
      C1  117000  116000  119000  118000
      C2     121     120     123     122
      C3  125000  124000  127000  126000

上面的介绍以loc作为介绍。iloc是以position为准,要比loc使用更简单一些。

参考文献:

https://pandas.pydata.org/pandas-docs/stable/advanced.html#using-slicers

猜你喜欢

转载自blog.csdn.net/anshuai_aw1/article/details/83510345
今日推荐