当前位置: 首页 > 知识库问答 >
问题:

在pandas多索引数据框中选择行

富凯旋
2023-03-14
  • 基于单个值/标签的切片
  • 基于一个或多个级别的多个标签的切片
  • 布尔条件和表达式的过滤
  • 哪些方法适用于什么情况

为简单起见的假设:

  1. 输入数据表没有重复的索引键
  2. 下面的输入数据只有两个级别。(此处所示的大多数解决方案都概括为N个级别)
mux = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    list('tuvwtuvwtuvwtuvw')
], names=['one', 'two'])

df = pd.DataFrame({'col': np.arange(len(mux))}, mux)

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    u      5
    v      6
    w      7
    t      8
c   u      9
    v     10
d   w     11
    t     12
    u     13
    v     14
    w     15
         col
one two     
a   t      0
    u      1
    v      2
    w      3
     col
two     
t      0
u      1
v      2
w      3
         col
one two     
a   t      0
b   t      4
    t      8
d   t     12
         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

问题2b
我如何获得级别“二”中对应于“t”和“w”的所有值?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

如何从df检索横截面,即具有索引特定值的单行?具体来说,如何检索('c','u')的横截面,由

         col
one two     
c   u      9

如何选择与('c','u')('a','w')相对应的两行?

         col
one two     
c   u      9
a   w      3
         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12
         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15
np.random.seed(0)
mux2 = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    np.random.choice(10, size=16)
], names=['one', 'two'])

df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2)

         col
one two     
a   5      0
    0      1
    3      2
    3      3
b   7      4
    9      5
    3      6
    5      7
    2      8
c   4      9
    7     10
d   6     11
    8     12
    8     13
    1     14
    6     15
         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

共有1个答案

南门朗
2023-03-14


本帖的结构如下:

  1. 将逐一讨论OP中提出的问题
  2. 对于每个问题,将演示一个或多个适用于解决此问题并获得预期结果的方法。

对于有兴趣了解附加功能、实现细节和手头主题的其他信息的读者,将包括注释(非常类似于本文)。这些笔记是通过搜索文档和发现各种晦涩难懂的特征,并根据我自己(承认有限)的经验汇编而成的。

dataframe.xs-从series/dataframe中提取一个特定的截面。

dataframe.query-动态地指定切片和/或筛选操作(例如,作为动态计算的html" target="_blank">表达式。在某些情况下比在其他情况下更适用。也请参阅文档的这一节,以查询多索引。

使用multiindex.get_level_values生成带有掩码的布尔索引(通常与index.isin结合使用,尤其是在使用多个值进行筛选时)。这在某些情况下也很有用。

从这四个习语的角度来看各种切片和过滤问题将是有益的,可以更好地理解什么可以应用于给定的情况。理解并不是所有的习语在每种情况下都同样有效(如果有的话)是非常重要的。如果一个习语没有被列为下面问题的潜在解决方案,这意味着习语不能有效地应用于该问题。

如何选择级别“一”中具有“A”的行?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

您可以使用loc,作为适用于大多数情况的通用解决方案:

df.loc[['a']]
TypeError: Expected tuple, got str
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)

这里,需要drop_level=false参数来防止XS删除结果中的级别“一”(我们切片的级别)。

这里的另一个选项是使用query:

df.query("one == 'a'")

如果索引没有名称,则需要将查询字符串更改为“ilevel_0=='a'”

df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
     col
two     
t      0
u      1
v      2
w      3
df.loc['a'] # Notice the single string argument instead the list.

或者,

df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')

注意,我们可以省略drop_level参数(默认情况下假定为true)。

注意
您可能会注意到,经过筛选的DataFrame可能仍然具有所有级别,即使在打印出DataFrame时没有显示这些级别。例如,

v = df.loc[['a']]
print(v)
         col
one two     
a   t      0
    u      1
    v      2
    w      3

print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])
v.index = v.index.remove_unused_levels()
print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])
         col
one two     
a   t      0
b   t      4
    t      8
d   t     12
df.loc[(slice(None), 't'), :]

它只是工作!™,但它是笨重的。我们可以使用pd.indexsliceAPI简化更自然的切片语法。

idx = pd.IndexSlice
df.loc[idx[:, 't'], :]

这里干净多了。

注意
为什么需要跨列的尾随片:?这是因为,loc可用于沿着两个轴进行选择和切片(axis=0axis=1)。如果不明确表示要在哪个轴上进行切片,操作就会变得模棱两可。请参阅切片文档中的红色大框。

df.loc(axis=0)[pd.IndexSlice[:, 't']]

使用xs,它是

df.xs('t', axis=0, level=1, drop_level=False)

对于query,它是

df.query("two == 't'")
# Or, if the first level has no name, 
# df.query("ilevel_1 == 't'") 

最后,使用get_level_values,您可以

df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15
df.loc[['b', 'd']]
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')

并且,使用get_level_values+index.isin:

df[df.index.get_level_values("one").isin(['b', 'd'])]

我将如何获得所有的值对应的“T”和“W”在级别“二”?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

对于loc,这只能与pd.indexslice结合。

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas') 
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
df[df.index.get_level_values('two').isin(['t', 'w'])]

如何从df检索横截面,即具有索引特定值的单行?具体来说,如何检索('c','u')的横截面,由

         col
one two     
c   u      9

通过指定一组键来使用loc:

df.loc[('c', 'u'), :]

或者,

df.loc[pd.IndexSlice[('c', 'u')]]
PerformanceWarning: indexing past lexsort depth may impact performance.
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
df.xs(('c', 'u'))
df.query("one == 'c' and two == 'u'")

你现在可以看到,这将是相对难以概括的。但对于这个特殊的问题仍然可以。

对于跨越多个级别的访问,仍然可以使用get_level_values,但不建议使用:

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]

如何选择与('c','u')('a','w')相对应的两行?

         col
one two     
c   u      9
a   w      3
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses) 

query = '(' + ') or ('.join([
    ' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)]) 
    for cs in cses
]) + ')'

print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))

df.query(query)

如果我有多个级别呢?
在这种情况下,一个选项是使用Droplevel删除您没有检查的级别,然后使用isin测试成员资格,然后对最终结果进行布尔索引。

df[df.index.droplevel(unused_level).isin([('c', 'u'), ('a', 'w')])]

如何检索与级别“一”中的“A”或级别“二”中的“T”对应的所有行?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

这实际上很难用loc来实现,同时确保正确性并保持代码的清晰度。df.loc[pd.indexslice['a',not']]不正确,它被解释为df.loc[pd.indexslice[('a',not')]](即选择横截面)。您可以考虑使用pd.concat单独处理每个标签的解决方案:

pd.concat([
    df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])

         col
one two     
a   t      0
    u      1
    v      2
    w      3
    t      0   # Does this look right to you? No, it isn't!
b   t      4
    t      8
d   t     12

但是您会注意到其中一行是重复的。这是因为该行满足两个切片条件,因此出现了两次。相反,您需要做的是

v = pd.concat([
        df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]

但是,如果您的DataFrame本身包含重复的索引(您想要的),那么这将不会保留它们。极其谨慎地使用。

对于query,这非常简单:

df.query("one == 'a' or two == 't'")
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2] 
         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

通常,像这样的切片问题需要显式地将键列表传递给loc。一种方法是:

keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]

如果您想保存一些键入,您将认识到有一种模式可以对“a”、“B”及其子层进行切片,因此我们可以将切片任务分成两个部分,并concat得到结果:

pd.concat([
     df.loc[(('a', 'b'), ('u', 'v')), :], 
     df.loc[('d', 'w'), :]
   ], axis=0)

“a”和“b”的切片规范(('a','b'),('u','v')),因为索引的子级别对每个级别都是相同的。

如何获取级别“Two”中的值大于5的所有行?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

这可以使用query完成,

df2.query("two > 5")

get_level_values

df2[df2.index.get_level_values('two') > 5]

如果需要对multiindex列进行切片,该怎么办?

np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
        list('ABCD'), list('efgh')
], names=['one','two'])

df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)

one  A           B           C           D         
two  e  f  g  h  e  f  g  h  e  f  g  h  e  f  g  h
0    5  0  3  3  7  9  3  5  2  4  7  6  8  8  1  6
1    7  7  8  1  5  9  8  9  4  3  0  3  5  0  2  3
2    8  1  3  3  3  7  0  1  9  9  0  4  7  3  2  7

以下是您需要对四个习惯用法进行的更改,以便它们能够与列一起工作。

>

  • 要使用loc进行切片,请使用

     df3.loc[:, ....] # Notice how we slice across the index with `:`. 
    

    或者,

     df3.loc[:, pd.IndexSlice[...]]
    
     df.loc[:, {condition}] 
    
     df3.T.query(...).T
    

  •  类似资料:
    • 问题内容: 我在不同的列中有数据,但是我不知道如何提取数据以将其保存在另一个变量中。 如何选择然后将其保存到df1中? 我试过了 似乎没有任何工作。 问题答案: 列名(字符串)无法按照你尝试的方式进行切片。 在这里,你有两个选择。如果从上下文中知道要切出哪些变量,则可以通过将列表传递给语法来仅返回那些列的视图。 或者,如果需要对它们进行数字索引而不是按其名称进行索引(例如,你的代码应在不知道前两列

    • 问题内容: 我有以下pd.DataFrame: 它具有带有和层次结构级别的MultiIndex列。该标签从0到n,并为每个标签,有两个和列。 我想子选择此DataFrame的所有(或)列。 问题答案: 有一种方法可以与布尔索引一起使用,以获得预期的结果。

    • 我试图提取数据帧中的第7到14列。然而 只给出第1到第3行。 有人知道如果我想找到这些专栏我该怎么做吗? 我知道如何使用列名,如,但由于名称太多,我只想在R中键入类似的内容。 提前感谢。

    • 是否有任何方法可以在数据框中按索引(即整数)选择行,按列名选择列? 我尝试使用loc,但它返回一个错误,我知道iloc只适用于索引。 这是数据帧df的第一行。我愿意选择第一行,名为“Volume”的列,并尝试使用df.loc[0,'Volume']

    • 问题内容: 摘要:这不起作用: 但是这样做: 为什么? 再生产: 这不起作用: 但是这样做: 链接到笔记本 我的问题是: 为什么只有第二种方式起作用?我似乎看不到选择/索引逻辑的差异。 版本是0.10.0 编辑:这不应该再这样了。从0.11版开始,提供。参见此处:http : //pandas.pydata.org/pandas- docs/stable/indexing.html 问题答案: 大

    • 我正在尝试从多索引数据帧中仅使用一个索引来创建新的数据帧。 理想情况下,我想要这样的东西: 和: 基本上,我想删除除level之外的多索引的所有其他索引。有没有一个简单的方法可以做到这一点?