Completa los valores faltantes por grupo en data.table

Si uno desea completar los valores perdidos de una variable en función de la observación anterior / posterior que no sea NA dentro de un grupo, el comando data.table es

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

que es bastante complejo. Es una pena, ya que el roll es una opción muy rápida y poderosa (especialmente en comparación con la aplicación de una función como zoo::na.locf dentro de cada grupo)

Puedo escribir una función de conveniencia para completar los valores perdidos

  fill_na =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]) } } 

Y luego escribe

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

Esto no es realmente satisfactorio ya que a uno le gustaría escribir

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

Sin embargo, esto toma una gran cantidad de tiempo para ejecutarse. Y, para el usuario final, es engorroso saber que fill_na debe llamarse con la opción by y no debe usarse con data.table by . ¿Hay una solución elegante alrededor de esto?

Alguna prueba de velocidad

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

¿Por qué no uso na.locf.default ? Aunque la diferencia de velocidad no es realmente importante, surge el mismo problema para otros tipos de comandos de datos.table (aquellos que dependen de una fusión por la variable en “por”) – es una pena ignorarlos sistemáticamente para obtener un syntax más fácil. También me gustan todas las opciones de rollo.

Aquí hay una forma más rápida y compacta de hacerlo (versión 1.9.3+):

 DT[, filled4 := DT[!is.na(value)][DT, value, roll = T]]