Python-pandas过滤串联的多个子串

发布于 2021-02-02 23:19:39

我需要过滤pandas数据帧中的行,以便特定的字符串列包含提供的子字符串列表中的至少一个。子字符串可能具有不寻常的/正则表达式字符。比较不应该涉及正则表达式,并且不区分大小写。

例如:

lst = ['kdSj;af-!?', 'aBC+dsfa?\-', 'sdKaJg|dksaf-*']

我目前使用这样的面具:

mask = np.logical_or.reduce([df[col].str.contains(i, regex=False, case=False) for i in lst])
df = df[mask]

我的数据框很大(〜1mio行),lst长度为100。是否有更有效的方法?例如,如果找到其中的第一项,lst则我们不必测试该行的任何后续字符串。

关注者
0
被浏览
76
1 个回答
  • 面试哥
    面试哥 2021-02-02
    为面试而生,有面试问题,就找面试哥。

    如果你坚持使用纯熊猫,那么出于性能和实用性的考虑,我认为你应该使用正则表达式来完成此任务。但是,你将需要首先适当地转义子字符串中的任何特殊字符,以确保它们在字面上匹配(并且不用作正则表达式元字符)。

    这很容易做到re.escape

    >>> import re
    >>> esc_lst = [re.escape(s) for s in lst]
    

    然后可以使用正则表达式管道将这些转义的子字符串连接起来|。可以对照字符串检查每个子字符串,直到找到一个匹配项(或它们都已经过测试)。

    >>> pattern = '|'.join(esc_lst)
    

    然后,掩蔽阶段将成为遍历各行的单个低级循环:

    df[col].str.contains(pattern, case=False)
    

    这是一个简单的设置,可以让你感受到性能:

    from random import randint, seed
    
    seed(321)
    
    # 100 substrings of 5 characters
    lst = [''.join([chr(randint(0, 256)) for _ in range(5)]) for _ in range(100)]
    
    # 50000 strings of 20 characters
    strings = [''.join([chr(randint(0, 256)) for _ in range(20)]) for _ in range(50000)]
    
    col = pd.Series(strings)
    esc_lst = [re.escape(s) for s in lst]
    pattern = '|'.join(esc_lst)
    

    提出的方法大约需要1秒(因此,一百万行最多可能需要20秒):

    %timeit col.str.contains(pattern, case=False)
    1 loop, best of 3: 981 ms per loop
    

    使用相同的输入数据,问题中的方法花费了大约5秒钟。

    值得注意的是,在没有匹配的情况下,这些时间是“最坏的情况”(因此检查了所有子字符串)。如果有比赛,则计时会有所改善。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看