Эффективное решение (рекурсивно) замены NA со средним значением лагов по группам - PullRequest
0 голосов
/ 22 ноября 2018

Мне нужно заменить NA средними значениями предыдущих трех значений по группам.После замены NA он будет служить входом для вычисления среднего значения, соответствующего следующему NA (если следующий NA будет в течение следующих трех месяцев).

Вот пример:

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")

Результат должен выглядеть следующим образом:

id   date   value
1 2017-04-01 40.00
1 2017-05-01 40.00
1 2017-06-01 10.00
1 2017-07-01 30.00
1 2017-08-01 26.66
2 2014-01-01 27.00
2 2014-02-01 13.00

, где 26,66 = (30 + 10 + 40) / 3

Какой эффективный способ сделать это (то есть избежать циклов)?

Ответы [ 2 ]

0 голосов
/ 22 ноября 2018

Определите функцию roll, которая принимает 3 или менее предыдущих значения в виде списка и текущего значения и возвращает в виде списка предыдущие 2 значения с текущим значением, если текущее значение не является NA, и предыдущие 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
0 голосов
/ 22 ноября 2018

Следующее использует только базу 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
...