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

我正在寻找一种有效的方法,从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)

任何建议都将非常感激!

571313 次浏览
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))

这里有一个错误:目前不能将参数传递给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

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

最后一个字符:

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

前两个字符:

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

我会使用pandas替换函数,非常简单和强大,就像你可以使用regex一样。下面我将使用regex \D删除任何非数字字符,但显然你可以使用regex获得相当有创意的效果。

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

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

在做这样的事情(即修改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

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

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

在最初的问题发布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将返回一个包含从第一个捕获组捕获的项目的Series。


.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字符串函数。原因是字符串函数本质上很难向量化(真正意义上的词),所以大多数字符串和正则表达式函数只是循环的包装器,开销更大。

我的文章熊猫的for循环真的很糟糕吗?我什么时候该在乎?更详细。

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生成的图形。下面列出了相关函数。

其中一些比较是不公平的,因为它们利用了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']])

尝试使用正则表达式:

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

假设你的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

使用“str.replace"当数据维数较大时,比lambda和map更快:

your_data["result"]=your_data["result"].str.replace("+","")
your_data["result"]=your_data["result"].str.replace("-","")