Pandas returns 当有很多组时,float64 的 groupby 滚动零和不正确

Pandas returns incorrect groupby rolling sum of zeros for float64 when having many groups

当使用 dtype float64 在 pandas 中进行 groupby rolling 时,当组数很大时,零和变成任意小的浮点数。例如,

import pandas as pd
import numpy as np

np.random.seed(1)
df = pd.DataFrame({'a': (np.random.random(800)*1e5+1e5).tolist() + [0.0]*800, 'b': list(range(80))*20})
a = df.groupby('b').rolling(5, min_periods=1).agg({'a': 'sum'})

第一行生成一个包含 2 列 ab 的数据框。

例如,对于第 79 组,df 如下所示:

                  a   b
79    158742.001924  79
159   115045.502837  79
239   171582.695286  79
319   181072.123361  79
399   194672.826961  79
479   130100.794308  79
559   169784.165605  79
639   132752.405585  79
719   162355.180105  79
799   148140.045915  79
879        0.000000  79
959        0.000000  79
1039       0.000000  79
1119       0.000000  79
1199       0.000000  79
1279       0.000000  79
1359       0.000000  79
1439       0.000000  79
1519       0.000000  79
1599       0.000000  79

第二行计算每组 a 列的 5 的滚动总和。

人们会期望每组中最后几个条目的滚动总和为零,例如79. 但是,会返回任意小的浮点数,例如-5.820766e-11 下面第 79 组

                 a
79    1.587420e+05
159   2.737875e+05
239   4.453702e+05
319   6.264423e+05
399   8.211152e+05
479   7.924739e+05
559   8.472126e+05
639   8.083823e+05
719   7.896654e+05
799   7.431326e+05
879   6.130318e+05
959   4.432476e+05
1039  3.104952e+05
1119  1.481400e+05
1199 -5.820766e-11
1279 -5.820766e-11
1359 -5.820766e-11
1439 -5.820766e-11
1519 -5.820766e-11
1599 -5.820766e-11

如果我们将组数减少到 20,问题就会消失。例如

df['b'] = df['b'] = list(range(20))*80
a = df.groupby('b').rolling(5, min_periods=1).agg({'a': 'sum'})

这产生(对于第 19 组,因为从 0-19 仅有 20 个组)

                  a
19    165083.125668
39    359750.793592
59    485563.758520
79    644305.760443
99    837370.199660
             ...
1519       0.000000
1539       0.000000
1559       0.000000
1579       0.000000
1599       0.000000
[80 rows x 1 columns]

这仅在 pandas 1.2.5/python 3.7.9/windows 10 上测试过。您可能需要增加组数才能显示,具体取决于在你的机器内存上。

在我的应用程序中,我无法真正控制组的数量。我可以将 dtype 更改为 float32,问题就消失了。但是,这导致我对大数字失去了精度。

除了使用 float32 之外,知道是什么原因造成的以及如何解决它吗?

TLDR:这是优化的副作用;解决方法是使用 non-pandas 总和。

原因是pandas试图优化。天真的滚动 window 函数将花费 O(n*w) 时间。但是,如果我们知道该函数是求和,我们可以减去 window 中的一个元素并添加进入它的元素。这种方法不再依赖于 window 大小,并且总是 O(n).

需要注意的是,现在我们将得到浮点精度的副作用,其表现类似于您所描述的。

来源:Python code calling window aggregation, Cython implementation of the rolling sum