滚动总和(浮点精度)的奇怪或不准确的结果
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
我有一个从外部来源 (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