如何加快对多个列执行总和的 pandas groupby?

How can I speed up a pandas groupby that is performing a sum on more than one column?

询问

我想加快 pandas groupby 的速度,它也在两列上应用求和并返回结果数据帧。

代码

df = df.groupby(['key','code','name','period','agg_metric'], sort=False, observed=True, dropna=False)[['metricA','metricB']]\
.sum().reset_index()

(该方法目前需要 2 分钟来处理我最大用例的数据。)

数据

总的来说,最大的数据框有大约 150 万行应用了 groupby。 Period 和 agg_metric 可以相互推断,其中只有 2 个 period 值(因此有 2 个 agg_metric 值)。 name值也可以从代码中推断出来。

groupby 之后,我剩下 700k 条记录。如果我理解正确,速度减慢是由于处理的结果组的数量。有没有一种可能的方法来向量化此方法并立即将求和应用于每个组,而不是我假设当前正在迭代的方法。

注释

我尝试过使用 groupby().agg({...})groupby().apply(lambda),两者花费的时间大致相同。我也试过删除一些 groupby 列,然后稍后再将它们添加回来,但它并没有加快计算速度,因此不能保证将它们从 groupby 中移除。该代码段还具有 sort=False 和 observed=True,但两者都没有改善处理时间。

我已经尽可能多地浏览了所有资源(尤其是这篇很棒的参考资料:)。我是矢量化的新手,我正在这样做,因为我正在从我们的数据库中卸载几个查询。

你有什么类型的数据? 看起来 metricA / metricB 列的类型是 object,而 pandas 对 Python 对象执行慢速求和而不是对 numpy 数组进行快速求和。尝试将指标列转换为 float64integer 类型。

您可以使用 df.info() 方法检查数据类型。

证明:

from string import ascii_letters
from time import time

import numpy as np
import pandas as pd
from numpy.random import choice

N = 1_500_000
np.random.seed(123)
letters = list(ascii_letters)
words = ["".join(choice(letters, 5)) for i in range(30)]

df = pd.DataFrame(
    {
        "key": choice(words, N),
        "code": choice(words, N),
        "name": choice(words, N),
        "period": np.random.randint(0, 10, N),
        "agg_metric": choice(["mean", "sum", "count"], N),
        "metricA": np.random.rand(N),
        "metricB": np.random.rand(N),
    }
)

def aggregate(df):
    return (
        df.groupby(
            ["key", "code", "name", "period", "agg_metric"],
            sort=False,
            observed=True,
            dropna=False,
        )[["metricA", "metricB"]]
        .sum()
        .reset_index()
    )

start = time()
df2 = aggregate(df)
print(f"sum floats took {time() - start}")

start = time()
df3 = aggregate(df.astype({"metricA": object, "metricB": object}))
print(f"sum objects took {time() - start}")

assert df2.equals(df3)

输出:

sum floats took 0.2983248233795166
sum objects took 81.04267287254333