【问题标题】:Can `ddply` (or similar) do a sliding window?`ddply`(或类似的)可以做一个滑动窗口吗?
【发布时间】:2011-08-29 04:14:16
【问题描述】:

类似

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 里面,这样我就可以得到漂亮的语法糖 是自带的吗?

【问题讨论】:

  • 你的意思是像zoo包中的rollapply
  • 不,但我懒散地考虑让 ddply 和朋友使用更通用的迭代器。
  • @hadley:快速浏览一下,似乎只需要允许人们传入他们自己的自定义 splitter_d (并记录它的假定返回值)就可以了。如果您将其作为额外参数提供,并将您自己的 splitter_d 作为默认值,我认为它应该可以工作,对吧?而写一个“滑动窗口索引”,本质上是欧文已经完成了。

标签: r plyr


【解决方案1】:

由于尚未发布此问题的答案,我想我会提出一个案例,以证明实际上有一种更好的方法来解决这类问题 - 也可能是速度提高数千倍。 (如果这没有帮助,请告诉我,但我认为在这里总比没有好)

每当我听到“移动平均线”或“滑动窗口”时,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))的身份。我很确定没有直接的方法可以在频域中找到中位数或分位数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-04
  • 1970-01-01
  • 2011-10-12
  • 2021-09-10
  • 2012-10-23
相关资源
最近更新 更多