【问题标题】:Remove unwanted parts from strings in a column从列中的字符串中删除不需要的部分
【发布时间】:2012-11-20 20:20:32
【问题描述】:

我正在寻找一种有效的方法来从 DataFrame 列中的字符串中删除不需要的部分。

数据如下:

    time    result
1    09:00   +52A
2    10:00   +62B
3    11:00   +44a
4    12:00   +30b
5    13:00   -110a

我需要将这些数据修剪为:

    time    result
1    09:00   52
2    10:00   62
3    11:00   44
4    12:00   30
5    13:00   110

我尝试了.str.lstrip('+-') 和.str.rstrip('aAbBcC'),但出现错误:

TypeError: wrapper() takes exactly 1 argument (2 given)

任何指针将不胜感激!

【问题讨论】:

    标签: python string pandas dataframe


    【解决方案1】:
    data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
    

    【讨论】:

    • 谢谢!这样可行。我还在纠结map(),不知道什么时候用,什么时候不用……
    • 我很高兴看到这种方法也适用于替换功能。
    • @eumiro 如果迭代每一列,你如何应用这个结果?
    • 我可以用这个功能来代替数字12这样的数字吗?如果我做 x.lstrip('12') 它取出所有 1 和 2s。
    【解决方案2】:

    如何从列中的字符串中删除不需要的部分?

    在最初的问题发布 6 年后,pandas 现在拥有大量“矢量化”字符串函数,可以简洁地执行这些字符串操作操作。

    这个答案将探索其中一些字符串函数,提出更快的替代方案,并在最后进行时间比较。


    .str.replace

    指定要匹配的子字符串/模式,以及替换它的子字符串。

    pd.__version__
    # '0.24.1'
    
    df    
        time result
    1  09:00   +52A
    2  10:00   +62B
    3  11:00   +44a
    4  12:00   +30b
    5  13:00  -110a
    

    df['result'] = df['result'].str.replace(r'\D', '')
    df
    
        time result
    1  09:00     52
    2  10:00     62
    3  11:00     44
    4  12:00     30
    5  13:00    110
    

    如果需要将结果转换为整数,可以使用Series.astype

    df['result'] = df['result'].str.replace(r'\D', '').astype(int)
    
    df.dtypes
    time      object
    result     int64
    dtype: object
    

    如果您不想就地修改df,请使用DataFrame.assign

    df2 = df.assign(result=df['result'].str.replace(r'\D', ''))
    df
    # Unchanged
    

    .str.extract

    对于提取要保留的子字符串很有用。

    df['result'] = df['result'].str.extract(r'(\d+)', expand=False)
    df
    
        time result
    1  09:00     52
    2  10:00     62
    3  11:00     44
    4  12:00     30
    5  13:00    110
    

    对于extract,必须至少指定一个捕获组。 expand=False 将返回一个系列,其中包含第一个捕获组中捕获的项目。


    .str.split.str.get

    假设您的所有字符串都遵循这种一致的结构,则拆分是有效的。

    # df['result'] = df['result'].str.split(r'\D').str[1]
    df['result'] = df['result'].str.split(r'\D').str.get(1)
    df
    
        time result
    1  09:00     52
    2  10:00     62
    3  11:00     44
    4  12:00     30
    5  13:00    110
    

    如果您正在寻找通用解决方案,不建议这样做。


    如果您对简洁易读的str感到满意 上面基于访问器的解决方案,你可以在这里停下来。但是,如果你是 对更快、性能更高的替代方案感兴趣,请继续阅读。


    优化:列表理解

    在某些情况下,列表推导应该优于 pandas 字符串函数。原因是字符串函数天生就很难向量化(真正意义上的),所以大多数字符串和正则表达式函数只是对循环的封装,开销更大。

    我的文章Are for-loops in pandas really bad? When should I care? 更详细。

    str.replace 选项可以使用re.sub 重写

    import re
    
    # Pre-compile your regex pattern for more performance.
    p = re.compile(r'\D')
    df['result'] = [p.sub('', x) for x in df['result']]
    df
    
        time result
    1  09:00     52
    2  10:00     62
    3  11:00     44
    4  12:00     30
    5  13:00    110
    

    str.extract 示例可以使用re.search 的列表推导重写,

    p = re.compile(r'\d+')
    df['result'] = [p.search(x)[0] for x in df['result']]
    df
    
        time result
    1  09:00     52
    2  10:00     62
    3  11:00     44
    4  12:00     30
    5  13:00    110
    

    如果有可能出现 NaN 或不匹配,您将需要重新编写以上内容以包含一些错误检查。我使用一个函数来做到这一点。

    def try_extract(pattern, string):
        try:
            m = pattern.search(string)
            return m.group(0)
        except (TypeError, ValueError, AttributeError):
            return np.nan
    
    p = re.compile(r'\d+')
    df['result'] = [try_extract(p, x) for x in df['result']]
    df
    
        time result
    1  09:00     52
    2  10:00     62
    3  11:00     44
    4  12:00     30
    5  13:00    110
    

    我们还可以使用列表推导重写@eumiro 和@MonkeyButter 的答案:

    df['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in df['result']]
    

    还有,

    df['result'] = [x[1:-1] for x in df['result']]
    

    适用于处理 NaN 等的相同规则。


    性能比较

    使用perfplot 生成的图表。 Full code listing, for your reference.下面列出了相关函数。

    其中一些比较是不公平的,因为它们利用了 OP 数据的结构,但你可以从中取用。需要注意的一点是,每个列表解析函数都比其等效的 pandas 变体更快或可比。

    函数

    def eumiro(df):
        return df.assign(
            result=df['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC')))
    
    def coder375(df):
        return df.assign(
            result=df['result'].replace(r'\D', r'', regex=True))
    
    def monkeybutter(df):
        return df.assign(result=df['result'].map(lambda x: x[1:-1]))
    
    def wes(df):
        return df.assign(result=df['result'].str.lstrip('+-').str.rstrip('aAbBcC'))
    
    def cs1(df):
        return df.assign(result=df['result'].str.replace(r'\D', ''))
    
    def cs2_ted(df):
        # `str.extract` based solution, similar to @Ted Petrou's. so timing together.
        return df.assign(result=df['result'].str.extract(r'(\d+)', expand=False))
    
    def cs1_listcomp(df):
        return df.assign(result=[p1.sub('', x) for x in df['result']])
    
    def cs2_listcomp(df):
        return df.assign(result=[p2.search(x)[0] for x in df['result']])
    
    def cs_eumiro_listcomp(df):
        return df.assign(
            result=[x.lstrip('+-').rstrip('aAbBcC') for x in df['result']])
    
    def cs_mb_listcomp(df):
        return df.assign(result=[x[1:-1] for x in df['result']])
    

    【讨论】:

    • 任何避免设置withcopywarning的解决方法:Try using .loc[row_indexer,col_indexer] = value instead
    • @PV8 不确定您的代码,但请查看:stackoverflow.com/questions/20625582/…
    • 对于像我这样的 REGEX 新手来说,\D 与 [^\d] 相同(任何不是数字的)from here。所以我们基本上将字符串中的所有非数字都替换为空。
    • 如此完整的答案应该是答案。
    【解决方案3】:

    我会使用 pandas 替换功能,它非常简单且功能强大,因为您可以使用正则表达式。下面我使用正则表达式 \D 删除任何非数字字符,但显然你可以使用正则表达式获得相当的创意。

    data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'')
    

    【讨论】:

    • 这个我试过了,还是不行。我想知道它是否仅在您要替换整个字符串而不是仅替换子字符串部分时才有效。
    • @bgenchel - 我使用这种方法替换了 pd.Series 中的部分字符串:df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix")。这会将“my_prefixaaa”之类的字符串转换为“new_prefixaaa”。
    • to_replace=r'\D'中的r做了什么?
    • @LucaGuarro 来自 python 文档:“在此示例中需要 r 前缀,使文字成为原始字符串文字,因为 Python 无法识别普通“熟”字符串文字中的转义序列,与正则表达式相反,现在会导致 DeprecationWarning 并最终成为 SyntaxError。”
    【解决方案4】:

    在您知道要从数据框列中删除的位置数的特定情况下,您可以在 lambda 函数中使用字符串索引来删除这些部分:

    最后一个字符:

    data['result'] = data['result'].map(lambda x: str(x)[:-1])
    

    前两个字符:

    data['result'] = data['result'].map(lambda x: str(x)[2:])
    

    【讨论】:

    • 我需要将地理坐标修剪为 8 个字符(包括 (.)、(-)),如果它们小于 8,我需要最后插入 '0' 以使所有坐标 8 个字符。有什么更简单的方法?
    • 我不完全理解您的问题,但您可能需要将 lambda 函数更改为类似 "{0:.8f}".format(x)
    • 非常感谢您的回复。简而言之,我有带有地理坐标的数据框——纬度和经度为两列。字符长度超过 8 个字符,我只保留从第一个开始的 8 个字符,其中还应包括 (-) 和 (.)。
    【解决方案5】:

    这里有一个错误:目前无法将参数传递给str.lstripstr.rstrip

    http://github.com/pydata/pandas/issues/2411

    编辑:2012-12-07 现在可以在 dev 分支上使用:

    In [8]: df['result'].str.lstrip('+-').str.rstrip('aAbBcC')
    Out[8]: 
    1     52
    2     62
    3     44
    4     30
    5    110
    Name: result
    

    【讨论】:

      【解决方案6】:

      一个非常简单的方法是使用extract 方法来选择所有数字。只需提供正则表达式 '\d+' 即可提取任意数量的数字。

      df['result'] = df.result.str.extract(r'(\d+)', expand=True).astype(int)
      df
      
          time  result
      1  09:00      52
      2  10:00      62
      3  11:00      44
      4  12:00      30
      5  13:00     110
      

      【讨论】:

        【解决方案7】:

        假设你的 DF 在数字之间也有那些额外的字符。最后一个条目。

          result   time
        0   +52A  09:00
        1   +62B  10:00
        2   +44a  11:00
        3   +30b  12:00
        4  -110a  13:00
        5   3+b0  14:00
        

        您可以尝试 str.replace 来删除字符,不仅从开始和结束,而且从中间删除。

        DF['result'] = DF['result'].str.replace('\+|a|b|\-|A|B', '')
        

        输出:

          result   time
        0     52  09:00
        1     62  10:00
        2     44  11:00
        3     30  12:00
        4    110  13:00
        5     30  14:00
        

        【讨论】:

          【解决方案8】:

          我经常对这些类型的任务使用列表推导,因为它们通常更快。

          执行此类操作的各种方法之间的性能可能存在很大差异(即修改 DataFrame 中系列的每个元素)。通常,列表理解可能是最快的 - 请参阅下面的代码竞赛以了解此任务:

          import pandas as pd
          #Map
          data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
          %timeit data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
          10000 loops, best of 3: 187 µs per loop
          #List comprehension
          data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
          %timeit data['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in data['result']]
          10000 loops, best of 3: 117 µs per loop
          #.str
          data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
          %timeit data['result'] = data['result'].str.lstrip('+-').str.rstrip('aAbBcC')
          1000 loops, best of 3: 336 µs per loop
          

          【讨论】:

            【解决方案9】:

            用正则表达式试试这个:

            import re
            data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
            

            【讨论】:

              猜你喜欢
              • 2022-01-25
              • 2022-12-01
              • 2011-12-24
              • 1970-01-01
              相关资源
              最近更新 更多