根据子字符串条件过滤 pandas DataFrame

2024-11-22 08:47:00
admin
原创
190
摘要:问题描述:我有一个带有字符串值列的 pandas DataFrame。我需要根据部分字符串匹配来选择行。类似这样的习语:re.search(pattern, cell_in_question) 返回布尔值。我熟悉的语法df[df['A'] == "hello world"],但似乎找不到...

问题描述:

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

类似这样的习语:

re.search(pattern, cell_in_question) 

返回布尔值。我熟悉的语法df[df['A'] == "hello world"],但似乎找不到使用部分字符串匹配(例如)执行相同操作的方法'hello'


解决方案 1:

矢量化字符串方法(即Series.str)允许您执行以下操作:

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

该功能在 pandas 0.8.1及更高版本中可用。

解决方案 2:

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

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

出现错误:

无法使用包含 NA/NaN 值的向量进行索引

但当添加“==True”条件时,它就可以完美运行,如下所示:

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

解决方案 3:

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

这篇文章适合那些想要

  • 在字符串列中搜索子字符串(最简单的情况),例如df1[df1['col'].str.contains(r'foo(?!$)')]

  • 搜索多个子字符串(类似于isin),例如使用df4[df4['col'].str.contains(r'foo|baz')]

  • 匹配文本中的整个单词(例如,“blue”应该匹配“the sky is blue”,而不是“bluejay”),例如df3[df3['col'].str.contains(r'blue')]

  • 匹配多个整个单词

  • 了解“ValueError:无法使用包含NA / NaN值的向量进行索引”背后的原因,并使用str.contains('pattern',na=False)

...并且想知道更多关于应该优先选择哪些方法的信息。

(PS:我看到过很多关于类似主题的问题,我觉得最好把它留在这里。)

友情免责声明,这篇文章很长


基本子字符串搜索

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

      col
0     foo
1  foobar
2     bar
3     baz

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

以下是基于正则表达式的搜索示例,

# 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)

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

寻址ValueError有时

,执行子字符串搜索并过滤结果将导致

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

如何一次性将其应用于多个列?

答案就在问题中。使用DataFrame.apply

# `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方法“应用”到多列(在我看来这是可以的,只要列不要太多)。

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


多子串搜索

最容易实现这一点是通过使用正则表达式 OR 管道进行正则表达式搜索。

# 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

有时,如果术语中包含可以被解释为正则表达式元字符的字符,则最好对其进行转义。如果您的术语包含以下任何字符...

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

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

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\\^'

匹配整个单词

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

例如,

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

速度

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

               col
0  the sky is blue

多个全词搜索

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

p = r'(?:{})'.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'

一个很好的替代方案:使用列表推导!

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

相反,

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

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

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

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
 

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

除了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

query有关方法系列的更多信息eval,请参阅在 Pandas 中根据公式动态评估表达式。


建议使用优先顺序

  1. (第一)str.contains,因为它简单且易于处理 NaN 和混合数据

  2. 列表推导,以提高其性能(特别是当您的数据纯是字符串时)

  3. np.vectorize

  4. (最后的)df.query

解决方案 4:

如果有人想知道如何执行相关问题:“通过部分字符串选择列”

使用:

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

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

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

解决方案 5:

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

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

解决方案 6:

您是否需要在 pandas 数据框列中对字符串进行不区分大小写的搜索:

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

解决方案 7:

假设您有以下内容DataFrame

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

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

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

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

解决方案 8:

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

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

解决方案 9:

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

mask = df['ENTITY'].str.contains('DM')

df = df.loc[~(mask)].copy(deep=True)

解决方案 10:

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

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

解决方案 11:

对于包含特殊字符的字符串,使用 contains 效果不佳。但使用 find 效果不错。

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

解决方案 12:

更普遍的例子——如果寻找一个单词的部分或一个字符串中的特定单词:

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           TrueFalse
0   cat andhat       1000.0         True
1   hat              2000000.0      False
2   the small dog    1000.0         True
3   fog              330000.0       False
4   pet 3            30000.0        False

解决方案 13:

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

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

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

解决方案 14:

与@cs95 的答案有些类似,但这里不需要指定引擎:

df.query('A.str.contains("hello").values')

解决方案 15:

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

解决方案 16:

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

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

这样,无论以何种方式书写,您都可以获得您要查找的列。

(显然,你必须为每种情况编写适当的正则表达式)

解决方案 17:

我的 2c 价值:

我做了以下事情:

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

解决方案 18:

queryAPI

正如其他答案中提到的,您可以query通过在表达式中调用来过滤行str.contains。它的优点是,与布尔索引不同,SettingWithCopyWarning使用它后您不会遇到麻烦。您还可以使用传递本地(或其他地方)定义的模式@。同样有用的 kwargs:

  • case=False: 执行不区分大小写的搜索

  • na=False:填写False缺失值,例如 NaN、NA、None 等。

df = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baZ', pd.NA]})
pat = r'f|z'
df.query('col.str.contains(@pat, case=False, na=False)')    # case-insensitive and return False if NaN

# or pass it as `local_dict`
df.query('col.str.contains(@pattern, case=False, na=False)', local_dict={'pattern': r'f|z'})

如上所示,您可以通过传递来处理列中的 NaN 值。 与将列转换为dtype 或执行其他布尔检查(如本页上的某些答案中所述)na=False相比,这不容易出错(并且速度更快) 。str

表现

由于 Python 字符串方法未经过优化,因此使用原始 Python 并使用显式循环执行任何任务通常更快。因此,如果您想要良好的性能,请使用列表推导而不是矢量化str.contains。从以下基准测试(在 Python 3.12.0 和 pandas 2.1.1 上测试)可以看出,str.contains虽然简洁,但比列表推导慢约 20%(即使使用三元运算符处理 NaN)。由于str.contains无论如何都是循环实现,因此无论 DataFrame 大小如何,这个差距都存在。

import re
import pandas as pd
df = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baZ', pd.NA]*100000})
pat = re.compile(r'f|z', flags=re.I)

%timeit df[df['col'].str.contains(pat, na=False)]
# 375 ms ± 15.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df[[bool(pat.search(x)) if (x==x) is True else False for x in df['col'].tolist()]]
# 318 ms ± 14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用