从数据帧中按部分字符串选择

2024-04-19 18:17:39 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个DataFrame,有4列,其中2列包含字符串值。我想知道是否有一种方法可以根据与特定列的部分字符串匹配来选择行

换句话说,一个函数或lambda函数

re.search(pattern, cell_in_question) 

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

有人能给我指出正确的方向吗


Tags: 方法lambda函数字符串inrehellodataframe
3条回答

基于github问题#620,您很快就可以执行以下操作:

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

更新:vectorized string methods (i.e., Series.str)在pandas 0.8.1及更高版本中提供

How do I select by partial string from a pandas DataFrame?

这篇文章是写给想读的读者的

  • 在字符串列中搜索子字符串(最简单的情况)
  • 搜索多个子字符串(类似于^{}
  • 匹配文本中的一个完整单词(例如,“blue”应匹配“天空是蓝色的”,但不匹配“bluejay”)
  • 匹配多个整词
  • 了解“ValueError:无法使用包含NA/NaN值的向量进行索引”背后的原因

…并希望了解更多关于哪些方法应优于其他方法的信息

(旁白:我已经看到很多关于类似主题的问题,我想把这个留在这里会很好。)

Friendly disclaimer, this is post is long.


基本子串搜索

# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1

      col
0     foo
1  foobar
2     bar
3     baz

^{}可用于执行子字符串搜索或基于正则表达式的搜索。除非显式禁用,否则搜索默认为基于正则表达式

下面是一个基于正则表达式的搜索示例

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

      col
1  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.
   
      col
0     foo
1  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)

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

寻址ValueErrors
有时,对结果执行子字符串搜索和筛选将导致

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     True
1     True
2      NaN
3     True
4    False
5      NaN
dtype: object


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

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

s.str.contains('foo|bar', na=False)

0     True
1     True
2    False
3     True
4    False
5    False
dtype: bool

如何一次将其应用于多个列?
答案在问题中。使用^{}

# `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      B
0   True   True
1   True  False
2  False   True
3   True  False
4  False  False
5  False  False

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

如果您有一个包含混合列的数据框,并且只想选择对象/字符串列,请查看^{}


多个子串搜索

这最容易通过使用正则表达式或管道的正则表达式搜索来实现

# Slightly modified example.
df4 = pd.DataFrame({'col': ['foo abc', 'foobar xyz', 'bar32', 'baz 45']})
df4

          col
0     foo abc
1  foobar xyz
2       bar32
3      baz 45

df4[df4['col'].str.contains(r'foo|baz')]

          col
0     foo abc
1  foobar xyz
3      baz 45

您还可以创建术语列表,然后将其合并:

terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]

          col
0     foo abc
1  foobar xyz
3      baz 45

有时,如果术语中的字符可以解释为regex metacharacters,则明智的做法是对术语进行转义。如果您的术语包含以下任何字符

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

然后,您需要使用^{}转义它们:

import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape具有转义特殊字符的效果,因此它们被逐字处理

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

匹配整个单词

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

比如说,

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

                     col
0        the sky is blue
1  bluejay by the window
 

现在考虑,

df3[df3['col'].str.contains('blue')]

                     col
0        the sky is blue
1  bluejay by the window

v/s

df3[df3['col'].str.contains(r'\bblue\b')]

               col
0  the sky is blue

多个整词搜索

与上面类似,除了我们向连接模式添加单词边界(\b

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

       col
0  foo abc
3   baz 45

其中p看起来像这样

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

一个很好的选择:使用List Comprehensions

因为你可以And you should!它们通常比字符串方法快一点,因为字符串方法很难矢量化,并且通常有循环实现

而不是

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

在列表comp中使用in运算符

df1[['foo' in x for x in df1['col']]]

       col
0  foo abc
1   foobar

而不是

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

在列表comp中使用^{}(缓存正则表达式)+^{}

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

      col
1  foobar

如果“col”有nan,那么

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']]]

      col
1  foobar
 

部分字符串匹配的更多选项:^{}^{}^{}

除了str.contains和列表理解之外,您还可以使用以下替代方法

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

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

          col
0     foo abc
1  foobar xyz

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

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

df1[f(df1['col'], 'foo')]

       col
0  foo abc
1   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'])]

      col
1  foobar

DataFrame.query
通过python引擎支持字符串方法。这并没有提供明显的性能优势,但如果您需要动态生成查询,这仍然很有用

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

      col
0     foo
1  foobar

有关queryeval方法系列的更多信息,请参见Dynamic Expression Evaluation in pandas using pd.eval()


推荐使用优先级

  1. (第一)str.contains,因为它简单且易于处理NAN和混合数据
  2. 列出理解,以了解其性能(特别是如果您的数据纯粹是字符串)
  3. np.vectorize
  4. (最后)df.query

我尝试了上述建议的解决方案:

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

并得到一个错误:

ValueError: cannot mask with array containing NA / NaN values

可以将NA值转换为False,如下所示:

df[df["A"].str.contains("Hello|Britain", na=False)]

相关问题 更多 >