关于r:滑动窗口的精度高于diff(cumsum(…))

Sliding window with higher precision than diff(cumsum(…))

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

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

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

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

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

但是我担心这里的精度。我想到了一个设置,其中所有值都具有相同的符号,向量将非常大。预先汇总值可能会导致大量数字,因此不会有很多有效数字可用于处理个体差异。感觉不好。

我想至少在使用单个函数而不是两个函数时,应该可以做得更好。一个实现可以维持当前的总和,无需为每个迭代添加一个元素,然后减去另一个元素。由于这样做仍然会沿途累积舍入误差,因此可以从两端分别进行计算,并且如果中心处的结果相距太远,则可以从中心处计算出一个新的结果,从而提高除法和除法的精度征服方法。

您知道有什么实现这样的实现的吗?
还是有原因导致此功能无法正常运行?
也许是diff(cumsum(…))方法没有看起来那么糟糕的原因吗?

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

我在真实数据中看到了一些看起来很吵的东西,我认为这是由于此类数字问题造成的。由于使用答案和评论中的不同建议来计算这些值的几种不同方法仍然导致相似的结果,因此可能是我的数据的奇怪性实际上并非由于数字问题。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
3
4
5
6
7
8
9
10
11
12
13
14
15
                               [,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足够慢,以至于我不想等待它的完成。如果您有性能基准或要添加到比较中的新建议,请随时编辑此问题。


此答案基于约书亚·乌尔里希(Joshua Ulrich)的评论。

caTools软件包提供了一个函数runmean,该函数计算我的部分和,除以窗口大小(或所讨论的窗口中not- NA元素的数量)。引用其文档:

In case of runmean(..., alg="exact") function a special algorithm is used (see references section) to ensure that round-off errors do not accumulate. As a result runmean is more accurate than filter(x, rep(1/k,k)) and runmean(..., alg="C") functions.

Note:

Function runmean(..., alg="exact") is based by code by Vadim Ogranovich, which is based on Python code (see last reference), pointed out by Gabor Grothendieck.

References:

  • About round-off error correction used in runmean: Shewchuk, Jonathan
    Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates
  • More on round-off error correction can be found at:
    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/393090

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

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

不幸的是,源代码包含一个runsum_exact函数,但已将其注释掉。求平均值的除法,再加上相乘得出总和,将引入舍入误差,这是可以避免的。为此,CHANGES文件说:

11) caTools-1.11 (Dec 2010)

  • Fully retired runsum.exact, which was not working for a while, use runmean with"exact" option instead.

此刻(2012年5月22日的caTools版本1.14)该软件包似乎是孤立的。


我不能说这是否是一个实现,但是有

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

看一下filter的正文,看来辛苦的工作已经转移到C例程C_rfilter,所以也许您可以检查一下它是否满足您的精度要求。否则,@ JoshuaUlrich的建议听起来很有希望。