滑动窗口的精度高于diff(cumsum(...))

时间:2013-05-30 19:27:47

标签: r precision sliding-window cumulative-sum

在滑动窗口中计算元素总和的最佳R习惯是什么?

从概念上讲,我想要以下内容:

for (i in 1:(length(input) - lag + 1))
  output[i] <- sum(input[i:(i + lag - 1)])

换句话说,每个输出元素应该是固定数量的输入元素(此处称为lag)的总和,从而产生适当更短的结果向量。我知道理论上我可以把它写成

output = diff(cumsum(c(0, input)), lag = lag)

但我担心这里的精确度。我有一个设置,其中所有值将具有相同的符号,并且向量将非常大。预先总结这些值可能会导致prety大数字,因此个别差异不会有很多有效数字。这感觉很糟糕。

我认为应该可以做得更好,至少在使用单个函数而不是两个函数时。实现可以保持当前总和,并且每次迭代添加一个元素并减去另一个元素。由于这仍然会在整个过程中累积舍入误差,因此可以从两端分别执行计算,如果中心的结果太远,则从中心计算出新的结果,从而提高分割和 - 的精度征服方法。

您知道有任何类似的实施吗?
或者有什么理由不能像我认为的那样起作用? 或者也许是diff(cumsum(…))方法没有看起来那么糟糕的原因?


编辑:我的上述表述中有一些错误,使它们不一致。现在他们似乎同意测试数据。 lag应该是求和的元素数量,因此我期望得到更短的向量。我不处理时间序列对象,所以绝对时间对齐与我无关。

我在实际数据中看到了一些看似嘈杂的东西,我原以为这是因为这些数字问题。由于使用答案和评论中的不同建议来计算这些值的几种不同方法仍然会产生类似的结果,因此我的数据的奇怪性可能实际上并不是由于数字问题。

因此,为了评估答案,我使用了以下设置:

library(Rmpfr)
library(caTools)

len <- 1024*1024*8
lag <- 3
precBits <- 128
taillen <- 6

set.seed(42) # reproducible
input <- runif(len)
input <- input + runif(len, min=-1e-9, max=1e-9) # use >32 bits

options(digits = 22)

# Reference: sum everything separately using high precision.
output <- mpfr(rep(0, taillen), precBits = precBits)
for (i in 1:taillen)
  output[i] <- sum(mpfr(input[(len-taillen+i-lag+1):(len-taillen+i)],
                        precBits=precBits))
output

addResult <- function(data, name) {
  n <- c(rownames(resmat), name)
  r <- rbind(resmat, as.numeric(tail(data, taillen)))
  rownames(r) <- n
  assign("resmat", r, parent.frame())
}

# reference solution, rounded to nearest double, assumed to be correct
resmat <- matrix(as.numeric(output), nrow=1)
rownames(resmat) <- "Reference"

# my original solution
addResult(diff(cumsum(c(0, input)), lag=lag), "diff+cumsum")

# filter as suggested by Matthew Plourde
addResult(filter(input, rep(1, lag), sides=1)[lag:length(input)], "filter")

# caTools as suggested by Joshua Ulrich
addResult(lag*runmean(input, lag, alg="exact", endrule="trim"), "caTools")

结果如下:

                               [,1]                    [,2]
Reference   2.380384891521345469556 2.036472557725210297264
diff+cumsum 2.380384892225265502930 2.036472558043897151947
filter      2.380384891521345469556 2.036472557725210741353
caTools     2.380384891521345469556 2.036472557725210741353
                               [,3]                    [,4]
Reference   1.999147923481302324689 1.998499369297661143463
diff+cumsum 1.999147923663258552551 1.998499369248747825623
filter      1.999147923481302324689 1.998499369297661143463
caTools     1.999147923481302324689 1.998499369297661143463
                               [,5]                    [,6]
Reference   2.363071143676507723796 1.939272651346203080180
diff+cumsum 2.363071143627166748047 1.939272651448845863342
filter      2.363071143676507723796 1.939272651346203080180
caTools     2.363071143676507723796 1.939272651346203080180

结果表明diff + cumsum仍然令人惊讶地准确。 (在我考虑添加第二个runif向量之前,它似乎更准确。)filtercaTools两者几乎无法与完美结果区分开来。至于性能,我还没有测试过(还)。我只知道128位的Rmpfr cumsum足够慢,我不想等待完成。如果您有性能基准,或者想要添加到比较中的新建议,请随意编辑此问题。

2 个答案:

答案 0 :(得分:1)

我不能说这是否是这样的实现,但有

filter(input, sides=2, filter=rep(1, lag+1))

查看filter的正文,看起来硬件工作被传递给C例程C_rfilter,所以也许您可以检查它以查看它是否满足您的精度要求。否则,@ JoshuaUlrich的建议听起来很有希望。

答案 1 :(得分:1)

此答案基于the commentJoshua Ulrich

caTools提供了一个函数runmean,它计算我的部分和,除以窗口大小(或者更确切地说是所讨论的窗口中的非NA元素的数量)。引用其文档:

  

runmean(..., alg="exact")函数的情况下,使用特殊算法(参见参考部分)以确保不会累积舍入误差。因此,runmeanfilter(x, rep(1/k,k))runmean(..., alg="C")函数更准确。

     

注意

     

函数runmean(..., alg="exact")基于Vadim Ogranovich的代码,该代码基于Python代码(参见last reference),由Gabor Grothendieck指出。

     

<强>参考

     

代码使用一系列双精度浮点值存储当前窗口的总和,其中较小的值表示较大元素引起的舍入误差。因此,即使输入数据在一次通过中处理,添加一个元素并在每个步骤中移除另一个元素,也不应该存在任何舍入误差的累积。最终结果应该与双精度算术可以表示的一样精确。

exact以外的算法似乎会产生稍微不同的结果,所以我可能不会建议这些。

源代码包含runsum_exact函数似乎有点不幸,但它被注释掉了。获得均值的除法与乘法相结合以得到总和,将引入本可以避免的舍入误差。对此CHANGES文件说:

  

11)caTools-1.11(2010年12月)

     
      
  • 完全退役的runsum.exact,暂时没有工作,使用带有“确切”选项的runmean。
  •   

目前(2012-05-22 caTools版本1.14),该软件包似乎是孤立的。