滚动总和(浮点精度)的奇怪或不准确的结果

Strange or inaccurate result with rolling sum (floating point precision)

我有一个从外部来源 (x) 获得的系列。都是正数,大部分为零。

x.describe()
count    23275.000000
mean         0.015597
std          0.411720
min          0.000000
25%          0.000000
50%          0.000000
75%          0.000000
max         26.000000
dtype: float64

但是,运行 rolling_sum 生成的值小于零。为什么会这样? 有什么方法可以avoid/bypass吗?

rolling_sum(x, window=100).iloc[-1]
-1.4743761767e-13
(rolling_sum(x, window=100)<0).sum()
16291

更奇怪的是,这两个计算(据我所知应该产生相同的值)不:

rolling_sum(x, window=100).iloc[-1]
-1.4743761767e-13
rolling_sum(x.iloc[-100:], window=100).iloc[-1]
0.0

(pandas 0.14.1 和 0.15.2 都有)

我想我能猜到发生了什么:

In [481]: df=pd.DataFrame( { 'x':[0,0,.1,.2,0,0] } )

In [482]: df2 = pd.rolling_sum(df,window=2)

In [483]: df2
Out[483]: 
              x
0           NaN
1  0.000000e+00
2  1.000000e-01
3  3.000000e-01
4  2.000000e-01
5  2.775558e-17

看起来还不错,除了最后一个,对吧?事实上,四舍五入掩盖了其他一些条目并不像乍一看那样干净。只是默认显示格式会掩盖这一点,除非您的值非常接近于零。

In [493]: for i in range(6):
     ...:     print '%22.19f' % df2.ix[i,'x']
                   nan
 0.0000000000000000000
 0.1000000000000000056
 0.3000000000000000444
 0.2000000000000000389
 0.0000000000000000278

这里发生的事情是 rolling_sum 实际上不会每次都进行新的求和。相反,它将通过添加最新数字并删除最旧数字来更新总和。在这个带有 window=2 的简单示例中,这没有用,但如果 window 大得多,则可以大大加快计算速度,因此这样做是有意义的。

但是,这意味着可能会发生一些意想不到的结果。您期望最后的滚动总和是 0+0 的结果,但事实并非如此,它实际上是这样的:

In [492]: (.0+.0)+(.1-.0)+(.2-.0)+(.0-.1)+(.0-.2)
Out[492]: 2.7755575615628914e-17

底线:你的结果基本上没问题。碰巧你做这件事的方式(使用这些数据)揭示了这些事情中固有的潜在精度问题。这种情况经常发生,但默认显示通常会隐藏这些发生在小数点后 13 位的事情。

编辑添加:根据 Korem 的评论,小的负数实际上会导致问题。我认为在这种情况下最好的办法是使用 numpy 的 around 函数并将上面的第二步替换为:

 df2 = np.around(pd.rolling_sum(df,window=2),decimals=5)

这将强制所有小数(正数或负数)为零。我认为这是一个非常安全的通用解决方案。如果您的所有数据都有整数值,您可以将其重新转换为整数,但这显然不是一个非常通用的解决方案。

此问题也与 pd.rolling() 方法有关,如果您在高精度的相对较小值列表中包含一个较大的正整数,也会出现此问题。

import pandas as pd
x = pd.DataFrame([0, 1, 2, 2.23425304, 3.2342352934, 4.32423857239])
x.rolling(window=2).mean()
          0
0       NaN
1  0.500000
2  1.500000
3  2.117127
4  2.734244
5  3.779237

正在用 1E15 替换第二个元素...

x = pd.DataFrame([0, 1, 1E15, 2.23425304, 3.2342352934, 4.32423857239])
x.rolling(window=2).mean()
              0
0           NaN
1  5.000000e-01
2  5.000000e+14
3  5.000000e+14
4  2.750000e+00
5  3.794993e+00

滚动标准偏差更明显...

x = pd.DataFrame([0, 1, 2, 2.23425304, 3.2342352934, 4.32423857239])
x.rolling(window=2).std()
          0
0       NaN
1  0.707107
2  0.707107
3  0.165642
4  0.707094
5  0.770749

x = pd.DataFrame([0, 1, 1E15, 2.23425304, 3.2342352934, 4.32423857239])
x.rolling(window=2).std()
              0
0           NaN
1  7.071068e-01
2  7.071068e+14
3  7.071068e+14
4  1.186328e+07
5  1.186328e+07

唯一的解决方案似乎是为了准确性而牺牲性能优势,即直接进行滚动平均。

def rolling_window_slow(window, df):
    df_mean = []
    for i in range(len(df) - window):
        df_mean.append(df.iloc[i:i+window, :].mean())
    return df_mean