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

时间:2015-03-09 21:31:09

标签: python pandas floating-accuracy

我有一个来自外部来源(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会产生小于零的值。为什么会这样? 有什么方法可以避免/绕过它?

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

(这是大熊猫0.14.1和0.15.2)

2 个答案:

答案 0 :(得分:6)

我想我可以猜到发生了什么:

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的这个简单示例中,这将没有用,但是如果窗口大得多,那么可以大大加快计算速度,因此以这种方式执行它是有意义的。

然而,这意味着可能会发生一些意想不到的结果。你期望最后的滚动总和是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)

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

答案 1 :(得分:0)

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