【问题标题】:Need moving average function to only consider previous observations需要移动平均函数来只考虑以前的观察
【发布时间】:2018-12-29 14:09:24
【问题描述】:

我需要为每个不同的数据组创建一个变量的移动平均值,该平均值仅考虑该变量的先前观察结果。

我曾经使用一个函数,然后稍微修改我的变量以使其工作。下面我来解释一下。

我从 stackoverflow 得到这个函数:

mav <- function(x,n) if(length(x) >= n)stats::filter(x,rep(1/n,n), sides=1) else NA_real_ 

让我们以 2 个观察值的移动平均值为例:

test = data.table("values" = c(1,2,3,4,5,6,7,8, 9,10,11,12), "category" = c(1,1,1,1,1,1,2,2,2,2,2,2))
test[, ma2 := as.numeric(mav(values, n = 2)), by = category]

这会产生:

   values category  ma2
      1        1   NA
      2        1  1.5
      3        1  2.5
      4        1  3.5
      5        1  4.5
      6        1  5.5
      7        2   NA
      8        2  7.5
      9        2  8.5
     10        2  9.5
     11        2 10.5
     12        2 11.5

我希望 ma2 的第三个观测值是 ma2 的最后两个观测值的平均值。但是在这里,ma2 的第 3 次观测值是第 2 次和第 3 次观测值的平均值。

所以我创建了另一个变量“Vprev”,它与“Values”相同,但每次观察都采用“Values”的前一个值:

test[, vprev:= as.numeric(shift(values, 1L, type = "lag" )), by = category]

然后,我在 vprev 变量上运行移动平均线(“TRUEma2”):

test[, TRUEma2 := as.numeric(mav(vprev, n = 2)), by = category] 

values category  ma2 vprev TRUEma2
  1        1   NA    NA      NA
  2        1  1.5     1      NA
  3        1  2.5     2     1.5
  4        1  3.5     3     2.5
  5        1  4.5     4     3.5
  6        1  5.5     5     4.5
  7        2   NA    NA      NA
  8        2  7.5     7      NA
  9        2  8.5     8     7.5
 10        2  9.5     9     8.5
 11        2 10.5    10     9.5
 12        2 11.5    11    10.5

过去工作得很好,因为我的数据集非常小。但是现在我必须在具有大约 2 到 3 百万个观察值的多个数据集上执行此操作。我必须为每个数据集中的大约 30 个变量创建移动平均线。我描述的每个变量的过程最多需要 1 分 40 秒,所以我计算出我需要 25 小时来预处理我的所有数据集......

我看到最耗时的是我创建一个新变量的部分,该变量是另一个变量的先前观察(大约 1 分钟):

test[, vprev:= as.numeric(shift(values, 1L, type = "lag" )), by = category]

移动平均线本身并不需要很多时间来计算。

我尝试通过在移动平均代码行中放置一个 shift() 来跳过这个:

test[, TRUEma2 := as.numeric(mav(shift(values,1L,type = "lag), n = 2)), by = category]   

但这并没有更快。

我也试过这样修改移动平均函数:

mav2 <- function(x,n) if(length(x) >= n+1)stats::filter(x-1,rep(1/n,n), sides=1) else NA_real_ 

但是x的第一个值可以取它之前的观察值,即不在同一个数据组/类别中。

     values category mav2
      1        1   NA
      2        1  0.5
      3        1  1.5
      4        1  2.5
      5        1  3.5
      6        1  4.5
      7        2   NA
      8        2  6.5
      9        2  7.5
     10        2  8.5
     11        2  9.5
     12        2 10.5

所以这是我的问题:是否有可能有一个与上述第一个一样快的移动平均函数,但它只计算先前观察的平均值?

非常感谢您的帮助:)

编辑:我尝试了 lbusett 和 Icecreamtoucan 提出的解决方案,虽然它适用于测试数据,但我在真实数据上收到以下错误消息

Error in[.data.table(toptrain2, ,:=(paste0("m3_", c("killsM")), : Type of RHS ('double') must match LHS ('logical'). To check and coerce would impact performance too much for the fastest cases. Either change the type of the target column, or coerce the RHS of := yourself (e.g. by using 1L instead of 1)

我被要求提供实际数据的样本。这是一个 dput(只是我数据的一小部分):

structure(list(killsM = c(4L, 2L, 0L, 3L, 6L, 0L, 1L, 2L, 3L, 5L, 6L, 1L, 4L, 4L, 2L, 6L, 6L, 3L, 1L, 2L), soloKillsM = c(4L, 2L, 0L, 0L, 3L, 0L, 0L, 1L, 1L, 3L, 0L, 0L, 1L, 2L, 0L, 3L, 0L, 1L, 0L, 0L), deathsM = c(3L, 5L, 5L, 1L, 4L, 4L, 3L, 2L, 0L, 4L, 1L, 7L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L), assistsM = c(1L, 1L, 2L, 2L, 7L, 0L, 2L, 2L, 3L, 0L, 4L, 1L, 0L, 1L, 1L, 1L, 4L, 1L, 3L, 3L), killParticipationM = c(0.151515151515152, 0.0909090909090909, 0.125, 0.3125, 0.464285714285714, 0, 0.157894736842105, 0.210526315789474, 0.222222222222222, 0.185185185185185, 0.434782608695652, 0.0869565217391304, 0.2, 0.25, 0.130434782608696, 0.304347826086957, 0.4, 0.16, 0.181818181818182, 0.227272727272727), firstTowerKillM = c(0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L)), row.names = c(NA, 20L), class = "data.frame")

对我来说,与测试数据的唯一区别似乎是变量的名称和观察值

【问题讨论】:

  • @IceCreamToucan 感谢您的建议,不幸的是它给了我以下错误消息:[.data.table(test, , :=(ma, shift(rollmeanr(values, : RHS 的类型) 中的错误) 'double') 必须匹配 LHS ('logical')。对于最快的情况,检查和强制会影响性能。要么更改目标列的类型,要么强制 := 自己的 RHS(例如,使用 1L 代替1)
  • 我承认我不知道该怎么做:/。你会如何改写: test[, ma := shift(rollmeanr(values, 4, na.pad = T)), category] ​​就其本身而言,“ma”是一个新的、不同于“values”的列,不是吗?
  • 我尝试了除“ma”之外的其他名称,并收到相同的错误消息。我会立即用 dput 更新主帖
  • 你在真实数据集上的“类别”变量是什么?如果您在类别变量的任何组中少于 2 个观察值,我提出的解决方案似乎会失败(可能是因为 rollmean 在 1 元素数组上给出 NULL)。
  • “类别”变量是“sumchamp”,但在 dput 中不存在。 Sumchamp 只是一个 6 位数的数字,我将其用作 ID。某些组的观察次数少于 2 次是 100% 正确的。要删除它们,我通常会先计算移动平均值,然后删除所有具有“NA”值的观测值。

标签: r data.table moving-average


【解决方案1】:

如何移动结果而不是输入值?像这样的东西(使用包zoo中的rollmean):

library(data.table)
library(zoo)
test = data.table("values" = c(1,2,3,4,5,6,7,8, 9,10,11,12), 
                  "category" = c(1,1,1,1,1,1,2,2,2,2,2,2))
test[, paste0("ravg_", c("values")) := shift(lapply(
  .SD, rollmean, k = 2, na.pad = TRUE, align = "right"), 1), 
  .SDcols = c("values"), by = category]

    values category ravg_values
 1:      1        1          NA
 2:      2        1          NA
 3:      3        1         1.5
 4:      4        1         2.5
 5:      5        1         3.5
 6:      6        1         4.5
 7:      7        2          NA
 8:      8        2          NA
 9:      9        2         7.5
10:     10        2         8.5
11:     11        2         9.5
12:     12        2        10.5

您还可以轻松地将其调整为多列(请参阅https://stackoverflow.com/a/31482551/6871135

【讨论】:

  • 我刚试过,不幸的是它给了我这个错误信息:[.data.table(test, , :=(paste0("ravg_", c("values")), 中的错误), : Type of RHS ('double') 必须与 LHS ('logical') 匹配。对于最快的情况,检查和强制对性能影响太大。要么更改目标列的类型,要么强制 := 自己的 RHS(例如,通过使用1L 而不是 1)
  • 我在测试数据集上没有看到错误。你能分享一个“真实”的子集吗?
  • 为方便起见,我只是在我的主帖中添加了一个 dput。
  • 从 1.12.0(或当前开发版本)开始,您可以使用 frollmean 而不是 zoo::rollmean,在使用多个变量时应该会显着加快速度,因为它将并行计算滚动平均值
  • lapply 是多余的。
【解决方案2】:

我认为您可以通过在用于计算平均值的函数中添加 shift 来加快速度,例如

mav_shift &lt;- function(x,n) if(length(x) &gt;= n)stats::filter(shift(x),rep(1/n,n), sides=1) else NA_real_

通过我的快速测试,这会略微增加运行函数的时间,并删除创建新变量的步骤。请进行测试以确保它按预期工作,但您的示例数据的结果似乎相同。

编辑和更快的解决方案:

mav_shift <- function(x,n) {
  if(length(x) >= n) { 
    stats::filter(shift(x),rep(1/n,n), sides=1) 
  } else NA_real_

result <- by(test$values, test$category, mav_shift, n=2, simplify=T)
test$new <- as.vector(unlist(result))

【讨论】:

  • 感谢您的建议!是的,它更快。现在计算一个变量的移动平均值需要 43 秒,而在创建新变量之前大约需要一分钟。移动平均线本身(没有进行 shift() 修改)花了 38 秒。不幸的是,为所有数据集中的所有变量运行大约 10 个小时的总数据预处理。你有什么让它更快的想法吗?
  • 你可以试试这个by(test$values, test$category, mav_shift, n=2, simplify=T)
  • 看起来棒极了,谢谢!但是如何在测试数据表中创建一个新变量呢?
  • result &lt;- by(test$values, test$category, mav_shift, n=2, simplify=T) test$new &lt;- as.vector(unlist(result))
  • by 太慢了
【解决方案3】:

您可以分别在data.table和zoo包中组合shiftrollmeanr函数,如下所示。

library(data.table)
library(zoo)
test = data.table(values = 1:12, category = rep(1:2, each = 6))
test[, mg2 := shift(rollmeanr(values, 2, fill = NA)), category]

   values category      mg2
1:      1        1       NA
2:      2        1       NA
3:      3        1      1.5
4:      4        1      2.5
5:      5        1      3.5
6:      6        1      4.5
7:      7        2       NA
8:      8        2       NA
9:      9        2      7.5
10:     10       2      8.5
11:     11       2      9.5
12:     12       2     10.5

【讨论】:

  • 此答案与 lbusett 答案相同,但在处理多个变量方面不太友好。
猜你喜欢
  • 1970-01-01
  • 2011-09-03
  • 2016-01-08
  • 2017-03-31
  • 1970-01-01
  • 2022-03-05
  • 1970-01-01
  • 2017-01-02
  • 2021-02-08
相关资源
最近更新 更多