在data.table中按组填充缺失值

22

如果想要根据同一组中前/后面的非缺失观测值来填补一个变量的缺失值,可以使用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.tableby 一起使用,这显得繁琐。是否有更优雅的解决方案?

一些速度测试

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"中变量进行合并的命令)也会出现同样的问题- 为了获得更简单的语法而系统地忽略它们是很遗憾的。我也非常喜欢所有的roll选项。


2
从速度的角度来看,“na.locf”解决方案与该解决方案相比如何? - GSee
@ssdecontrol 你是什么意思? - Matthew
2
如果你在 zoo 库中去掉 :: 的调用,在我的情况下会快约 30%。也就是说,使用 na.locf.default 而不是 zoo::na.locf.default - GSee
3
因为::是一个函数,使用它会增加额外的函数调用开销。 - GSee
@Matthew,如果能直接在data.table中处理缺失值就太好了。你能否提交一个问题报告?只需将其链接回这个SO帖子即可。 - Arun
显示剩余4条评论
2个回答

16
现在有一种本地的data.table方法来填充缺失值(自1.12.4起)。
这个问题引发了一个github问题,最近通过创建函数nafillsetnafill而关闭。现在你可以使用它们了。
DT[, value_filled_in := nafill(value, type = "locf")]

也可以使用常量值或下一个观察值填充NA

与问题中的方法不同之处在于,这些函数目前仅适用于NA而不是NaN,而is.na对于NaNTRUE - 这个问题计划在下一个版本中通过额外的参数进行修复。

我与项目无关,但我看到虽然github问题链接在这里,但没有另一种方式的链接,所以我代表未来的访问者回答。

更新:默认情况下,NaN现在被视为与NA相同。


14

以下是稍微更快、更紧凑的方法(版本1.9.3+):

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

我的错误!我以为这会将整个表格复制两次(一次通过DT[!is.na(value)],另一次通过X[Y]),这对于典型的宽数据集来说是有问题的。但实际上并非如此(至少对于DT[!is.na(value)]来说不是这样)? - Matthew
1
好的。在Y中对列进行子集操作不会改变任何内容。然而,似乎(DT[, filled4 := DT[!is.na(value), list(date,id,value)][DT, value, roll = T]]在宽数据库中比你的答案更快。 - Matthew
1
嗨@eddi。当我尝试使用data.table 1.12.3和OP的示例数据运行您的答案时,它会出现错误(Error in vecseq(f__, len__, if (allow.cartesian || notjoin || !anyDuplicated(f__, :)。你知道为什么吗?干杯 - Henrik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接