Pandas:多级索引的布尔索引

15

这里有很多标题类似的问题,但我找不到一个针对这个问题的。

我有来自许多不同来源的数据框,并且我想通过其他数据框进行过滤。当布尔序列的大小与筛选的数据框相同时,使用布尔索引非常有效,但是当序列的大小与筛选数据框的一个更高级别索引相同时,则无法使用。

简而言之,假设我有以下数据框:

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]

1
df[df.index.get_level_values('a').isin(filt)]怎么样?我认为你不能按照你的建议来做你想做的事情,因为你的系列与MultiIndex的一个级别不是“相同大小”。正常显示只会显示该级别的三个值,但是每个MultiIndex级别实际上与整个DataFrame一样长。 - BrenBarn
如果一定要这样做,那么应该是 df[df.index.get_level_values('a').isin(filt[filt].index)]。我想 "不,这是不可能的" 是一个公正的答案。 - Korem
8个回答

7
如果要将索引'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
2
@Korem,它确实像您发布的那样工作,您只是忘记了答案中的.values - joris
@joris 这个看起来运行速度更快(在真实的大规模数据上进行了基准测试),所以我会接受这个 - 但还是谢谢你的帮助! - Korem

3

如果布尔系列与您想要索引的数据框不对齐,您可以先使用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

注意:在Series中无法使用align,因此需要在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]相比,优势在于您对索引具有更多控制权。
多级索引切片的主题在这里中深入讨论。
以下是完整代码:
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, :], :])

1
更易读(我喜欢的)解决方案是重新索引布尔系列(dataframe),以匹配多级索引df的索引:
df.loc[filt.reindex(df.index, level='a')]

0

在 @Markus Dutschke 的 答案 基础上,需要注意的是 IndexSlice 对象可以只创建一次,然后反复使用(甚至用于切片不同的对象)。我发现这样可以创建更易读的代码,特别是当在同一个 .loc 中同时对多级索引行和列进行两次切片时。

将此应用于他的答案并稍微简化(不需要 .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, :], :])

0

不确定在大规模数据框上速度会有多快/慢,但我有时会这样做:

df.loc[filt[filt].index]

问题在于loc方法只能在一维索引上使用布尔输入。如果您提供要保留的第一级元素的值,则可以正常进行操作。因此,通过使用自身过滤filt(因为它在一维索引上),并保留其索引中的值,即可实现您的目标。

0

简单来说:

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

解释:

  • .rename_axis('a')索引重命名为a(我们想要按其过滤的索引)
  • .rename('c')重命名为c(存储值的列)
  • .to_frame() 将此系列转换为DataFrame,以便与df兼容
  • df.where(...) 过滤行,使过滤器为False时留下缺失值(NaN
  • .drop_na() 删除具有缺失值的行(在我们的例子中是a == 2
  • .astype(int)float转换回int(不确定为什么一开始是float

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


哈哈。谢谢!我喜欢你的解决方案以“简单地”开始 :) - normanius

0

我遇到了完全相同的问题。我找到了这个问题并尝试了这里的解决方案,但是它们中没有一个足够高效。我的数据框架是:A = 700k行 x 14列B = 100M行 x 3列B具有MultiIndex,其中第一(高)级别等于A的索引。让CA的大小为10k行的切片。我的任务是尽快从B中获取其高级索引与C的索引匹配的行。在运行时选择CAB是静态的。

我尝试了这里的解决方案:get_level_values需要很长时间,df.align甚至没有完成就给出了MemoryError(而且还花了几秒钟)。

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

  1. 对于来自 A 的每个 i 值的索引,找到包含 i 作为 MultiIndex 第一级的第一个和最后一个(不包括)在 B 中的位置索引。将这些对存储在 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接