使用 pandas 进行基于 NLTK 的文本处理

2023-12-03

使用 nltk 时,标点符号和数字、小写字母不起作用。

My code

stopwords=nltk.corpus.stopwords.words('english')+ list(string.punctuation)
user_defined_stop_words=['st','rd','hong','kong']                    
new_stop_words=stopwords+user_defined_stop_words

def preprocess(text):
    return [word for word in word_tokenize(text) if word.lower() not in new_stop_words and not word.isdigit()]

miss_data['Clean_addr'] = miss_data['Adj_Addr'].apply(preprocess)

输入样本

23FLOOR 9 DES VOEUX RD WEST     HONG KONG
PAG CONSULTING FLAT 15 AIA CENTRAL 1 CONNAUGHT RD CENTRAL
C/O CITY LOST STUDIOS AND FLAT 4F 13-15 HILLIER ST SHEUNG HONG KONG

预期输出

 floor des voeux west
 pag consulting flat aia central connaught central
 co city lost studios flat f hillier sheung

你的函数是slow并且是不完整的。首先,针对问题——

  1. 您没有小写您的数据。
  2. 您没有正确删除数字和标点符号。
  3. 您没有返回字符串(您应该使用str.join并返回)
  4. 此外,文本处理的列表理解是引入可读性问题的主要方式,更不用说可能的冗余(您可以多次调用一个函数,对于每个if它出现的情况。

接下来,您的函数存在一些明显的低效率问题,尤其是停用词删除代码。

  1. Your stopwords结构是一个list, and in对列表的检查是slow。要做的第一件事是将其转换为set,使得not in检查恒定时间。

  2. 你正在使用nltk.word_tokenize这太慢了。

  3. 最后,你不应该总是依赖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

举个例子。那么这将是applyied到你的专栏 -

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

现在是繁重的工作。

  1. 小写与str.lower
  2. 使用消除噪音str.replace
  3. 使用以下命令将单词拆分为单独的单元格str.split
  4. 使用应用删除停用词pd.DataFrame.isin + pd.DataFrame.where
  5. 最后,使用加入数据框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但由于“隐藏”循环周围的开销减少了,所以速度恰好快了一点。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 pandas 进行基于 NLTK 的文本处理 的相关文章

随机推荐