处理数百万条推文 - 循环非常慢。关于如何加快速度的任何想法?

Processing several million tweets - loop is very slow. Any ideas on how to speed it up?

如标题所示,我正在处理数百万条推文,其中一个数据点是两个不同列表中是否存在任何单词(每个列表包含大约 500 个单词)。可以理解,它很慢,但我会定期这样做,所以我想加快速度。有什么想法吗?

lista = ['word1', 'word2', ... 'word500']
listb = ['word1', 'word2', ..., 'word500']

def token_list_count(df):

    for i, t in df.iterrows():

        list_a = 0
        list_b = 0

        for tok in t['tokens']:
            if tok in lista: list_a += 1
            elif tok in listb: list_b += 1

        df.loc[i, 'token_count'] = int(len(t['tokens']))
        df.loc[i, 'lista_count'] = int(list_a)
        df.loc[i, 'listb_count'] = int(list_b)

        if i % 25000 == 0: print('25k more processed...')

    return df

编辑:

输入/之前:

输出/之后:

使用下面的代码应该可以为您节省一些时间。请注意,如果您按原样使用下面的代码,您在 listb 中出现的次数是不正确的。

您不会从下面的代码中获得太多加速,因为创建集合将占用几乎所有来自更快查找的时间优势,但是如果您将 seta 和 setb 也用于另一个查找循环,您将节省时间如果进一步的代码。

也许你可以安排,直接将列表作为集合获取,从而节省创建集合的时间?

lista = ['word1a', 'word2a', 'word500a']
listb = ['word1b', 'word2a', 'word500b']

seta = set(lista)
setb = set(listb)

def token_list_count(df):

    dfIterrows = df.iterrows() # TRY THIS !!! 
    for i, t in dfIterrows:    # TRY THIS !!! 
        list_a = 0
        list_b = 0
        tTokens = t['tokens']  # TRY THIS !!!
        for tok in tTokens:    # TRY THIS !!!
            if   tok in seta: list_a += 1
            elif tok in setb: list_b += 1
            # why not "if tok in setb: list_b +=1" ???
        df.loc[i, 'token_count'] = int(len(t['tokens']))
        df.loc[i, 'lista_count'] = int(list_a)
        df.loc[i, 'listb_count'] = int(list_b)

        if i % 25000 == 0: print('25k more processed...')

    return df

如果速度对你来说真的很重要,我建议你只使用 Python 作为一个框架, 除了多处理之外,还使用适当的可执行文件或数据库查询 进行查找用于在 seta 和 setb 中查找。

另一种选择是考虑 root 在评论中提出的建议:“”"The biggest issue here is that you're iterating over a DataFrame, which should almost always be avoided. Using a vectorized approach is what will yield the best performance improvements. "”“

为了避免迭代数据框,您可以尝试下面的函数。第一行将 tokens 列扩展为数据框,其中每个第一个标记将在第一列中,第二个标记将在第二列中,依此类推。总列数将等于 df 中最大的 token_count。从那里可以很容易地使用 .isin 方法计算 listalistb 计数,并对每一行求和。

由完整 df 制作的 tok 数据帧可能会占用大量内存,在这种情况下,可能需要将 df 分成几个块并分别处理它们。

def token_list_count(df):
    tok = df['tokens'].apply(pd.Series)
    df['token_count'] = tok.notnull().sum(axis=1)
    df['lista_count'] = tok.isin(lista).sum(axis=1)
    df['listb_count'] = tok.isin(listb).sum(axis=1)
    return df

你原来的功能还有改进的余地:

  • 没有必要使用 iterrows() 遍历整个 df,因为您只需要一列即可进行计算。所以你可以使用 df.tokens.iteritems() 代替。这将使您在每次迭代时都创建一个新的 Series 对象。
  • 因为您一次只设置一个新值,所以您可以使用 at 索引器而不是 loc。请参阅 pandas 文档中的 Fast scalar value getting and setting

我不知道这会有多大帮助,但可能会有一些帮助 =)。

def token_list_count(df):
    for i, tok in df.tokens.iteritems():
        list_a = sum((t in lista) for t in tok)
        list_b = sum((t in listb) for t in tok)

        df.at[i, 'token_count'] = int(len(tok))
        df.at[i, 'lista_count'] = int(list_a)
        df.at[i, 'listb_count'] = int(list_b)

        if i % 25000 == 0: print('25k more processed...')
    return df

你也可以不使用 for 循环但使用 apply:

df['token_count'] = df.tokens.apply(len)
df['lista_count'] = df.tokens.apply(lambda tok: sum((t in lista) for t in tok))
df['listb_count'] = df.tokens.apply(lambda tok: sum((t in listb) for t in tok))