如何通过指定的分组计算移动平均值并处理NA

时间:2019-02-17 21:15:57

标签: r data.table zoo moving-average rollapply

我有一个data.table,它需要针对指定的分组(ID1,ID2)在前n天的数据(为简单起见,我们使用n = 2,而不是包括当天)来计算移动平均值。移动平均值应尝试包括每个ID1-ID2对的值的最后2天。我想计算移动平均线以两种方式处理NA: 1.仅在有2个非NA观测值时进行计算,否则平均值应为NA(例如,ID1-ID2内的前2天将始终具有NA)。 2.根据最近2天内的所有非NA观测值(na.rm = TRUE吗?)计算移动平均值。

我尝试使用zoo软件包及其中的各种功能。我已经确定了以下内容(使用shift()排除了平均值中考虑的星期,以相反的顺序显示日期以突出显示最初并不总是按日期排序):

library(zoo)
library(data.table)
DATE = rev(rep(seq(as.Date("2018-01-01"),as.Date("2018-01-04"),"day"),4))
VALUE =seq(1,16,1)
VALUE[16] <- NA
ID1 = rep(c("A","B"),each=8)
ID2 = rep(1:2,2,each=4)
testdata = data.frame (DATE, ID1, ID2, VALUE)
setDT(testdata)[order(DATE), VALUE_AVG := shift(rollapplyr(VALUE, 2, mean, 
na.rm=TRUE,fill = NA)), by = c("ID1", "ID2")]

我似乎很难按多列进行分组。 VALUE以NA值开头/结尾的分组似乎也引起了问题。我对在data.table框架内有意义的任何解决方案持开放态度,尤其是frollmean(需要更新我的R + data.table版本)。我不知道是否需要结合指定的对齐方式(例如“右”)对日期进行不同的排序。

我希望我的输出看起来像以下内容,但按照ID1-ID2分组的最旧日期优先排序:

           DATE ID1 ID2 VALUE VALUE_AVG
 1: 2018-01-04   A   1     1       2.5
 2: 2018-01-03   A   1     2       3.5
 3: 2018-01-02   A   1     3        NA
 4: 2018-01-01   A   1     4        NA
 5: 2018-01-04   A   2     5       6.5
 6: 2018-01-03   A   2     6       7.5
 7: 2018-01-02   A   2     7        NA
 8: 2018-01-01   A   2     8        NA
 9: 2018-01-04   B   1     9      10.5
10: 2018-01-03   B   1    10      11.5
11: 2018-01-02   B   1    11        NA
12: 2018-01-01   B   1    12        NA
13: 2018-01-04   B   2    13      14.5
14: 2018-01-03   B   2    14      15.0
15: 2018-01-02   B   2    15        NA
16: 2018-01-01   B   2    NA        NA

我的代码似乎可以大致实现示例数据的预期结果。但是,当尝试在大型数据集上平均运行4周(其中ID1和ID2都是整数)时,出现以下错误:

Error in seq.default(start.at, NROW(data), by = by) : 
  wrong sign in 'by' argument

对于大多数ID1-ID2组合,我的结果似乎正确,但是在ID1的某些特定情况下,VALUE具有前导NA和尾随NA。我猜这是造成问题的原因,尽管上面的示例没有。

2 个答案:

答案 0 :(得分:1)

使用shift会使此操作不必要地复杂。 rollapply已经可以自行处理。在rollapplyr中指定:

  • 宽度list(-seq(2)),用于指定它应在偏移量-1和-2上起作用。

  • partial = TRUE表示如果前面的行少于2行,它将使用其中的任何行。

  • fill = NA用NA填充空白单元格

  • na.rm = TRUE删除所有NA,仅对剩余单元格执行均值。如果先前的单元格全都是NA,则表示给出NaN。

仅考虑存在2个先前给出NA的非NA的情况,否则请删除partial = TRUEna.rm = TRUE参数。

第一种情况

取前2行中非NA的平均值,如果前行较少,则取较少的行。

testdata <- data.table(DATE, ID1, ID2, VALUE, key = c("ID1", "ID2", "DATE"))
testdata[, VALUE_AVG := 
  rollapplyr(VALUE, list(-seq(2)), mean, fill = NA, partial = TRUE, na.rm = TRUE),
  by = c("ID1", "ID2")]
testdata

给予:

          DATE ID1 ID2 VALUE VALUE_AVG
 1: 2018-01-01   A   1     4        NA
 2: 2018-01-02   A   1     3       4.0
 3: 2018-01-03   A   1     2       3.5
 4: 2018-01-04   A   1     1       2.5
 5: 2018-01-01   A   2     8        NA
 6: 2018-01-02   A   2     7       8.0
 7: 2018-01-03   A   2     6       7.5
 8: 2018-01-04   A   2     5       6.5
 9: 2018-01-01   B   1    12        NA
10: 2018-01-02   B   1    11      12.0
11: 2018-01-03   B   1    10      11.5
12: 2018-01-04   B   1     9      10.5
13: 2018-01-01   B   2    NA        NA
14: 2018-01-02   B   2    15       NaN
15: 2018-01-03   B   2    14      15.0
16: 2018-01-04   B   2    13      14.5

第二种情况

如果前2行中的任何一行为NA或前行少于2行,则为NA。

testdata <- data.table(DATE, ID1, ID2, VALUE, key = c("ID1", "ID2", "DATE"))
testdata[, VALUE_AVG := 
  rollapplyr(VALUE, list(-seq(2)), mean, fill = NA),
  by = c("ID1", "ID2")]
testdata

给予:

          DATE ID1 ID2 VALUE VALUE_AVG
 1: 2018-01-01   A   1     4        NA
 2: 2018-01-02   A   1     3        NA
 3: 2018-01-03   A   1     2       3.5
 4: 2018-01-04   A   1     1       2.5
 5: 2018-01-01   A   2     8        NA
 6: 2018-01-02   A   2     7        NA
 7: 2018-01-03   A   2     6       7.5
 8: 2018-01-04   A   2     5       6.5
 9: 2018-01-01   B   1    12        NA
10: 2018-01-02   B   1    11        NA
11: 2018-01-03   B   1    10      11.5
12: 2018-01-04   B   1     9      10.5
13: 2018-01-01   B   2    NA        NA
14: 2018-01-02   B   2    15        NA
15: 2018-01-03   B   2    14        NA
16: 2018-01-04   B   2    13      14.5

答案 1 :(得分:0)

Maybe something like:

setorder(setDT(testdata), ID1, ID2, DATE)
testdata[order(DATE), VALUE_AVG := shift(
        rollapplyr(VALUE, 2L, function(x) if(sum(!is.na(x)) > 0L) mean(x, na.rm=TRUE), fill = NA_real_)
    ), by = c("ID1", "ID2")]