按组用递归平均值替换(递归)NA的有效解决方案

时间:2018-11-22 15:56:14

标签: r dplyr apply

我需要按组用前三个值的平均值替换NA。 替换NA后,它将用作计算对应于下一个NA的均值的输入(如果下一个NA在未来三个月之内)。

这里是一个例子:

# The 9 comes from tha fact that x is in [0:20], log10(y) is in [0, 180]. The factor of 0.6 is roughly the aspect ratio of the main plot shape.
plt.gca().text(x, y, r'A', rotation_mode='anchor', rotation=np.rad2deg(np.arctan(0.6 * x/9.0)), horizontalalignment='center')

数据:

id date value 1 2017-04-01 40 1 2017-05-01 40 1 2017-06-01 10 1 2017-07-01 NA 1 2017-08-01 NA 2 2014-01-01 27 2 2014-02-01 13

输出应如下所示:

dt <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 2L, 2L), date = structure(c(17257, 17287, 17318, 17348, 17379, 16071, 16102), class = "Date"), value = c(40, 40, 10, NA, NA, 27, 13)), row.names = c(1L, 2L, 3L, 4L, 5L, 8L, 9L), class = "data.frame")

其中26.66 =(30 + 10 + 40)/ 3

执行此操作的有效方法是什么(即避免for循环)?

2 个答案:

答案 0 :(得分:1)

以下仅使用基数R,并且可以满足您的需求。

sp <- split(dt, dt$id)
sp <- lapply(sp, function(DF){
  for(i in which(is.na(DF$value))){
    tmp <- DF[seq_len(i - 1), ]
    DF$value[i] <- mean(tail(tmp$value, 3))
  }
  DF
})

result <- do.call(rbind, sp)
row.names(result) <- NULL

result
#  id       date    value
#1  1 2017-01-04 40.00000
#2  1 2017-01-05 40.00000
#3  1 2017-01-06 10.00000
#4  1 2017-01-07 30.00000
#5  1 2017-01-08 26.66667
#6  2 2014-01-01 27.00000
#7  2 2014-01-02 13.00000

答案 1 :(得分:1)

定义一个roll函数,该函数将3个或更少的先前值作为列表和当前值,如果当前值不是NA且先前的2个值作为列表返回前2个值和当前值如果当前值为NA,则取平均值。将其与Reduce一起使用,并选择结果中每个列表的最后一个值。然后使用ave将所有内容应用于每个组。

roll <- function(prev, cur) {
  prev <- unlist(prev)
  list(tail(prev, 2), if (is.na(cur)) mean(prev) else cur)
}

reduce_roll <- function(x) {
  sapply(Reduce(roll, init = x[1], x[-1], acc = TRUE), tail, 1)
}

transform(dt, value = ave(value, id, FUN = reduce_roll))

给予:

  id       date    value
1  1 2017-04-01       40
2  1 2017-05-01       40
3  1 2017-06-01       10
4  1 2017-07-01       30
5  1 2017-08-01 26.66667
8  2 2014-01-01       27
9  2 2014-02-01       13