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

问题描述 投票:89回答:7

我正在寻找一种有效的方法来从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
7个回答
124
投票
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))

40
投票

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

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

29
投票

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

最后一个字符

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

前两个字符:

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

16
投票

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

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

编辑:2012-12-07这现在在开发分支上工作:

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

14
投票

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

在原始问题发布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 and .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字符串函数。原因是因为字符串函数本身很难进行向量化(在单词的真正意义上),因此大多数字符串和正则表达式函数只是包含更多开销的循环的包装器。

我的写作For loops with pandas - 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等的相同规则。


绩效比较

enter image description here

使用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']])

10
投票

一种非常简单的方法是使用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
投票

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

执行此类操作的各种方法之间的性能可能存在很大差异(即,修改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
© www.soinside.com 2019 - 2024. All rights reserved.