【问题标题】:Filter pandas DataFrame by substring criteria按子字符串标准过滤 pandas DataFrame
【发布时间】:2012-07-06 05:42:15
【问题描述】:

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

类似这样的成语:

re.search(pattern, cell_in_question) 

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

【问题讨论】:

    标签: python string pandas dataframe


    【解决方案1】:

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

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

    【讨论】:

      【解决方案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]
      

      【讨论】:

      • df[df['A'].astype(str).str.contains("Hello|Britain")] 也很好用
      • 另一种解决方案是:``` df[df["A"].str.contains("Hello|Britain") == True] ``
      【解决方案3】:

      我的 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'
                   )
          )
      

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 非常感谢,您的回答对我帮助很大,因为我正在努力通过数据为 bool 类型的列过滤数据框。您的解决方案帮助我完成了我需要的过滤器。为你 +1。
        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

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

          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
          

          【讨论】:

            【解决方案7】:

            如何从 pandas DataFrame 中选择部分字符串?

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

            • 在字符串列中搜索子字符串(最简单的情况)
            • 搜索多个子字符串(类似于isin
            • 匹配文本中的整个单词(例如,“blue”应该匹配“the sky is blue”而不是“bluejay”)
            • 匹配多个完整的单词
            • 了解“ValueError: cannot index with vector contains NA / NaN values”背后的原因

            ...并且想更多地了解哪些方法应该优于其他方法。

            (P.S.:我看过很多关于类似主题的问题,我认为最好把它留在这里。)

            友情提示,此帖很长


            基本子串搜索

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

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

            寻址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
            

            如何一次将其应用于多个列?
            答案就在问题中。使用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
            

            有时,明智的做法是避开您的术语,以防它们包含可解释为regex metacharacters 的字符。如果您的条款包含以下任何字符...

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

            然后,您需要使用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\\^'
            

            匹配整个单词

            默认情况下,子字符串搜索搜索指定的子字符串/模式,无论它是否是全字。为了只匹配完整的单词,我们需要在这里使用正则表达式——特别是,我们的模式需要指定单词边界 (\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)]
            

            在列表组合中使用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.findnp.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
            这是一个循环的包装器,但开销比大多数 pandas str 方法要少。

            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. (First)str.contains,因为它简单且易于处理 NaN 和混合数据
            2. 列表推导,因为它的性能(特别是如果您的数据是纯字符串)
            3. np.vectorize
            4. (最后)df.query

            【讨论】:

            • 在两列或多列中搜索字符串时,您能否编辑正确的方法?基本上:any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2'])) 和我尝试过的所有变体都窒息(它抱怨any(),这是正确的......但是文档很高兴地不清楚如何进行这样的查询。
            • @DenisdeBernardy df[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)
            • @00schneider r 在这种情况下用于指示原始字符串文字。这些使编写正则表达式字符串变得更加容易。 stackoverflow.com/q/2081640
            • @arno_v 很高兴听到这个消息,看起来 pandas 的性能正在提高!
            • 非常有帮助!!特别是“重新导入”功能改变了游戏规则。起首!
            【解决方案8】:

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

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

            【讨论】:

              【解决方案9】:

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

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

              警告。这种方法虽然很方便,但速度相对较慢。

              【讨论】:

                【解决方案10】:

                对于带有特殊字符的字符串,使用 contains 效果不佳。虽然找到了。

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

                【讨论】:

                  【解决方案11】:

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

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

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

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

                  【讨论】:

                  • 这会过滤列 headers。不一般,不正确。
                  • @MicheldeRuiter 仍然不正确,而是过滤索引标签!
                  【解决方案12】:

                  如果有人想知道如何解决相关问题:"Select column by partial string"

                  用途:

                  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)  
                  

                  【讨论】:

                  • 这可以提炼成:df.loc[:, df.columns.str.contains('a')]
                  • 可以进一步提炼成df.filter(like='a')
                  • 这应该是自己的问题+答案,已经有50人搜索了...
                  • @PV8 问题已存在:stackoverflow.com/questions/31551412/…。但是当我在谷歌上搜索“pandas Select column by partial string”时,这个线程首先出现
                  【解决方案13】:

                  根据 github 问题 #620,看来您很快就能做到以下几点:

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

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

                  【讨论】:

                  • 如果我想在“OR”条件下找到“Hello”和“Britain”。
                  • 由于 str.* 方法将输入模式视为正则表达式,您可以使用df[df['A'].str.contains("Hello|Britain")]
                  • 是否可以将.str.contains 转换为使用.query() api
                  • df[df['value'].astype(str).str.contains('1234.+')] 用于过滤掉非字符串类型的列。
                  【解决方案14】:

                  假设你有以下DataFrame

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

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

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

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

                  【讨论】:

                  • 我如何修改上面说 x['a'] 只存在于 x['b'] 的开头?
                  • apply 在性能和内存方面是个坏主意。见this answer
                  【解决方案15】:

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

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

                  【讨论】:

                  • 你可以 df[df.index.to_series().str.contains('LLChit')]
                  • 为了更简洁,不需要to_seriesdf[df.index.str.contains('Hello|Britain')]
                  【解决方案16】:

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

                  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
                  

                  【讨论】:

                  • 如果你在循环之前编译正则表达式应该快 2 到 3 倍:regex = re.compile(regex) 然后 if regex.search(record)
                  • @MarkokraM docs.python.org/3.6/library/re.html#re.compile 表示为您缓存了最新的正则表达式,因此您无需自己编译。
                  • 不要使用 iteritems 来遍历 DataFrame。它在可扩展性和性能方面排名最后
                  • 迭代数据帧违背了 pandas 的全部目的。改用加勒特的解决方案
                  猜你喜欢
                  • 2020-06-07
                  • 1970-01-01
                  • 2020-09-24
                  • 2020-12-23
                  • 1970-01-01
                  • 2021-09-05
                  相关资源
                  最近更新 更多