Если кто-то хочет заполнить отсутствующие значения переменной на основе предыдущего / апостериорного наблюдения, отличного от NA, в группе, команда data.table будет

setkey(DT,id,date)
DT[, value_filled_in := DT[!is.na(value), list(id, date, value)][DT[, list(id, date)], value, roll = TRUE]]

Что довольно сложно. Жаль, поскольку roll - очень быстрый и мощный вариант (особенно по сравнению с применением такой функции, как zoo::na.locf в каждой группе)

Я могу написать удобную функцию для заполнения недостающих значений

   fill_na <-  function(x , by = NULL, roll =TRUE , rollends= if (roll=="nearest") c(TRUE,TRUE)
             else if (roll>=0) c(FALSE,TRUE)
             else c(TRUE,FALSE)){
    id <- seq_along(x)
    if (is.null(by)){
      DT <- data.table("x" = x, "id" = id, key = "id") 
      return(DT[!is.na(x)][DT[, list(id)], x, roll = roll, rollends = rollends, allow.cartesian = TRUE])

    } else{
      DT <- data.table("x" = x, "by" = by, "id" = id, key = c("by", "id")) 
      return(DT[!is.na(x)][DT[, list(by, id)], x, roll = roll, rollends = rollends, allow.cartesian = TRUE])
    }
  }

А затем напишите

setkey(DT,id, date)
DT[, value_filled_in := fill_na(value, by = id)]

Это не совсем удовлетворительно, так как хотелось бы написать

setkey(DT,id, date)
DT[, value_filled_in := fill_na(value), by = id]

Однако на это уходит очень много времени. И для конечного пользователя сложно узнать, что fill_na следует вызывать с параметром by, а не использовать с data.table by. Есть ли здесь элегантное решение?

Некоторый тест скорости

N <- 2e6
set.seed(1)
DT <- data.table(
         date = sample(10, N, TRUE),
           id = sample(1e5, N, TRUE),   
        value = sample(c(NA,1:5), N, TRUE),
       value2 = sample(c(NA,1:5), N, TRUE)                   
      )
setkey(DT,id,date)
DT<- unique(DT)

system.time(DT[, filled0 := DT[!is.na(value), list(id, date, value)][DT[, list(id, date)], value, roll = TRUE]])
#> user  system elapsed 
#>  0.086   0.006   0.105 
system.time(DT[, filled1 := zoo::na.locf.default(value, na.rm = FALSE), by = id])
#> user  system elapsed 
#> 5.235   0.016   5.274 
# (lower speed and no built in option like roll=integer or roll=nearest, rollend, etc)
system.time(DT[, filled2 := fill_na(value, by = id)])
#>   user  system elapsed 
#>  0.194   0.019   0.221 
system.time(DT[, filled3 := fill_na(value), by = id])
#>    user  system elapsed 
#> 237.256   0.913 238.405 

Почему бы мне просто не использовать na.locf.default? Несмотря на то, что разница в скорости на самом деле не важна, та же проблема возникает для других типов команд data.table (тех, которые полагаются на слияние по переменной в "by") - стыдно систематически игнорировать их, чтобы получить более простой синтаксис. Еще мне очень нравятся все варианты рулонов.

19
Matthew 3 Окт 2014 в 05:13

2 ответа

Лучший ответ

Теперь существует собственный data.table способ заполнения пропущенных значений (начиная с 1.12.4).

Этот вопрос породил проблему на github, которая недавно была закрыта созданием функций nafill и setnafill. Теперь вы можете использовать

DT[, value_filled_in := nafill(value, type = "locf")]

Также возможно заполнить NA постоянным значением или следующим переносом наблюдения.

Одно отличие от подхода в вопросе заключается в том, что эти функции в настоящее время работают только с NA, а не с NaN, тогда как is.na является TRUE для NaN - это планируется исправить в следующем выпуске с помощью дополнительного аргумента.

Я не участвую в проекте, но я видел, что, хотя здесь есть ссылка на проблему с github, другой ссылки не было, поэтому я отвечаю от имени будущих посетителей.

Обновление: по умолчанию NaN теперь обрабатывается так же, как NA.

13
anotherfred 9 Мар 2021 в 16:44

Вот несколько более быстрый и компактный способ сделать это (версия 1.9.3+):

DT[, filled4 := DT[!is.na(value)][DT, value, roll = T]]
14
eddi 3 Окт 2014 в 19:16
Моя ошибка! Я думал, что это скопирует всю таблицу дважды (один раз через DT[!is.na(value)], другой через X[Y]), что было бы проблематично для типичного широкого набора данных. Не так ли (по крайней мере, для DT[!is.na(value)])?
 – 
Matthew
3 Окт 2014 в 20:57
1
Хорошо. Подмножество столбцов в Y ничего не меняет. Однако кажется, что (DT[, filled4 := DT[!is.na(value), list(date,id,value)][DT, value, roll = T]] быстрее, чем ваш ответ в широкой базе данных
 – 
Matthew
3 Окт 2014 в 21:03
1
Привет @эдди. Когда я пробую ваш ответ с data.table 1.12.3 и образцами данных OP, возникают ошибки (Error in vecseq(f__, len__, if (allow.cartesian || notjoin || !anyDuplicated(f__, :). Ты знаешь почему? Ваше здоровье
 – 
Henrik
19 Апр 2019 в 22:05