我想针对大量组逐个计算滚动总和,但是我很难快速地完成它。
Pandas具有用于滚动和扩展计算的内置方法
这是一个例子:
import pandas as pd
import numpy as np
obs_per_g = 20
g = 10000
obs = g * obs_per_g
k = 20
df = pd.DataFrame(
data=np.random.normal(size=obs * k).reshape(obs, k),
index=pd.MultiIndex.from_product(iterables=[range(g), range(obs_per_g)]),
)
我可以使用要滚动和扩展的总和
df.groupby(level=0).expanding().sum()
df.groupby(level=0).rolling(window=5).sum()
但是对于大量的团体来说,这需要很长时间。要扩展总和,相反,使用pandas方法的cumsum快60倍(上例中为16s vs 280ms),并将小时变成分钟。
df.groupby(level=0).cumsum()
在大熊猫中是否可以快速实现滚动总和,例如cumsum用于扩展总和?如果没有,我可以使用numpy完成此操作吗?
答案 0 :(得分:1)
我对.rolling()
很满意,但只有小型数据集,或者如果您要应用的函数是非标准的,我就拥有相同的经验,对于sum()
,我建议使用cumsum()
并减去cumsum().shift(5)
df.groupby(level=0).cumsum() - df.groupby(level=0).cumsum().shift(5)
答案 1 :(得分:1)
提供这方面的最新信息,如果升级pandas,groupby 滚动的性能得到了显着提高。与 0.24 或 1.0.0 相比,这在 1.1.0 中快了大约 4-5 倍,在 >1.2.0 中快了 12 倍。
我相信最大的性能改进来自这个 PR,这意味着它可以在 cython 中做更多的事情(在它像 groupby.apply(lambda x: x.rolling())
一样实现之前)。
我使用以下代码进行基准测试:
import pandas
import numpy
print(pandas.__version__)
print(numpy.__version__)
def stack_overflow_df():
obs_per_g = 20
g = 10000
obs = g * obs_per_g
k = 2
df = pandas.DataFrame(
data=numpy.random.normal(size=obs * k).reshape(obs, k),
index=pandas.MultiIndex.from_product(iterables=[range(g), range(obs_per_g)]),
)
return df
df = stack_overflow_df()
# N.B. droplevel important to make indices match
rolling_result = (
df.groupby(level=0)[[0, 1]].rolling(10, min_periods=1).sum().droplevel(level=0)
)
df[["value_0_rolling_sum", "value_1_rolling_sum"]] = rolling_result
%%timeit
# results:
# numpy version always 1.19.4
# pandas 0.24 = 12.3 seconds
# pandas 1.0.5 = 12.9 seconds
# pandas 1.1.0 = broken with groupby rolling bug
# pandas 1.1.1 = 2.9 seconds
# pandas 1.1.5 = 2.5 seconds
# pandas 1.2.0 = 1.06 seconds
# pandas 1.2.2 = 1.06 seconds
我认为如果尝试使用 numpy.cumsum 来提高性能(无论 Pandas 版本如何),必须小心。例如,使用以下内容:
# Gives different output
df.groupby(level=0)[[0, 1]].cumsum() - df.groupby(level=0)[[0, 1]].cumsum().shift(10)
虽然这要快得多,但输出不正确。这种转变在所有行上执行,并混合不同组的累积。即下一组的第一个结果被移回上一组。
要获得与上述相同的行为,您需要使用 apply:
df.groupby(level=0)[[0, 1]].cumsum() - df.groupby(level=0)[[0, 1]].apply(
lambda x: x.cumsum().shift(10).fillna(0)
)
在最新版本 (1.2.2) 中,这比直接使用滚动要慢。 因此,对于 groupby 滚动总和,我不认为 numpy.cumsum 是 pandas>=1.1.1
的最佳解决方案为了完整起见,如果您的组是列而不是索引,您应该使用如下语法:
# N.B. reset_index important to make indices match
rolling_result = (
df.groupby(["category_0", "category_1"])[["value_0", "value_1"]]
.rolling(10, min_periods=1)
.sum()
.reset_index(drop=True)
)
df[["value_0_rolling_sum", "value_1_rolling_sum"]] = rolling_result