你的函数是slow并且是不完整的。首先,针对问题——
- 您没有小写您的数据。
- 您没有正确删除数字和标点符号。
- 您没有返回字符串(您应该使用
str.join
并返回)
- 此外,文本处理的列表理解是引入可读性问题的主要方式,更不用说可能的冗余(您可以多次调用一个函数,对于每个
if
它出现的情况。
接下来,您的函数存在一些明显的低效率问题,尤其是停用词删除代码。
Your stopwords
结构是一个list, and in
对列表的检查是slow。要做的第一件事是将其转换为set
,使得not in
检查恒定时间。
你正在使用nltk.word_tokenize
这太慢了。
最后,你不应该总是依赖apply
,即使您使用 NLTK,但很少有可用的矢量化解决方案。几乎总是有其他方法可以做同样的事情。通常情况下,即使是 Python 循环也会更快。但这并不是一成不变的。
首先,创建您的增强型stopwords
as a set -
user_defined_stop_words = ['st','rd','hong','kong']
i = nltk.corpus.stopwords.words('english')
j = list(string.punctuation) + user_defined_stop_words
stopwords = set(i).union(j)
下一个修复是摆脱列表理解并将其转换为多行函数。这使得事情变得更容易处理。函数的每一行都应该致力于解决特定的任务(例如,删除数字/标点符号,或删除停用词,或小写) -
def preprocess(x):
x = re.sub('[^a-z\s]', '', x.lower()) # get rid of noise
x = [w for w in x.split() if w not in set(stopwords)] # remove stopwords
return ' '.join(x) # join the list
举个例子。那么这将是apply
ied到你的专栏 -
df['Clean_addr'] = df['Adj_Addr'].apply(preprocess)
作为替代方案,这是一种不依赖于apply
。这对于小句子应该很有效。
将您的数据加载到一个系列中 -
v = miss_data['Adj_Addr']
v
0 23FLOOR 9 DES VOEUX RD WEST HONG KONG
1 PAG CONSULTING FLAT 15 AIA CENTRAL 1 CONNAUGHT...
2 C/O CITY LOST STUDIOS AND FLAT 4F 13-15 HILLIE...
Name: Adj_Addr, dtype: object
现在是繁重的工作。
- 小写与
str.lower
- 使用消除噪音
str.replace
- 使用以下命令将单词拆分为单独的单元格
str.split
- 使用应用删除停用词
pd.DataFrame.isin
+ pd.DataFrame.where
- 最后,使用加入数据框
agg
.
v = v.str.lower().str.replace('[^a-z\s]', '').str.split(expand=True)
v.where(~v.isin(stopwords) & v.notnull(), '')\
.agg(' '.join, axis=1)\
.str.replace('\s+', ' ')\
.str.strip()
0 floor des voeux west
1 pag consulting flat aia central connaught central
2 co city lost studios flat f hillier sheung
dtype: object
要在多列上使用此代码,请将此代码放在函数中preprocess2
并打电话apply
-
def preprocess2(v):
v = v.str.lower().str.replace('[^a-z\s]', '').str.split(expand=True)
return v.where(~v.isin(stopwords) & v.notnull(), '')\
.agg(' '.join, axis=1)\
.str.replace('\s+', ' ')\
.str.strip()
c = ['Col1', 'Col2', ...] # columns to operate
df[c] = df[c].apply(preprocess2, axis=0)
你仍然需要一个apply
调用,但对于少量列,它的扩展性应该不会太差。如果你不喜欢apply
,那么这里有一个适合你的疯狂变体 -
for _c in c:
df[_c] = preprocess2(df[_c])
让我们看看我们的非循环版本和原始版本之间的区别 -
s = pd.concat([s] * 100000, ignore_index=True)
s.size
300000
首先,健全性检查 -
preprocess2(s).eq(s.apply(preprocess)).all()
True
现在时间到了。
%timeit preprocess2(s)
1 loop, best of 3: 13.8 s per loop
%timeit s.apply(preprocess)
1 loop, best of 3: 9.72 s per loop
这令人惊讶,因为apply
很少比非循环解决方案更快。但这在这种情况下是有意义的,因为我们已经优化了preprocess
相当多,并且 pandas 中的字符串操作很少被矢量化(它们通常是矢量化的,但性能增益没有您期望的那么多)。
让我们看看是否可以做得更好,绕过apply
, using np.vectorize
preprocess3 = np.vectorize(preprocess)
%timeit preprocess3(s)
1 loop, best of 3: 9.65 s per loop
哪个等同于apply
但由于“隐藏”循环周围的开销减少了,所以速度恰好快了一点。