关于python:从pandas DataFrame中选择部分字符串

Select by partial string from a pandas DataFrame

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

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

1
re.search(pattern, cell_in_question)

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

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


基于Github发行620,您很快就能做到以下几点:

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

更新:矢量化字符串方法(即series.str)在pandas 0.8.1及更高版本中提供。


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

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

并得到一个错误:

1
"cannot index with vector containing NA / NaN values"

但当添加"==true"条件时,它工作得很好,如下所示:

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


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

用途:

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

通过部分字符串匹配选择行,通过axis=0过滤:

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


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

1
2
df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]


假设您有以下DataFrame

1
2
3
4
5
>>> df = pd.DataFrame([['hello', 'hello world'], ['abcd', 'defg']], columns=['a','b'])
>>> df
       a            b
0  hello  hello world
1   abcd         defg

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

1
2
3
4
>>> df.apply(lambda x: x['a'] in x['b'], axis=1)
0     True
1    False
dtype: bool

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


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

这篇文章是为那些想要

  • 在字符串列中搜索子字符串(最简单的情况)
  • 搜索多个子串(类似于isin)
  • 匹配文本中的整个单词(例如,"蓝色"应与"天空是蓝色"匹配,而不是"蓝色杰伊")。
  • 匹配多个整词

…并希望了解更多关于哪些方法比其他方法更可取的信息。

(附言:我看过很多关于类似主题的问题,我想把这个放在这里会更好。)

基本子字符串搜索

1
2
3
4
5
6
7
8
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1

      col
0     foo
1  foobar
2     bar
3     baz

要选择包含"foo"的所有行,请使用str.contains

1
2
3
4
5
df1[df1['col'].str.contains('foo')]

      col
0     foo
1  foobar

请注意,这是纯子字符串搜索,因此可以安全地禁用基于regex的匹配。

1
2
3
4
5
df1[df1['col'].str.contains('foo', regex=False)]

      col
0     foo
1  foobar

从性能上讲,这确实会有所不同。

1
2
3
4
5
6
7
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)

如果不需要,请避免使用基于regex的搜索。

Note
Partial substring searches that are anchored at the start or end of strings can be done using str.startswith or str.endswith
respectively.

Additionally, for regex based searches anchored at the start, use str.match.

基于regex的搜索大多数str方法支持正则表达式。例如,要在df1中查找包含"foo"和其他内容的行,我们可以使用

1
2
3
4
df1[df1['col'].str.contains(r'foo(?!$)')]

      col
1  foobar

多个子字符串搜索

这最容易通过使用regex或pipe进行regex搜索来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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

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

1
2
3
4
5
6
7
terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]

          col
0     foo abc
1  foobar xyz
3      baz 45

有时,如果术语中包含可以解释为regex元字符的字符,则最好不要使用这些字符。如果您的术语包含以下任何字符…

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

然后,您需要使用re.escape来避开它们:

1
2
3
4
5
6
7
import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape具有逃逸特殊字符的效果,因此可以从字面上对它们进行处理。

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

匹配整个单词

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

例如,

1
2
3
4
5
6
df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})
df3

                     col
0        the sky is blue
1  bluejay by the window

现在考虑一下,

1
2
3
4
5
df3[df3['col'].str.contains('blue')]

                     col
0        the sky is blue
1  bluejay by the window

V/S

1
2
3
4
df3[df3['col'].str.contains(r'\bblue\b')]

               col
0  the sky is blue

多个整词搜索

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

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

       col
0  foo abc
3   baz 45

如果p看起来像这样,

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

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

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

而不是,

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

在列表组件中使用in运算符,

1
2
3
4
5
df1[['foo' in x for x in df1['col']]]

       col
0  foo abc
1   foobar

而不是,

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

在列表组件中使用re.compile(缓存regex)+Pattern.search

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

      col
1  foobar

如果"col"有nan,则不是

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

使用,

1
2
3
4
5
6
7
8
9
10
11
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

部分字符串匹配的更多选项:np.char.findnp.vectorizeDataFrame.query

除了str.contains和list理解之外,您还可以使用以下选项。

np.char.find仅支持子字符串搜索(read:no regex)。

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

          col
0     foo abc
1  foobar xyz

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

1
2
3
4
5
6
7
8
9
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解决方案:

1
2
3
4
5
6
7
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引擎支持字符串方法。这并没有提供明显的性能优势,但对于了解是否需要动态生成查询仍然很有用。

1
2
3
4
5
df1.query('col.str.contains("foo")', engine='python')

      col
0     foo
1  foobar

有关queryeval方法家族的更多信息,可以在使用pd.eval()的大熊猫动态表达评估中找到。

建议使用优先级

  • (一)str.contains的简单性
  • 列出理解,为其性能
  • np.vectorize
  • (上)df.query

  • 以下是我最后对部分字符串匹配所做的操作。如果有人有更有效的方法,请告诉我。

    1
    2
    3
    4
    5
    6
    7
    8
    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