我有一个Pandas数据框,其中包含75,000行文本(每行约350个字符)。我需要搜索该数据框中45k个子字符串列表的出现。
预期输出是authors_data
dict
,其中包含作者列表和出现次数。以下代码假定我有一个dataframe['text']
列和一个名为authors_list
的子字符串列表。
authors_data = {}
for author in authors_list:
count = 0
for i, row in df.iterrows():
if author in row.text:
count += 1
authors_data[author] = count
print(author, authors_data[author])
我进行了一些初步测试,有10位作者花了我大约50秒的时间。完整的表格将花费我几天时间。因此,我正在寻找更省时的方式来运行代码。
df.iterrows()
够快吗?我应该研究任何特定的库吗?
让我知道!
答案 0 :(得分:2)
如果您的作者被明确划定,例如在每个系列元素中以逗号分隔,可以将collections.Counter
与itertools.chain
一起使用:
from collections import Counter
from itertools import chain
res = Counter(chain.from_iterable(df['Authors'].str.split(',').map(set)))
# Counter({'Frank Herbert': 1, 'George Orwell': 2, 'John Steinbeck': 1,
# 'John Williams': 2, 'Philip K Dick': 1, 'Philip Roth': 1,
# 'Ursula K Le Guin': 1})
当然,这种结构化数据并不总是可用。如果系列元素是带有任意数据的字符串,并且预定义作者列表很小,则可以使用pd.Series.str.contains
。
L = ['George Orwell', 'John Steinbeck', 'Frank Herbert', 'John Williams']
res = {i: df['Authors'].str.contains(i, regex=False).sum() for i in L}
# {'Frank Herbert': 1, 'George Orwell': 2, 'John Steinbeck': 1, 'John Williams': 2}
之所以可行,是因为pd.Series.str.contains
返回一系列布尔值,然后可以对它们进行求和,因为在大多数Python / Pandas数字计算中,True
被认为等效于1
。我们关闭regex
以提高性能。
众所周知,基于字符串的Pandas速度很慢。您可以改为将sum
与生成器表达式和in
运算符结合使用,以提高速度:
df = pd.concat([df]*100000)
%timeit {i: df['Authors'].str.contains(i, regex=False).sum() for i in L} # 420 ms
%timeit {i: sum(i in x for x in df['Authors'].values) for i in L} # 235 ms
%timeit {i: df['Authors'].map(lambda x: i in x).sum() for i in L} # 424 ms
请注意,对于场景1,Counter
方法实际上更昂贵,因为它们需要作为第一步进行拆分:
chainer = chain.from_iterable
%timeit Counter(chainer([set(i.split(',')) for i in df['Authors'].values])) # 650 ms
%timeit Counter(chainer(df['Authors'].str.split(',').map(set))) # 828 ms
John Williams
和John Williamson
。如果这种差异对您很重要,则您可能希望使用专业包装。设置
df = pd.DataFrame({'Authors': ['Ursula K Le Guin,Philip K Dick,Frank Herbert,Ursula K Le Guin',
'John Williams,Philip Roth,John Williams,George Orwell',
'George Orwell,John Steinbeck,George Orwell,John Williams']})
答案 1 :(得分:2)
我尝试了这个,它正在做您想要的。您可以测试一下,看看是否更快。
for author in authors_list:
authors_data[author] = df['AUTHORCOL'].map(lambda x: author in x).sum()
答案 2 :(得分:1)
这不是一个完整的答案,但是您可以做一些事情来使事情变得更快:
-使用正则表达式:实际上是创建一个模式,然后编译。 Find out how many times a regex matches in a string in Python根据您的情况,每个作者只能编译一次。
-您有两个循环。假设作者数量合理,将最小的循环放入。您会惊讶于有时这有多么重要。这意味着,在移至下一行之前,搜索所有作者。 350个字符可以容纳到CPU的缓存中,如果运气好的话,可以节省很多时间。
将事情发挥到极限,但可能并不那么容易:编译模式是一种自动机,它仅查看输入的每个字符一次并识别输出(这就是您“编译”模式的原因 https://en.wikipedia.org/wiki/Deterministic_finite_automaton)。您可以创建所有自动机,然后从输入中获取每个字符并将其提供给所有自动机。然后,您将只“一次”处理每个输入字符(乘以作者的非恒定大小)
答案 3 :(得分:1)
单线可能会有所帮助。
authors_data = {author: df.text.map(lambda x: author in x).sum() for author in authors_list}