#1 分隔值
如果您的作者可以清楚地进行分隔,例如在每个系列元素中使用逗号分隔,那么您可以使用collections.Counter
和itertools.chain
:
from collections import Counter
from itertools import chain
res = Counter(chain.from_iterable(df['Authors'].str.split(',').map(set)))
#2 任意字符串
当然,并非总是能获得这样的结构化数据。如果您的系列元素是带有任意数据的字符串,而您预定义的作者列表很小,则可以使用 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}
这是因为
pd.Series.str.contains
返回一个布尔值系列,您可以对其进行求和,因为在Python / Pandas的大多数数字计算中,
True
被视为等同于
1
。我们关闭
regex
以提高性能。
性能
Pandas基于字符串的方法因其速度缓慢而闻名。您可以改用使用生成器表达式和
in
运算符的
sum
来额外加速:
df = pd.concat([df]*100000)
%timeit {i: df['Authors'].str.contains(i, regex=False).sum() for i in L}
%timeit {i: sum(i in x for x in df['Authors'].values) for i in L}
%timeit {i: df['Authors'].map(lambda x: i in x).sum() for i in L}
请注意,对于方案#1,Counter
方法实际上更加昂贵,因为它们需要将分割作为预备步骤:
chainer = chain.from_iterable
%timeit Counter(chainer([set(i.split(',')) for i in df['Authors'].values]))
%timeit Counter(chainer(df['Authors'].str.split(',').map(set)))
进一步改进
- 方案#2并不完美,因为它们不能区分(例如)
John Williams
和John Williamson
。如果这种区分对您很重要,您可能希望使用专业软件包。
- 对于#1和#2,您可能希望考虑使用Aho-Corasick算法。有一个示例实现,但是可能需要更多的工作来计算每行中找到的元素数量。
设置
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']})