通过子字符串条件过滤熊猫DataFrame

我有一个带有字符串值列的熊猫DataFrame。我需要根据部分字符串匹配选择行。

像这样的成语:

re.search(pattern, cell_in_question)

返回一个布尔值。我熟悉df[df['A'] == "hello world"]的语法,但似乎找不到一种方法来对部分字符串匹配执行相同的操作,比如'hello'

1236033 次浏览

这是我最终对部分字符串匹配所做的。如果有人有更有效的方法,请告诉我。

def stringSearchColumn_DataFrame(df, colName, regex):newdf = DataFrame()for idx, record in df[colName].iteritems():
if re.search(regex, record):newdf = concat([df[df[colName] == record], newdf], ignore_index=True)
return newdf

矢量化字符串方法(即#0)让您执行以下操作:

df[df['A'].str.contains("hello")]

这在熊猫0.8.1和更高版本中可用。

快速提示:如果您想根据索引中包含的部分字符串进行选择,请尝试以下操作:

df['stridx']=df.indexdf[df['stridx'].str.contains("Hello|Britain")]

我在ipython笔记本的macos上使用熊猫0.14.1。我尝试了上面建议的行:

df[df["A"].str.contains("Hello|Britain")]

并得到一个错误:

不能索引包含NA/NaN值的向量

但是当添加“==True”条件时,它工作得很好,如下所示:

df[df['A'].str.contains("Hello|Britain")==True]

假设您有以下DataFrame

>>> df = pd.DataFrame([['hello', 'hello world'], ['abcd', 'defg']], columns=['a','b'])>>> dfa            b0  hello  hello world1   abcd         defg

您始终可以在lambda表达式中使用in运算符来创建过滤器。

>>> df.apply(lambda x: x['a'] in x['b'], axis=1)0     True1    Falsedtype: bool

这里的技巧是使用apply中的axis=1选项逐行将元素传递给lambda函数,而不是逐列传递。

如果有人想知道如何执行相关问题:"按部分字符串选择列"

用途:

df.filter(like='hello')  # select columns which contain the word hello

要通过部分字符串匹配来选择行,请将axis=0传递给filter:

# selects rows which contain the word hello in their index labeldf.filter(like='hello', axis=0)

如何从熊猫DataFrame中通过部分字符串进行选择?

这篇文章是为想要的读者准备的

  • 在字符串列中搜索子字符串(最简单的情况),如df1[df1['col'].str.contains(r'foo(?!$)')]
  • 搜索多个子字符串(类似于isin),例如df4[df4['col'].str.contains(r'foo|baz')]
  • 匹配文本中的整个单词(例如,“蓝色”应该匹配“天空是蓝色的”,但不匹配“蓝色鸟”),例如,使用df3[df3['col'].str.contains(r'\bblue\b')]
  • 匹配多个整词
  • 理解“ValueError:无法使用包含NA/NaN值的向量进行索引”背后的原因并使用str.contains('pattern',na=False)进行更正

…并想知道更多关于什么方法应该优先于其他方法。

(附言:我看到很多类似主题的问题,我想把这个留在这里会很好。

友好免责声明,这篇文章是长期


基本子字符串搜索

# setupdf1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})df1
col0     foo1  foobar2     bar3     baz

str.contains可用于执行子字符串搜索或基于正则表达式的搜索。除非您明确禁用它,否则搜索默认为基于正则表达式。

这是一个基于正则表达式的搜索示例,

# find rows in `df1` which contain "foo" followed by somethingdf1[df1['col'].str.contains(r'foo(?!$)')]
col1  foobar

有时不需要正则表达式搜索,因此指定regex=False以禁用它。

#select all rows containing "foo"df1[df1['col'].str.contains('foo', regex=False)]# same as df1[df1['col'].str.contains('foo')] but faster.   
col0     foo1  foobar

性能方面,正则表达式搜索比子字符串搜索慢:

df2 = pd.concat([df1] * 1000, ignore_index=True)
%timeit df2[df2['col'].str.contains('foo')]%timeit df2[df2['col'].str.contains('foo', regex=False)]
6.31 ms ± 126 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)2.8 ms ± 241 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

避免使用基于正则表达式的搜索,如果你不需要它。

地址#0
有时,对结果执行子字符串搜索和过滤会导致

ValueError: cannot index with vector containing NA / NaN values

这通常是因为对象列中的混合数据或NaN,

s = pd.Series(['foo', 'foobar', np.nan, 'bar', 'baz', 123])s.str.contains('foo|bar')
0     True1     True2      NaN3     True4    False5      NaNdtype: object

s[s.str.contains('foo|bar')]# ---------------------------------------------------------------------------# ValueError                                Traceback (most recent call last)

任何不是字符串的东西都不能应用字符串方法,所以结果是NaN(自然)。在这种情况下,指定na=False忽略非字符串数据,

s.str.contains('foo|bar', na=False)
0     True1     True2    False3     True4    False5    Falsedtype: bool

如何一次将其应用于多个列?
答案就在问题中。使用#0

# `axis=1` tells `apply` to apply the lambda function column-wise.df.apply(lambda col: col.str.contains('foo|bar', na=False), axis=1)
A      B0   True   True1   True  False2  False   True3   True  False4  False  False5  False  False

下面的所有解决方案都可以使用按列apply方法“应用”到多个列(在我的书中可以,只要你没有太多列)。

如果您有一个具有混合列的DataFrame并且只想选择对象/字符串列,请查看select_dtypes


多重子串搜寻

这最容易通过使用regex OR管道的regex搜索来实现。

# Slightly modified example.df4 = pd.DataFrame({'col': ['foo abc', 'foobar xyz', 'bar32', 'baz 45']})df4
col0     foo abc1  foobar xyz2       bar323      baz 45
df4[df4['col'].str.contains(r'foo|baz')]
col0     foo abc1  foobar xyz3      baz 45

您还可以创建一个术语列表,然后加入它们:

terms = ['foo', 'baz']df4[df4['col'].str.contains('|'.join(terms))]
col0     foo abc1  foobar xyz3      baz 45

有时,对术语进行转义是明智的,以防它们具有可以解释为正则表达式元字符的字符。如果您的术语包含以下任何字符…

. ^ $ * + ? { } [ ] \ | ( )

然后,您需要使用re.escape逃脱

import redf4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]
col0     foo abc1  foobar xyz3      baz 45

re.escape具有转义特殊字符的效果,因此它们按字面意思处理。

re.escape(r'.foo^')# '\\.foo\\^'

匹配整个单词

默认情况下,子字符串搜索搜索指定的子字符串/模式,而不管它是否是完整的单词。为了只匹配完整的单词,我们需要在这里使用正则表达式-特别是,我们的模式需要指定单词边界(\b)。

例如,

df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})df3
col0        the sky is blue1  bluejay by the window 

现在考虑,

df3[df3['col'].str.contains('blue')]
col0        the sky is blue1  bluejay by the window

v/s

df3[df3['col'].str.contains(r'\bblue\b')]
col0  the sky is blue

多个整词搜索

与上面类似,除了我们在连接模式中添加一个字边界(\b)。

p = r'\b(?:{})\b'.format('|'.join(map(re.escape, terms)))df4[df4['col'].str.contains(p)]
col0  foo abc3   baz 45

p看起来像这样,

p# '\\b(?:foo|baz)\\b'

一个很好的选择:使用列表理解

因为你可以!而且你应该!它们通常比字符串方法快一点,因为字符串方法很难向量化,并且通常具有循环实现。

而不是,

df1[df1['col'].str.contains('foo', regex=False)]

在列表comp中使用in运算符,

df1[['foo' in x for x in df1['col']]]
col0  foo abc1   foobar

而不是,

regex_pattern = r'foo(?!$)'df1[df1['col'].str.contains(regex_pattern)]

在列表比较中使用re.compile(缓存你的正则表达式)+Pattern.search

p = re.compile(regex_pattern, flags=re.IGNORECASE)df1[[bool(p.search(x)) for x in df1['col']]]
col1  foobar

如果“cols”有NaNs,则不是

df1[df1['col'].str.contains(regex_pattern, na=False)]

使用,

def try_search(p, x):try:return bool(p.search(x))except TypeError:return False
p = re.compile(regex_pattern)df1[[try_search(p, x) for x in df1['col']]]
col1  foobar 

部分模式匹配的更多选项:np.char.findnp.vectorizeDataFrame.query

除了str.contains和列表推导,您还可以使用以下替代方案。

np.char.find
仅支持子字符串搜索(读取:无正则表达式)。

df4[np.char.find(df4['col'].values.astype(str), 'foo') > -1]
col0     foo abc1  foobar xyz

np.vectorize
这是一个围绕循环的包装器,但比大多数熊猫str方法的开销要小。

f = np.vectorize(lambda haystack, needle: needle in haystack)f(df1['col'], 'foo')# array([ True,  True, False, False])
df1[f(df1['col'], 'foo')]
col0  foo abc1   foobar

可能的正则表达式解决方案:

regex_pattern = r'foo(?!$)'p = re.compile(regex_pattern)f = np.vectorize(lambda x: pd.notna(x) and bool(p.search(x)))df1[f(df1['col'])]
col1  foobar

DataFrame.query
通过python引擎支持字符串方法。这没有提供明显的性能优势,但对于了解您是否需要动态生成查询很有用。

df1.query('col.str.contains("foo")', engine='python')
col0     foo1  foobar

关于queryeval系列方法的更多信息可以在在Pandas中动态评估公式中的表达式中找到。


推荐使用优先级

  1. (第一)str.contains,因为它的简单性和容易处理NaN和混合数据
  2. 列表推导,因为它的性能(特别是如果你的数据是纯字符串)
  3. np.vectorize
  4. (最后)df.query

在此之前有答案可以完成所要求的功能,无论如何,我想展示最普遍的方式:

df.filter(regex=".*STRING_YOU_LOOK_FOR.*")

这种方式让你得到你寻找的列,无论写的方式是什么。

(显然,您必须为每个案例编写正确的regex表达式)

使用包含对于我的带有特殊字符的字符串不起作用。查找工作虽然。

df[df['A'].str.find("hello") != -1]

也许您想在Pandas数据框的所有列中搜索一些文本,而不仅仅是在它们的子集中。在这种情况下,以下代码将有所帮助。

df[df.apply(lambda row: row.astype(str).str.contains('String To Find').any(), axis=1)]

警告。此方法相对较慢,但很方便。

如果您需要在熊猫数据框列中对字符串进行不区分大小写搜索:

df[df['A'].str.contains("hello", case=False)]

一个更通用的例子-如果在字符串中查找单词的部分或特定单词:

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

句子或单词的特定部分:

searchfor = '.*cat.*hat.*|.*the.*dog.*'

创建显示受影响行的列(可以根据需要过滤掉)

df["TrueFalse"]=df['col1'].str.contains(searchfor, regex=True)
col1             col2           TrueFalse0   cat andhat       1000.0         True1   hat              2000000.0      False2   the small dog    1000.0         True3   fog              330000.0       False4   pet 3            30000.0        False

假设我们在dataframedf中有一个名为“ENTITY”的列。我们可以过滤我们的df,以获得整个dataframedf,其中“实体”列的行不包含“DM”通过使用如下掩码:

mask = df['ENTITY'].str.contains('DM')
df = df.loc[~(mask)].copy(deep=True)

您可以尝试将它们视为字符串:

df[df['A'].astype(str).str.contains("Hello|Britain")]

我的2c值:

我做了以下事情:

sale_method = pd.DataFrame(model_data['Sale Method'].str.upper())sale_method['sale_classification'] = \np.where(sale_method['Sale Method'].isin(['PRIVATE']),'private',np.where(sale_method['Sale Method'].str.contains('AUCTION'),'auction','other'))

有点类似于@cs95的答案,但在这里您不需要指定引擎:

df.query('A.str.contains("hello").values')
df[df['A'].str.contains("hello", case=False)]