【问题标题】:Keywords count in df: Performance tuningdf 中的关键字计数:性能调整
【发布时间】:2021-08-24 06:07:34
【问题描述】:

我有一个带有文本列的 df。说:

d = {'text': ["merry had a little lamb and a broken limb", "Little Jonathan found a chicken"]}
df = pd.DataFrame(data=d)

我还有一个包含约 400 个关键字的列表,例如:

observations_words_list = ["bent","block","broken"]

我想看看有多少记录的文本中有多个关键字,我就是这样做的:

    df = df['text'].dropna().reset_index()
    df_len = len(df['text'])
    obs = set(observations_words_list)
    df['Observations'] = df['text'].apply(lambda x: len(set(str(x).lower().split()).intersection(obs)))
    obs_count = len(df[df['Observations'] > 0])/df_len

对于示例 df(实际上我读取了大约 0.5m 条记录的 csv),我希望新列的第一条记录为 1,第二条记录为 0,总体而言 obs_count=0.5

运行时不理想,我正在寻找一种更快的方法来处理这一步。

会喜欢你的想法。谢谢!

【问题讨论】:

  • 你能查一下this吗?
  • 解决方案是Aho-Corasick algorithm这里
  • 这在此处不适用,因为它在单词中查找单词,而在这里我相信 Gavriel 只是在字符串中查找整个单词(因此使用 str.split())

标签: python pandas list performance


【解决方案1】:

290ms for 100k * df

我了解您只想查看有多少条记录包含 ONE OR more 关键字,与绝对数量无关。在这种情况下,有一个更好的(我认为更优雅)的使用正则表达式的单行解决方案,它不会使用 apply 遍历数据:

d = {'text': ["merry had a little lamb and a broken limb", "Little Jonathan found a chicken"]*100000}
df = pd.DataFrame(data=d)

observations_words_list = ["bent","block","broken"]
obs = '|'.join(r"\b{}\b".format(x) for x in observations_words_list)
contains_obs = df['text'].str.contains(obs, flags=re.IGNORECASE, regex=True)
obs_count = sum(contains_obs)/len(df['text'])

如果您希望保留观察次数,可以使用“计数”来减少时间损失(410 毫秒):

df['Observations'] = df['text'].str.count(obs, flags=re.IGNORECASE)
obs_count = sum(df['Observations']>0)/len(df['text'])

【讨论】:

  • 是的,看来 OP 不关心性能。
  • 只是一个不同的搜索。但是,我感谢您的洞察力,因为我将来会为自己使用 Aho-Corasick 算法:) 所以无论如何都要给你一个加分。不幸的是,100k*df 的算法需要 5.5 秒,但当然它在字符串上做了很多工作......
  • 顺便说一句,单词边界需要'|'.join(r"\b{}\b".format(x) for x in observations_words_list)
  • 刚刚将 ["bent","block","broken"] 替换为用于 gd 测量的观察字词列表...您能接受答案吗?
  • 你赢了哥们。谢谢!
【解决方案2】:

您可以尝试以下单行:

print(df['text'].str.lower().str.split(expand=True).isin(set(observations_words_list)).sum(axis=1).mean())

输出:

0.5

【讨论】:

  • 测试df = pd.concat([df] * 100000, ignore_index=True)
  • 运维解决方案448 ms ± 132 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  • 你的846 ms ± 22.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  • 测试你改进的df['text'].str.lower().str.split(expand=True).isin(observations_words_list).sum(axis=1).mean()
  • %%timeit df['text'].str.lower().str.split(expand=True).isin(set(observations_words_list)).sum(axis=1).mean() 834 ms ± 7.91 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
【解决方案3】:

mean.gt(0) 可能会有所改进:

df['text'].apply(lambda x: len(set(str(x).lower().split()).intersection(obs))).gt(0).mean()

d = {'text': ["merry had a little lamb and a broken limb", "Little Jonathan found a chicken"]}
df = pd.DataFrame(data=d)
df = pd.concat([df] * 100000, ignore_index=True)


In [44]: %%timeit
    ...: df_len = len(df['text'])
    ...: obs = set(observations_words_list)
    ...: df['Observations'] = df['text'].apply(lambda x: len(set(str(x).lower().split()).intersection(obs)))
    ...: obs_count = len(df[df['Observations'] > 0])/df_len
    ...: 
    ...: 
448 ms ± 132 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [45]: %%timeit
    ...: obs = set(observations_words_list)
    ...: df['text'].apply(lambda x: len(set(str(x).lower().split()).intersection(obs))).mean()
    ...: 
    ...: 
324 ms ± 4.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 如果结果是二进制的(1 的总和超过计数 = 均值),我可以看到为什么要使用均值,但是当有多个关键字时,结果可能会出现偏差,你不觉得吗?
  • @GavrielGoidel - 我需要测试一下。
  • @GavrielGoidel - 另一种解决方案更快?
猜你喜欢
  • 2014-05-23
  • 2011-08-10
  • 1970-01-01
  • 1970-01-01
  • 2010-10-28
  • 2012-05-23
  • 2021-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多