【问题标题】:Select rows in pandas MultiIndex DataFrame在 pandas MultiIndex DataFrame 中选择行
【发布时间】:2019-05-24 10:40:54
【问题描述】:

pandas 选择/过滤dataframe whose index is a MultiIndex 行的最常见方法是什么?

  • 基于单个值/标签的切片
  • 基于一个或多个级别的多个标签进行切片
  • 过滤布尔条件和表达式
  • 哪些方法适用于哪些情况

简单假设:

  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

问题 1:选择单个项目

如何在“一”级选择具有“a”的行?

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

此外,我如何才能在输出中删除“一”级?

     col
two     
t      0
u      1
v      2
w      3

问题 1b
如何在“第二”级对所有值为“t”的行进行切片?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

问题 2:在一个关卡中选择多个值

如何在“一级”中选择与“b”和“d”项对应的行?

         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

问题 3:切片单个横截面 (x, y)

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

         col
one two     
c   u      9

问题 4:切片多个横截面[(a, b), (c, d), ...]

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

         col
one two     
c   u      9
a   w      3

问题 5:每个级别切割一件物品

如何检索与“一级”中的“a”或“二级”中的“t”对应的所有行?

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

问题 6:任意切片

如何切割特定的横截面?对于“a”和“b”,我想选择具有子级别“u”和“v”的所有行,对于“d”,我想选择具有子级别“w”的行。

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

问题 7 将使用由数字级别组成的独特设置:

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

问题 7:在多索引的各个级别上按数字不等式过滤

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

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

注意:这篇文章将介绍如何创建 MultiIndex,如何对它们执行分配操作,或任何与性能相关的讨论(这些是另一次单独的主题)。

【问题讨论】:

标签: python pandas dataframe slice multi-index


【解决方案1】:

MultiIndex / Advanced Indexing

注意
这篇文章的结构如下:

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

注释s(很像这个)将为有兴趣了解其他功能、实现细节的读者提供, 和其他关于手头主题的粗略信息。这些笔记已 通过搜索文档并发现各种晦涩难懂的内容进行编译 功能,以及我自己(诚然有限)的经验。

所有代码示例均已在 pandas v0.23.4、python3.7 上创建和测试。如果某些事情不清楚,或者事实上不正确,或者如果您没有 找到适用于您的用例的解决方案,请随时 建议编辑,在 cmets 中要求澄清,或打开新的 问题,......如适用。

这里介绍一些我们会经常重温的常用成语(以下简称四成语)

  1. DataFrame.loc - 按标签选择的通用解决方案(+ pd.IndexSlice 适用于涉及切片的更复杂应用)

  2. DataFrame.xs - 从 Series/DataFrame 中提取特定的横截面。

  3. DataFrame.query - 动态指定切片和/或过滤操作(即,作为动态评估的表达式。比其他情况更适用于某些场景。另请参阅this section of the docs 以查询多索引。

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

根据四个成语来查看各种切片和过滤问题将有助于更好地了解可以应用于特定情况的内容。了解并非所有成语在每种情况下都同样有效(如果有的话)是非常重要的。如果一个成语没有被列为以下问题的潜在解决方案,则意味着该成语不能有效地应用于该问题。


问题 1

如何在“一”级选择具有“a”的行?

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

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

df.loc[['a']]

此时,如果你得到

TypeError: Expected tuple, got str

这意味着您使用的是旧版本的 pandas。考虑升级!否则,请使用df.loc[('a', slice(None)), :]

或者,您可以在此处使用xs,因为我们正在提取单个横截面。注意levelsaxis 参数(这里可以假设合理的默认值)。

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'"

最后,使用get_level_values

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'])

您可以使用MultiIndex.remove_unused_levels 摆脱这些级别:

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'])

问题 1b

如何在“第二”级对所有值为“t”的行进行切片?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

直觉上,你会想要一些涉及slice()的东西:

df.loc[(slice(None), 't'), :]

It Just Works!™ 但它很笨重。我们可以在这里使用pd.IndexSlice API 来促进更自然的切片语法。

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

这样干净多了。

注意
为什么需要跨列的尾随切片:?这是因为,loc 可用于沿两个轴选择和切片(axis=0axis=1)。没有明确说明切片是哪个轴 是要做上,操作变得模棱两可。请参阅documentation on slicing 中的大红框。

如果您想消除任何歧义,loc 接受 axis 参数:

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

如果没有axis 参数(即,只需执行df.loc[pd.IndexSlice[:, 't']]),则假定在列上进行切片, 在这种情况下会产生KeyError

这在slicers 中有记录。但是,出于本文的目的,我们将明确指定所有轴。

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']

都是一样的效果。


问题 2

如何在“一级”中选择与“b”和“d”项对应的行?

         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

使用 loc,通过指定列表以类似的方式完成。

df.loc[['b', 'd']]

解决上述选择“b”和“d”的问题,也可以使用query

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')

注意
是的,默认解析器是'pandas',但重要的是要强调这种语法不是传统的python。这 Pandas 解析器生成的解析树与 表达。这样做是为了让一些操作更直观 指定。欲了解更多信息,请阅读我的帖子 Dynamic Expression Evaluation in pandas using pd.eval().

还有,get_level_values + Index.isin

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

问题 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

对于loc,这只有可以与pd.IndexSlice结合使用。

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 

pd.IndexSlice[:, ['t', 'w']] 中的第一个冒号: 表示跨第一级切片。随着被查询级别的深度增加,您将需要指定更多的切片,每个级别一个切片。但是,您不需要指定更多级别超出被切片的级别。

query,这是

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')

使用get_level_valuesIndex.isin(与上面类似):

df[df.index.get_level_values('two').isin(['t', 'w'])]

问题 3

如何检索横截面,即具有特定值的单行 对于来自df 的索引?具体来说,我如何找回十字架 ('c', 'u') 的部分,由

给出
         col
one two     
c   u      9

通过指定一个键元组来使用loc

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

或者,

df.loc[pd.IndexSlice[('c', 'u')]]

注意
此时,您可能会遇到如下所示的PerformanceWarning

PerformanceWarning: indexing past lexsort depth may impact performance.

这只是意味着您的索引未排序。 pandas 依赖于被排序的索引(在这种情况下,按字典顺序,因为我们正在处理字符串值)以获得最佳搜索和检索。一个快速的解决方法是对你的 DataFrame 提前使用DataFrame.sort_index。如果您打算这样做,从性能的角度来看,这尤其可取 多个这样的查询串联:

df_sort = df.sort_index()
df_sort.loc[('c', 'u')]

也可以使用MultiIndex.is_lexsorted()查看索引是否 是否排序。此函数相应地返回TrueFalse。 可以调用这个函数来判断是否进行了额外的排序 step 是否需要。

对于xs,这再次简单地将单个元组作为第一个参数传递,所有其他参数设置为适当的默认值:

df.xs(('c', 'u'))

使用query,事情变得有点笨拙:

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]

问题 4

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

         col
one two     
c   u      9
a   w      3

使用loc,这仍然很简单:

df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]

使用query,您需要通过遍历横截面和级别来动态生成查询字符串:

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)

100% 不推荐!但这是可能的。

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

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

问题 5

如何检索与“一”级中的“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', 't']] 不正确,它被解释为df.loc[pd.IndexSlice[('a', 't')]](即选择横截面)。您可能会想到使用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'")

使用get_level_values,这仍然很简单,但没有那么优雅:

m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2] 

问题 6

如何切割特定的横截面?对于“a”和“b”,我想选择具有子级别“u”和“v”的所有行,并且 对于“d”,我想选择具有子级别“w”的行。

         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')),因为被索引的相同子级别对于每个级别都是相同的。


问题 7

如何获取“二级”中的值大于 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]

注意
与此示例类似,我们可以使用这些构造基于任意条件进行过滤。通常,请记住 locxs 专门用于基于标签的索引,而 queryget_level_values 有助于构建通用条件掩码 用于过滤。


奖金问题

如果我需要对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

这些是您需要对四个成语进行以下更改才能使它们与列一起使用。

  1. 要使用loc 进行切片,请使用

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

    或者,

     df3.loc[:, pd.IndexSlice[...]]
    
  2. 要适当地使用xs,只需传递一个参数axis=1

  3. 您可以使用df.columns.get_level_values 直接访问列级别值。然后你需要做类似的事情

     df.loc[:, {condition}] 
    

    其中{condition} 表示使用columns.get_level_values 构建的某些条件。

  4. 要使用query,您唯一的选择是转置,查询索引,然后再次转置:

     df3.T.query(...).T
    

    不推荐,使用其他 3 个选项之一。

【讨论】:

    【解决方案2】:

    最近我遇到了一个用例,我有一个 3 级以上的多索引数据框,在该用例中,我无法使上述任何解决方案产生我正在寻找的结果。上述解决方案很可能确实适用于我的用例,我尝试了几个,但是我无法让它们在我有空的时间内工作。

    我远非专家,但我偶然发现了上面综合答案中未列出的解决方案。我不保证解决方案在任何方面都是最优的。

    这是获得与上述问题 #6 略有不同的结果的另一种方法。 (可能还有其他问题)

    特别是我在寻找:

    1. 一种从一级索引中选择两个以上值并从另一级索引中选择一个值的方法,以及
    2. 一种将先前操作的索引值保留在数据帧输出中的方法。

    作为齿轮中的活动扳手(但完全可以修复):

    1. 索引未命名。

    在下面的玩具数据框上:

        index = pd.MultiIndex.from_product([['a','b'],
                                   ['stock1','stock2','stock3'],
                                   ['price','volume','velocity']])
    
        df = pd.DataFrame([1,2,3,4,5,6,7,8,9,
                          10,11,12,13,14,15,16,17,18], 
                           index)
    
                            0
        a stock1 price      1
                 volume     2
                 velocity   3
          stock2 price      4
                 volume     5
                 velocity   6
          stock3 price      7
                 volume     8
                 velocity   9
        b stock1 price     10
                 volume    11
                 velocity  12
          stock2 price     13
                 volume    14
                 velocity  15
          stock3 price     16
                 volume    17
                 velocity  18
    

    当然,使用下面的作品:

        df.xs(('stock1', 'velocity'), level=(1,2))
    
            0
        a   3
        b  12
    

    但我想要一个不同的结果,所以我得到这个结果的方法是:

       df.iloc[df.index.isin(['stock1'], level=1) & 
               df.index.isin(['velocity'], level=2)] 
    
                            0
        a stock1 velocity   3
        b stock1 velocity  12
    

    如果我想要一个级别的两个+值和另一个级别的单个(或 2+)值:

        df.iloc[df.index.isin(['stock1','stock3'], level=1) & 
                df.index.isin(['velocity'], level=2)] 
    
                            0
        a stock1 velocity   3
          stock3 velocity   9
        b stock1 velocity  12
          stock3 velocity  18
    

    上述方法可能有点笨拙,但我发现它满足了我的需求,而且我更容易理解和阅读。

    【讨论】:

    • 很好,不知道level 的参数Index.isin
    • 还有xs 方法如果没有找到就会引发错误,不像isin 返回空列表。
    【解决方案3】:

    这对于 dfsql

    来说似乎是一个很好的案例
    df.sql(<SQL select statement>)
    

    https://github.com/mindsdb/dfsql

    这里有一篇关于它的完整文章:

    https://medium.com/riselab/why-every-data-scientist-using-pandas-needs-modin-bringing-sql-to-dataframes-3b216b29a7c0

    【讨论】:

    • 不要只提供链接。请添加一些示例。为相应的子问题添加解决方案。
    猜你喜欢
    • 1970-01-01
    • 2019-12-02
    • 2020-05-26
    • 2018-10-14
    • 2019-01-26
    • 2022-06-15
    相关资源
    最近更新 更多