代码之家  ›  专栏  ›  技术社区  ›  cs95 abhishek58g

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

  •  29
  • cs95 abhishek58g  · 技术社区  · 6 年前

    目标与动机

    这个 MultiIndex 然而,多年来,API越来越受欢迎,并不是所有关于它的内容都能在结构、工作和相关操作方面被完全理解。

    一个重要的操作是 过滤 .过滤是一个常见的需求,但是用例是多样的。因此,某些方法和函数将比其他方法更适用于某些用例。

    总之,本文的目的是探讨一些常见的过滤问题和用例,演示解决这些问题的各种不同方法,并讨论它们的适用性。这篇文章要解决的一些高级问题是

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

    这些问题分为6个具体问题,列举如下。为了简单起见,下面设置中的示例数据帧只有两个级别,并且没有重复的索引键。大多数问题的解决方案都可以归纳为n个层次。

    这个职位将 介绍如何创建多索引,如何对其执行分配操作,或任何与性能相关的讨论(这些是另一次单独的主题)。


    问题

    问题1-6将在下面的设置上下文中被询问。

    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
    如何在级别“2”上对值为“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
    如何获得“2”级中与“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: 每层一件
    如何检索“1”级中的“a”和“2”级中的“u”对应的所有行?

             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
    

    问题6: 基于不等式的数值级滤波
    如何获取级别“2”中的值大于5的所有行?

             col
    one two     
    b   7      4
        9      5
    c   7     10
    d   6     11
        8     12
        8     13
        6     15
    
    1 回复  |  直到 6 年前
        1
  •  25
  •   cs95 abhishek58g    6 年前

    MultiIndex / Advanced Indexing

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

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

    注释 对于有兴趣了解其他功能、实现细节和 以及其他粗略的信息。这些笔记 通过对文档的梳理和对各种晦涩难懂的内容的揭示而编纂而成 特点,并从我自己(公认的有限)的经验。

    所有代码示例都已创建并测试于 熊猫v0.23.4,蟒蛇3.7 . 如果有什么不清楚,或事实上不正确,或如果你没有 找到一个适用于您的用例的解决方案,请随时 建议编辑、在评论中要求澄清或打开新的 问题……如适用。

    下面是我们将经常访问的一些常用习语(以下简称“四个习语”)的介绍。

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

    2. DataFrame.xs -从序列/数据帧中提取特定的横截面。

    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
    

    这意味着你使用的是老版本的熊猫。考虑升级!否则,使用 df.loc[('a', slice(None)), :] .

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

    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 默认情况下)。

    注释
    您可能会注意到,筛选的数据帧可能仍然具有所有级别,即使它们在打印数据帧时不显示。例如,

    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

    如何在级别“2”上对值为“t”的所有行进行切片?

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

    凭直觉,你会想要一些 slice() :

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

    它只是工作!_,但是它很笨重。我们可以使用 PDD索引片 API在这里。

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

    这要干净得多。

    注释
    为什么尾随片 : 是否需要跨越列?这是因为, 洛克 可用于沿两个轴选择和切片( axis=0 axis=1 )没有明确说明切片的轴 将在上执行,操作将变得不明确。看到大红盒在 documentation on slicing .

    如果你想消除任何模棱两可的阴影, 洛克 接受一个 参数:

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

    没有 参数(即 df.loc[pd.IndexSlice[:, 't']] ,假定切片在柱上, 和A KeyError 会在这种情况下长大。

    记录在 slicers . 然而,在本文中,我们将明确地指定所有轴。

    XS 它是

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

    查询 它是

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

    最后,用 获取\级别\值 你可以这样做

    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”的问题,您还可以使用 查询 :

    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() .

    以及 获取\级别\值 + 索引索引 :

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

    问题2B

    如何获得“2”级中与“t”和“w”对应的所有值?

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

    洛克 ,这是可能的 只有 与…结合 PDD索引片 .

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

    第一结肠 : 在里面 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'])]
    

    问题3

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

             col
    one two     
    c   u      9
    

    使用 洛克 通过指定键的元组:

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

    或者,

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

    注释
    此时,您可能会遇到 PerformanceWarning 看起来是这样的:

    PerformanceWarning: indexing past lexsort depth may impact performance.
    

    这只意味着索引没有排序。pandas依赖于被排序的索引(在本例中,是从词典的角度,因为我们处理的是字符串值)来优化搜索和检索。快速解决方法是 数据帧预先使用 DataFrame.sort_index . 如果您计划这样做,从性能角度来看,这是特别可取的 多个这样的查询串联在一起:

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

    您也可以使用 MultiIndex.is_lexsorted() 检查索引是否 是否排序。此函数返回 False 因此。 您可以调用此函数来确定附加排序 是否需要步骤。

    XS ,这再次简单地将一个元组作为第一个参数传递,并将所有其他参数设置为相应的默认值:

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

    查询 ,事情变得有点笨拙:

    df.query("one == 'c' and two == 'u'")
    

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

    通道跨越多个层次, 获取\级别\值 仍可以使用,但不建议使用:

    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
    

    洛克 ,这仍然简单到:

    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)
    

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


    问题5

    如何检索级别“1”中与“a”对应的所有行,以及 “U”在二级?

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

    这实际上很难处理 洛克 同时确保正确性 仍然保持代码清晰。 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()]
    

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

    查询 太简单了:

    df.query("one == 'a' or two == 't'")
    

    获取\级别\值 ,这仍然很简单,但没有那么优雅:

    m1 = (df.index.get_level_values('one') == 'c')
    m2 = (df.index.get_level_values('two') == 'u')
    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
    

    这是我为帮助理解四个习惯用法的适用性而添加的一个特殊情况,因为切片是 非常 具体,不遵循任何实际模式。

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

    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

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

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

    这可以用 查询 ,

    df2.query("two > 5")
    

    获取\级别\值 .

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

    注释
    与本例类似,我们可以使用这些构造基于任意条件进行过滤。一般来说,记住这一点很有用 洛克 XS 专门用于基于标签的索引,而 查询 获取\级别\值 有助于构建通用条件掩码 用于过滤。


    红利问题

    如果我需要切一片 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. 切成薄片 洛克 使用

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

      或者,

      df3.loc[:, pd.IndexSlice[...]]
      
    2. 使用 XS 如果合适,只需传递一个参数 轴=1 .

    3. 您可以直接使用 df.columns.get_level_values . 然后你需要做一些像

      df.loc[:, {condition}] 
      

      在哪里? {condition} 表示使用 columns.get_level_values .

    4. 使用 查询 ,您唯一的选择是转置、查询索引并再次转置:

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

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