如何在 pandas 中测试一个字符串是否包含列表中的任意一个子字符串?

254

有没有一个函数可以相当于df.isin()df[col].str.contains()的组合?

例如,假设我有系列数据s = pd.Series(['cat','hat','dog','fog','pet']),我想找到所有包含['og', 'at']中的任何一个的地方,我希望得到除'pet'之外的所有内容。

我有一个解决方案,但它相当不简洁:

searchfor = ['og', 'at']
found = [s.str.contains(x) for x in searchfor]
result = pd.DataFrame[found]
result.any()

有没有更好的方法来做这件事?


5
注意:@unutbu在这里描述了一种比使用pd.Series.str.contains更有效的解决方案。如果性能是一个问题,那么这可能值得探究。 - jpp
5
强烈推荐查看这个答案,它介绍了如何使用多个关键词/正则表达式进行部分字符串搜索(向下滚动到“多个子字符串搜索”小标题)。 - cs95
在问题的具体示例中,您可以使用带有元组参数的 pd.Series.str.endswith:https://pandas.pydata.org/docs/reference/api/pandas.Series.str.endswith.html - user7868
4个回答

437

一个选项是仅使用正则表达式|字符,尝试在你的Series s中匹配每个单词的子字符串(仍然使用str.contains)。

您可以通过将searchfor中的单词与|连接起来来构造正则表达式:

>>> searchfor = ['og', 'at']
>>> s[s.str.contains('|'.join(searchfor))]
0    cat
1    hat
2    dog
3    fog
dtype: object

正如下面评论中@AndyHayden所指出的,如果你的子字符串包含一些特殊字符比如$^,而你想要匹配它们的字面意思时,请注意。这些字符在正则表达式的上下文中有具体的意义并会影响匹配结果。

你可以通过使用re.escape对非字母数字字符进行转义,来使你的子字符串列表更加安全:

>>> import re
>>> matches = ['$money', 'x^y']
>>> safe_matches = [re.escape(m) for m in matches]
>>> safe_matches
['\\$money', 'x\\^y']

使用 str.contains 时,新列表中的字符串将与每个字符逐字匹配。


4
或许可以添加这个链接 http://pandas.pydata.org/pandas-docs/stable/text.html#splitting-and-replacing-strings 。从 pandas 0.15 开始,字符串操作更加容易。 - goofd
7
需要注意的一件事是,如果searchfor中的字符串具有特殊的正则表达式字符(可以使用re.escape进行转义)。 - Andy Hayden
我不知道为什么你的方法不能与“str.startswith('|'.join(searchfor))”一起使用。 - Doo Hyun Shin
2
在这种情况下,我理解我们使用“|”表示或,那么我们如何使用AND? - The Dan

105

您可以使用 str.contains 和正则表达式模式中的 OR (|) 一起:

s[s.str.contains('og|at')]

或者您可以将该系列添加到 dataframe 中,然后使用 str.contains

df = pd.DataFrame(s)
df[s.str.contains('og|at')] 

输出:

0 cat
1 hat
2 dog
3 fog 

3
如何在 AND 中实现它? - JacoSolari
3
@JacoSolari 请查看此答案 https://dev59.com/-FoU5IYBdhLWcg3w35nN - James
3
@James,好的,谢谢。为了完整起见,在这里提供答案中最受欢迎的一行代码。 df.col.str.contains(r'(?=.*apple)(?=.*banana)',regex=True) - JacoSolari

15

这里是一个能够工作的一行lambda表达式:

df["TrueFalse"] = df['col1'].apply(lambda x: 1 if any(i in x for i in searchfor) else 0)

输入:

searchfor = ['og', 'at']

df = pd.DataFrame([('cat', 1000.0), ('hat', 2000000.0), ('dog', 1000.0), ('fog', 330000.0),('pet', 330000.0)], columns=['col1', 'col2'])

   col1  col2
0   cat 1000.0
1   hat 2000000.0
2   dog 1000.0
3   fog 330000.0
4   pet 330000.0

应用Lambda:

df["TrueFalse"] = df['col1'].apply(lambda x: 1 if any(i in x for i in searchfor) else 0)

输出:

    col1    col2        TrueFalse
0   cat     1000.0      1
1   hat     2000000.0   1
2   dog     1000.0      1
3   fog     330000.0    1
4   pet     330000.0    0

7
我使用了df.loc[df.col1.apply(lambda x: True if any(i in x for i in searchfor) else False)]来完成任务,效果很好,谢谢。 - emremrah

0

我曾经遇到过同样的问题。不用让它变得太复杂,你可以在每个条目之间添加|,例如fieldname.str.contains("cat|dog")就可以解决问题。


你好,这个解决方案已经提供了(https://dev59.com/wF8d5IYBdhLWcg3wpzn4#26578218),请尽量避免重复回答。 - Alexander L. Hayes

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