检查数据框的元组中是否存在某个值。

3

我有一个非常大的数据框(3800万行):

df = pd.DataFrame({'I':[1,2,3,4], 'C':[80,160,240,80],
                   'F':[(1,2,3,4),(5,7,2),(9,6,2,5,7),(4,0,8,3,2)]})

     C                F  I
0   80     (1, 2, 3, 4)  1
1  160        (5, 7, 2)  2
2  240  (9, 6, 2, 5, 7)  3
3   80  (4, 0, 8, 3, 2)  4

现在我想要过滤掉包含数字3的行,在列'F'中。

处理后应为:

     C                F  I
0   80     (1, 2, 3, 4)  1
3   80  (4, 0, 8, 3, 2)  4

有没有一种高性能、低内存使用的方法来实现这个?我尝试过 np.equal((3), df['F'].values).all(),但显然这不起作用。
4个回答

7

如果性能很重要,请在列表推导式中使用in

df = df[[3 in x for x in df['F']]]

或者:

df = df[df['F'].apply(set) >= set([3])]

print (df)
   I   C                F
0  1  80     (1, 2, 3, 4)
3  4  80  (4, 0, 8, 3, 2)

性能(取决于匹配值的数量和df的长度):

#[40000 rows x 3 columns]
df = pd.concat([df] * 10000, ignore_index=True)


In [166]: %timeit df[[3 in x for x in df['F']]]
5.57 ms ± 132 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [167]: %timeit df[df['F'].apply(lambda x: 3 in x)]
12.2 ms ± 625 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [170]:  %timeit df[df['F'].apply(set) >= set([3])]
29 ms ± 396 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [171]:  %timeit df[pd.DataFrame(df['F'].values.tolist()).eq(3).any(1)]
37.4 ms ± 248 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

更好的结构,就像@jpp指出的那样创建:
from itertools import chain

lens = df['F'].str.len()
df = pd.DataFrame({
    'I' : df['I'].values.repeat(lens),
    'C' : df['C'].values.repeat(lens),
    'F' : list(chain.from_iterable(df['F'].tolist()))
})
print (df)
    I    C  F
0   1   80  1
1   1   80  2
2   1   80  3
3   1   80  4
4   2  160  5
5   2  160  7
6   2  160  2
7   3  240  9
8   3  240  6
9   3  240  2
10  3  240  5
11  3  240  7
12  4   80  4
13  4   80  0
14  4   80  8
15  4   80  3
16  4   80  2

谢谢大家,哪一个是最快的?我猜测是 set - Ward
@Ward - 我认为使用列表推导式的in是最快的。 - jezrael
@Ward - 添加建议的数据结构 - 将元组展平到列并重复“I”和“C”列。 - jezrael
扩展后的数据框更符合需求。但是,最好的解决方案取决于数组有多么不规则……比如,你会在一个元组中获取1个值还是在另一个元组中获取100个值。如果始终只有3-5个值,则我的解决方案可能更好。如果非常不规则,jezrael的扩展版本更好。 - jpp
@jpp - 是的,我同意,这取决于实际数据。但它不会创建NaN - 就像在您的解决方案中一样,而是重复行。 - jezrael

1
你应该使用in运算符与apply方法结合使用,通过传递一个lambda表达式来实现。
df[df['F'].apply(lambda x: 3 in x)]

输出

   I   C                F
0  1  80     (1, 2, 3, 4)
3  4  80  (4, 0, 8, 3, 2)

1

有一种高性能,低内存使用的方法可以做到这一点吗?

不,没有。 一系列元组不能进行矢量化。它由双层指针组成,不适合 Pandas / NumPy。您可以使用诸如 str 访问器或列表推导等技巧。或者,甚至尝试扩展为数据帧:

mask = pd.DataFrame(df['F'].values.tolist()).eq(3).any(1)

print(mask)

0     True
1    False
2    False
3     True
dtype: bool

但这些方法都很昂贵。为了提高性能,您应该在构建序列之前改善数据结构。


我使用元组是因为我认为它可以减少内存使用量。实际上,这是一个包含整数('F')的列表,每个整数都有与之关联的成本('C')和索引('I')。是否有更好的方式来优化序列的性能结构? - Ward
不,tuple 不会减少内存存储。NumPy 数组通过避免使用指针并使用一个连续的内存块来减少内存存储。 - jpp
那么,我应该将这些 int 列表放入 numpy 数组中,然后再放入数据框中吗? - Ward
如果您计划执行多个计算或存储数据,那么是的。事实上,如果您分解它,我的解决方案就是这样做的。对于一次性独立计算,jezrael的解决方案更好。 - jpp
不,我需要对存储的数据执行多个计算。 - Ward

0
一个简单的在loc内部应用就可以解决问题。
df.loc[df.F.apply(lambda t : 3 in t)]


    I   C   F
0   1   80  (1, 2, 3, 4)
3   4   80  (4, 0, 8, 3, 2)

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