`ddply`(或类似函数)能够做滑动窗口吗?

15

类似于什么

sliding = function(df, n, f)
    ldply(1:(nrow(df) - n + 1), function(k)
        f(df[k:(k + n - 1), ])
    )

那将会像这样使用

> df
  n         a
1 1 0.8021891
2 2 0.9446330
...

> sliding(df, 2, function(df) with(df,
+     data.frame(n = n[1], a = a[1], b = sum(n - a))
+ ))
  n         a        b
1 1 0.8021891 1.253178
...

除了直接在ddply中使用,这样我就可以获得它带来的优美语法糖,还有其他方法吗?


4
你是指zoo包中的rollapply函数吗? - joran
2
不,但我曾经考虑过让ddply和其他函数能够与更通用的迭代器一起使用。 - hadley
1
@hadley:从快速查看来看,似乎只需要允许人们传入自己的自定义splitter_d(并记录其预期返回值),就可以完成这个任务。如果您将此作为额外参数提供,并将自己的splitter_d作为默认值,我认为它应该可以工作,对吧?而编写“滑动窗口索引”本质上已经由Owen完成了。 - Nick Sabbe
1个回答

9
由于这个问题还没有得到回答,我想提供一个更好的解决方案,可以更快地解决这类问题,速度可能高达数千倍。每当我听到“移动平均”或“滑动窗口”时,我马上想到FFT卷积。这是因为它可以以极高效率处理这些类型的问题。由于所有的“滑动”都在后台完成,所以我认为它也具有你所期望的所有句法美感。
(以下代码可在https://gist.github.com/1320175中的一个文件中找到)
我们首先模拟一些数据(这里我使用整数是为了简单起见,但当然你不需要这样做)。
require(plyr)
set.seed(12345)

n = 10
n.sum = 2
a = sample.int(10, n, replace=T)

df = data.frame(n=1:n, a)

> df
    n  a
1   1  8
2   2  9
3   3  8
4   4  9
5   5  5
6   6  2
7   7  4
8   8  6
9   9  8
10 10 10

现在,我们将一次性预先计算出n-a的所有值。
n.minus.a = with(df, n - a)

接下来,定义一个内核 k,当它与我们的输入n.minus.a卷积时,将对我们的数据进行求和(或平均/平滑/其他操作)。
k = rep(0, n)
k[1:n.sum] = 1

一切设置完成后,我们可以通过 fft() 在频率域中定义一个函数来高效地执行此卷积。

myConv <- function(x, k){
  Fx  = fft(x)
  Fk  = fft(k)
  Fxk = Fx * Fk
  xk  = fft(Fxk, inverse=T)
  (Re(xk) / n)[-(1:(n.sum-1))]
}

执行这个的语法非常简单易懂:
> myConv(n.minus.a, k)
[1] -14 -12 -10  -5   4   7   5   3   1

当您使用R中的convolve()便捷函数时,所有这些也会在幕后发生。
> convolve(n.minus.a, k)[1:(length(n.minus.a)-n.sum+1)]
[1] -14 -12 -10  -5   4   7   5   3   1

我们现在对比一下手动方法,以证明结果是等价的:
> sliding(df, 2, function(df) with(df, data.frame(n = n[1], a = a[1], b = sum(n - a))))
  n a   b
1 1 8 -14
2 2 9 -12
3 3 8 -10
4 4 9  -5
5 5 5   4
6 6 2   7
7 7 4   5
8 8 6   3
9 9 8   1

最后,我们将使 n=10^4 并测试所有这些方法的速度:

> system.time(myConv(n.minus.a, k))
   user  system elapsed 
  0.002   0.000   0.002 
> system.time(convolve(n.minus.a, k, type='circ')[1:(length(n.minus.a)-n.sum+1)])
   user  system elapsed 
  0.002   0.000   0.002 
> system.time(sliding(df, 2, function(df) with(df, data.frame(n = n[1], a = a[1], b = sum(n - a)))))
   user  system elapsed 
  7.944   0.018   7.962 

FFT方法返回结果几乎是瞬间的,即使是粗略计时,也比手动方法快近4000倍。
当然,并非每种类型的滑动问题都可以归类到这种范式中,但对于像使用sum()(以及平均数、加权平均数等)的数值问题,它完美地发挥了作用。无论如何,通常值得至少谷歌一下,看看是否有可用的滤波器核心能够解决特定的问题。祝好运!

不幸的是,这种方法依赖于FT[a(t) convolve b(t)] === FT(a(t)) * FT(b(t))的恒等式。我相信在频域中找到中位数或分位数没有直接的方法。 - user1158559

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接