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