【问题标题】:pandas: Boolean indexing with multi indexpandas:具有多索引的布尔索引
【发布时间】:2022-04-02 04:48:28
【问题描述】:

这里有很多标题相似的问题,但我找不到解决这个问题的问题。

我有来自许多不同来源的数据框,我想一个一个地过滤。当布尔系列与过滤后的数据帧大小相同时,使用布尔索引非常有效,但当系列的大小与过滤后的数据帧的更高级别的索引相同时则不行。

简而言之,假设我有这个数据框:

In [4]: df = pd.DataFrame({'a':[1,1,1,2,2,2,3,3,3], 
                           'b':[1,2,3,1,2,3,1,2,3], 
                           'c':range(9)}).set_index(['a', 'b'])
Out[4]: 
     c
a b   
1 1  0
  2  1
  3  2
2 1  3
  2  4
  3  5
3 1  6
  2  7
  3  8

还有这个系列:

In [5]: filt = pd.Series({1:True, 2:False, 3:True})
Out[6]: 
1     True
2    False
3     True
dtype: bool

我想要的输出是这样的:

     c
a b   
1 1  0
  2  1
  3  2
3 1  6
  2  7
  3  8

我不是在寻找不使用filt系列的解决方案,例如:

df[df.index.get_level_values('a') != 2]
df[df.index.get_level_values('a').isin([1,3])]

我想知道我是否可以按原样使用我的输入 filt 系列,因为我会在 c: 上使用过滤器

filt = df.c < 7
df[filt]

【问题讨论】:

  • df[df.index.get_level_values('a').isin(filt)] 怎么样?我不认为你可以按照你建议的方式做你想做的事,因为你的系列与 MultiIndex 的一个级别“大小不同”。正常显示仅显示该级别的三个值,但 MultiIndex 的每个级别实际上与整个 DataFrame 的长度相同。
  • @BrenBarn 如果有的话,它必须是df[df.index.get_level_values('a').isin(filt[filt].index)]。我想“不,那是不可能的”是一个公平的答案。

标签: python pandas


【解决方案1】:

如果您将索引“a”转换回一列,您可以按如下方式进行:

>>> df = pd.DataFrame({'a':[1,1,1,2,2,2,3,3,3], 
                       'b':[1,2,3,1,2,3,1,2,3], 
                       'c':range(9)})
>>> filt = pd.Series({1:True, 2:False, 3:True})
>>> df[filt[df['a']].values]
   a  b  c
0  1  1  0
1  1  2  1
2  1  3  2
6  3  1  6
7  3  2  7
8  3  3  8

编辑。 正如@joris 所建议的,这也适用于索引。这是您的示例数据的代码:

>>> df[filt[df.index.get_level_values('a')].values]
     c
a b   
1 1  0
  2  1
  3  2
3 1  6
  2  7
  3  8

【讨论】:

  • 这很好!我认为在不重置索引的情况下也可以执行df[filt[df.index.get_level_values('a')]],但这会导致ValueError: cannot reindex from a duplicate axis,无论如何绕过它?
  • @Korem 它确实像您发布的那样工作,您只是忘记了答案中的.values
  • @joris 这似乎工作得更快(以真实的大规模数据为基准),所以我会接受这个 - 但感谢您的帮助!
【解决方案2】:

如果布尔系列未与您要为其编制索引的数据框对齐,您可以先明确地将其与 align 对齐:

In [25]: df_aligned, filt_aligned = df.align(filt.to_frame(), level=0, axis=0)

In [26]: filt_aligned
Out[26]:
         0
a b
1 1   True
  2   True
  3   True
2 1  False
  2  False
  3  False
3 1   True
  2   True
  3   True

然后你就可以用它来索引了:

In [27]: df[filt_aligned[0]]
Out[27]:
     c
a b
1 1  0
  2  1
  3  2
3 1  6
  2  7
  3  8

注意:对齐不适用于系列,因此 align 调用中的 to_frame 以及上面的 [0] 以获取系列。

【讨论】:

    【解决方案3】:

    您可以使用pd.IndexSlicer

    >>> df.loc[pd.IndexSlice[filt[filt].index.values, :], :]
         c
    a b   
    1 1  0
      2  1
      3  2
    3 1  6
      2  7
      3  8
    

    其中filt[filt].index.values 就是[1, 3]。换句话说

    >>> df.loc[pd.IndexSlice[[1, 3], :]]
         c
    a b   
    1 1  0
      2  1
      3  2
    3 1  6
      2  7
      3  8
    

    所以如果你设计你的过滤器结构有点不同,表达式会变短。 Emanuele Paolini 的解决方案 df[filt[df.index.get_level_values('a')].values] 的优势在于,您可以更好地控制索引。

    here 更深入地介绍了多索引切片的主题。

    这里有完整的代码

    import pandas as pd
    import numpy as np
    df = pd.DataFrame({'a':[1,1,1,2,2,2,3,3,3], 'b':[1,2,3,1,2,3,1,2,3], 'c':range(9)}).set_index(['a', 'b'])
    filt = pd.Series({1:True, 2:False, 3:True})
    
    print(df.loc[pd.IndexSlice[[1, 3], :]])
    print(df.loc[(df.index.levels[0].values[filt], slice(None)), :])
    print(df.loc[pd.IndexSlice[filt[filt].index.values, :], :])
    

    【讨论】:

      【解决方案4】:

      更具可读性(我喜欢)的解决方案是重新索引布尔系列(数据框)以匹配多索引 df 的索引:

      df.loc[filt.reindex(df.index, level='a')]
      

      【讨论】:

        【解决方案5】:

        我面临着完全相同的问题。我发现了这个问题并在这里尝试了解决方案,但没有一个足够有效。我的数据框是:A = 700k rows x 14 colsB = 100M rows x 3 colsB 有一个MultiIndex,其中第一个(高)级别等于A 的索引。让C 成为来自A 大小为10k 行的切片。我的任务是尽可能快地从B 的高级索引匹配C 的索引中获取行。 C 在运行时被选中。 AB 是静态的。

        我从这里尝试了解决方案:get_level_values 需要很多秒,df.align 甚至没有完成给MemoryError(也需要几秒)。

        对我有用的解决方案(在运行时在~300msec 中)如下:

        1. 对于来自A 的每个indexi,在B 中找到第一个和最后一个(不包含)位置索引,其中包含i 作为第一级多索引。将这些对存储在A 中。这是一次并提前完成的。 示例代码:

          def construct_position_indexes(A, B):
              indexes = defaultdict(list)
              prev_index = 0
              for i, cur_index in enumerate(B.index.get_level_values(0)):
                  if cur_index != prev_index:
                      indexes[cur_index].append(i)
                      if prev_index:
                          indexes[prev_index].append(i)
                  prev_index = cur_index
              indexes[cur_index].append(i+1)
              index_df = pd.DataFrame(indexes.values(),
                                      index=indexes.keys(),
                                      columns=['start_index', 'end_index'], dtype=int)
              A = A.join(index_df)
              # they become floats, so we fix that
              A['start_index'] = A.start_index.fillna(0).astype(int)
              A['end_index'] = A.end_index.fillna(0).astype(int)
              return A
          
        2. 在运行时,从C获取位置边界,并构造一个所有位置索引的列表以在B中搜索,并将它们传递给B.take()

          def get_slice(B, C):
              all_indexes = []
              for start_index, end_index in zip(
                      C.start_index.values, C.end_index.values):
                  all_indexes.extend(range(start_index, end_index))
              return B.take(all_indexes)
          

        我希望它不会太复杂。本质上,这个想法是为A中的每一行存储B中行的对应(位置)索引的范围,以便在运行时我们可以快速构建所有位置索引的列表以查询B

        这是一个玩具示例:

        A = pd.DataFrame(range(3), columns=['dataA'], index=['A0', 'A1', 'A2'])
        print A
        
            dataA
        A0      0
        A1      1
        A2      2
        
        mindex = pd.MultiIndex.from_tuples([
            ('A0', 'B0'), ('A0', 'B1'), ('A1', 'B0'), 
            ('A2', 'B0'), ('A2', 'B1'), ('A2', 'B3')])
        B = pd.DataFrame(range(6), columns=['dataB'], index=mindex)
        print B
        
               dataB
        A0 B0      0
           B1      1
        A1 B0      2
        A2 B0      3
           B1      4
           B3      5
        
        A = construct_position_indexes(A, B)
        print A
        
            dataA  start_index  end_index
        A0      0            0          2
        A1      1            2          3
        A2      2            3          6
        
        C = A.iloc[[0, 2], :]
        print C
        
            dataA  start_index  end_index
        A0      0            0          2
        A2      2            3          6
        
        print get_slice(B, C)
        
               dataB
        A0 B0      0
           B1      1
        A2 B0      3
           B1      4
           B3      5
        

        【讨论】:

          【解决方案6】:

          简单地说:

          df.where(
              filt.rename_axis('a').rename('c').to_frame()
          ).dropna().astype(int)
          

          解释:

          • .rename_axis('a')index 重命名为 a(我们要过滤的索引)
          • .rename('c')重命名为c(存储值的列)
          • .to_frame() 将此 Series 转换为 DataFrame,以与 df 兼容
          • df.where(...) 过滤行,留下缺失值 (NaN) 其中过滤器为 False
          • .drop_na() 删除缺失值的行(在我们的例子中是a == 2
          • .astype(int)float 转换回 int(不知道为什么要以 float 开头)

          顺便说一句,df.where(...)df[...] 在这里的行为似乎相似,所以请自行选择。

          【讨论】:

          • 哈哈。谢谢!我喜欢你的解决方案以“简单”开头:)
          【解决方案7】:

          不确定在大规模数据帧上的速度有多快/多慢,但我有时会做的是

          df.loc[filt[filt].index]
          

          问题在于loc 方法仅适用于一维索引上的布尔输入。如果您提供了要保留的第一级元素的值,那么您就可以开始了。因此,通过使用自身过滤 filt(因为它位于一维索引上)并保留其索引中的值,您就可以实现目标。

          【讨论】:

            【解决方案8】:

            以@Markus Dutschke 的answer 为基础,请注意IndexSlice 对象可以只创建一次,然后反复使用(甚至可以分割不同的对象)。我发现这会创建更具可读性的代码,尤其是在使用它两次对同一.loc 中的两个 MultiIndex 行和列进行切片时。

            将此应用于他的答案并稍微简化(不需要 .values):

            idx = pd.IndexSlice
            df.loc[idx[filt[filt].index, :], :]
            

            或完整代码:

            import pandas as pd
            import numpy as np
            df = pd.DataFrame({'a':[1,1,1,2,2,2,3,3,3], 'b':[1,2,3,1,2,3,1,2,3], 'c':range(9)}).set_index(['a', 'b'])
            filt = pd.Series({1:True, 2:False, 3:True})
            idx = pd.IndexSlice
            
            print(df.loc[idx[[1, 3], :]])
            print(df.loc[(df.index.levels[0].values[filt], slice(None)), :])
            print(df.loc[idx[filt[filt].index, :], :])
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-08-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-06-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多