这是一个自我回答的帖子。下面我概述了 NLP 领域中的一个常见问题,并提出了一些解决它的高效方法。
通常需要删除 标点符号 在文本清理和预处理期间。标点符号定义为 string.punctuation
中的任何字符:
>>> import string
string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
这是一个很常见的问题,并且在令人作呕之前就已经被问到了。最地道的解决方案使用pandas
str.replace
.但是,对于涉及大量文本的情况,可能需要考虑更高效的解决方案。什么是
str.replace
的一些好的、高性能的替代品?在处理数十万条记录时?
最佳答案
设置
出于演示的目的,让我们考虑这个 DataFrame。
df = pd.DataFrame({'text':['a..b?!??', '%hgh&12','abc123!!!', '$$$1234']})
df
text
0 a..b?!??
1 %hgh&12
2 abc123!!!
3 $$$1234
下面,我按照性能的升序一一列出了替代方案
str.replace
包含此选项是为了建立默认方法作为比较其他更高效解决方案的基准。
这使用内置的 Pandas
str.replace
执行基于正则表达式的替换的函数。df['text'] = df['text'].str.replace(r'[^\w\s]+', '')
df
text
0 ab
1 hgh12
2 abc123
3 1234
这很容易编码,并且可读性很强,但速度很慢。
regex.sub
这涉及使用
sub
来自 re
的函数图书馆。为性能预编译正则表达式模式,并调用 regex.sub
在列表理解中。转换 df['text']
如果您可以节省一些内存,则预先列出一个列表,您会从中获得不错的性能提升。import re
p = re.compile(r'[^\w\s]+')
df['text'] = [p.sub('', x) for x in df['text'].tolist()]
df
text
0 ab
1 hgh12
2 abc123
3 1234
注:如果您的数据具有 NaN 值,则此方法(以及下面的下一个方法)将无法按原样工作。请参阅“ 其他注意事项”部分。
str.translate
python 的
str.translate
函数是用C实现的,因此速度非常快。这是如何工作的:
str.translate
在大字符串上,删除标点符号(排除步骤 1 中的分隔符)。 在此示例中,我们考虑管道分隔符
|
.如果您的数据包含管道,则您必须选择另一个分隔符。import string
punct = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{}~' # `|` is not present here
transtab = str.maketrans(dict.fromkeys(punct, ''))
df['text'] = '|'.join(df['text'].tolist()).translate(transtab).split('|')
df
text
0 ab
1 hgh12
2 abc123
3 1234
性能
str.translate
到目前为止,表现最好。请注意,下图包含另一个变体 Series.str.translate
来自 MaxU's answer .(有趣的是,我第二次重新运行,结果与之前略有不同。在第二次运行期间,似乎
re.sub
在非常少量的数据上胜过 str.translate
。)使用
translate
存在固有风险(特别是,自动化决定使用哪个分隔符的过程的问题很重要),但权衡取舍是值得冒险的。其他注意事项
使用列表理解方法处理 NaN; 请注意,此方法(以及下一个)仅在您的数据没有 NaN 时才有效。处理 NaN 时,您必须确定非空值的索引并仅替换它们。尝试这样的事情:
df = pd.DataFrame({'text': [
'a..b?!??', np.nan, '%hgh&12','abc123!!!', '$$$1234', np.nan]})
idx = np.flatnonzero(df['text'].notna())
col_idx = df.columns.get_loc('text')
df.iloc[idx,col_idx] = [
p.sub('', x) for x in df.iloc[idx,col_idx].tolist()]
df
text
0 ab
1 NaN
2 hgh12
3 abc123
4 1234
5 NaN
处理数据帧; 如果您正在处理 DataFrames,其中每一列都需要替换,则过程很简单:
v = pd.Series(df.values.ravel())
df[:] = translate(v).values.reshape(df.shape)
或者,
v = df.stack()
v[:] = translate(v)
df = v.unstack()
请注意
translate
函数在下面的基准代码中定义。每个解决方案都有权衡,因此决定哪种解决方案最适合您的需求将取决于您愿意牺牲什么。两个非常常见的考虑因素是性能(我们已经看到)和内存使用。
str.translate
是一种占用大量内存的解决方案,因此请谨慎使用。另一个考虑因素是正则表达式的复杂性。有时,您可能想要删除不是字母数字或空格的任何内容。其他时候,您需要保留某些字符,例如连字符、冒号和句子终止符
[.!?]
.明确指定这些会增加正则表达式的复杂性,这反过来可能会影响这些解决方案的性能。确保您测试这些解决方案在决定使用什么之前对您的数据进行处理。
最后,此解决方案将删除 unicode 字符。您可能想要调整您的正则表达式(如果使用基于正则表达式的解决方案),或者只是使用
str.translate
除此以外。为了获得更高的性能(对于更大的 N),请查看 Paul Panzer 的这个答案.
附录
函数
def pd_replace(df):
return df.assign(text=df['text'].str.replace(r'[^\w\s]+', ''))
def re_sub(df):
p = re.compile(r'[^\w\s]+')
return df.assign(text=[p.sub('', x) for x in df['text'].tolist()])
def translate(df):
punct = string.punctuation.replace('|', '')
transtab = str.maketrans(dict.fromkeys(punct, ''))
return df.assign(
text='|'.join(df['text'].tolist()).translate(transtab).split('|')
)
# MaxU's version (https://stackoverflow.com/a/50444659/4909087)
def pd_translate(df):
punct = string.punctuation.replace('|', '')
transtab = str.maketrans(dict.fromkeys(punct, ''))
return df.assign(text=df['text'].str.translate(transtab))
性能基准代码
from timeit import timeit
import pandas as pd
import matplotlib.pyplot as plt
res = pd.DataFrame(
index=['pd_replace', 're_sub', 'translate', 'pd_translate'],
columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000],
dtype=float
)
for f in res.index:
for c in res.columns:
l = ['a..b?!??', '%hgh&12','abc123!!!', '$$$1234'] * c
df = pd.DataFrame({'text' : l})
stmt = '{}(df)'.format(f)
setp = 'from __main__ import df, {}'.format(f)
res.at[f, c] = timeit(stmt, setp, number=30)
ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N");
ax.set_ylabel("time (relative)");
plt.show()
关于python - 使用 Pandas 快速去除标点符号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50444346/