如何加快数据框中单词列表的单词移除速度?
我正在尝试从一个中等大小的 pandas 数据框中去掉不在字典里的单词,这个数据框大约有 18,000 行,但我的方法非常慢。基本上,我尝试了列表推导式,并把它应用到整个数据框上。这样做是可以的,但速度太慢了,我还没有成功地把这个过程向量化。请问我该怎么做呢?
而且,我的方法似乎会同时影响多个数据框,这不是我想要的结果。我该如何解决这个问题呢?
下面的代码展示了我采取的方法,只是数据量少得多:
import pandas as pd
df = pd.DataFrame({'Text': [['this', 'is', 'an', 'apple'],
['this', 'is', 'a', 'carrot'],
['this', 'is', 'a', 'steak']],
'Class': ['fruit', 'vegetable', 'meat']})
valid_words = ['apple', 'carrot', 'steak']
def dictwords(text):
valid_text = [word for word in text if word in valid_words]
return valid_text
clean = df
clean['Text'] = clean['Text'].apply(dictwords)
这个方法可以用,但对于我的实际数据来说太慢了。我的真实数据集中大约有 60,000 个独特的单词,包括有效和无效的,我只想保留大约 30,000 个。文本大约有 18,000 行。正如大家所预料的,使用 .apply() 进行这个过程会花费非常长的时间。
我尝试过使用 njit/jit 来进行并行处理,但效果不太好。对于这些数据,我可以尝试哪些向量化或并行处理的技术?有没有比列表推导式更好的方法呢?
另外,我发现当我把 dictwords() 应用到干净的数据集时,它似乎也同时应用到了 df 上。我不太明白为什么会这样,或者如何防止这种情况,所以如果能解释一下就太好了。在我测试过的所有 Jupyter Notebook 平台上似乎都有这个问题。
2 个回答
示例
生成60,000个独特的单词,30,000个目标单词,以及18,000行的数据框样本
import pandas as pd
import numpy as np
# 60k unique word ('0' ~ '59999')
words = list(map(str, range(0, 60000)))
# target 30k word ('0' ~ '29999')
valid_words = words[:30000]
# random dataframe 18k rows
np.random.seed(0)
val = np.random.choice(words, (18000, 4)).tolist()
df = pd.DataFrame({'Text': val})
数据框
Text
0 [2732, 43567, 42613, 52416] <-- not numeric, string(think like word)
1 [45891, 21243, 30403, 32103]
2 [41993, 57043, 20757, 55026]
... ...
17997 [6688, 22472, 36124, 56143]
17998 [55253, 29436, 4113, 22639]
17999 [1128, 12103, 39056, 28174]
18000 rows × 1 columns
代码
虽然这不是一个完全的向量化操作,但我们可以通过向量化的方式来创建一个数据框,并把不在valid_words
里的值替换成NaN(缺失值)。这样做的速度会比你原来的方法快,如果你最后把结果聚合成一个列表的话。
out = (pd.DataFrame(df['Text'].tolist())[lambda x: x.isin(valid_words)]
.apply(lambda x: list(x.dropna()),axis=1)
)
输出
0 [2732]
1 [21243]
2 [20757]
...
17997 [6688, 22472]
17998 [29436, 4113, 22639]
17999 [1128, 12103, 28174]
Length: 18000, dtype: object
执行时间如下:
970 ms ± 50.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
因为“Text”这一列里包含了列表,所以你可以用pandas的explode功能,把这些列表拆开,然后用isin函数来筛选出只包含你列表中单词的行。
如果每一行只包含你列表中的一个单词,你只需要前两行代码就可以了。但如果你想把单词放回列表里,或者某些行包含了你列表中的多个单词,那你就需要用到第三行代码。
clean = df.explode('Text')
clean = clean[clean['Text'].isin(valid_words)]
clean = clean.groupby(clean.index).agg({'Text': list, 'Class':'first'})
最终结果
Text Class
[apple, carrot] fruit
[carrot] vegetable
[steak] meat
第一行中的胡萝卜是我在测试数据中添加的,用来测试如果某些行中有多个单词的情况。