【问题标题】:Fill in missing values by group in data.table在data.table中按组填写缺失值
【发布时间】:2014-10-03 01:13:56
【问题描述】:

如果要根据组内的先前/后验非 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”中的变量合并的命令)也会出现同样的问题 - 系统地忽略它们以获得更简单的语法。我也很喜欢所有的滚动选项。

【问题讨论】:

  • na.locf 解决方案在速度方面与此解决方案相比如何?
  • 包装整个东西(比如dplyr::mutate)不是一个选项吗?
  • 如果您向create a sample data.table 提供代码,我们可以用来检查我们的结果并帮助进行基准测试,这将会很有帮助。
  • 如果你摆脱了动物园一号的:: 电话,对我来说它快了大约 30%。即调用na.locf.default 而不是zoo::na.locf.default
  • @ssdecontrol 因为:: 是一个函数,并且有与该额外函数调用相关的开销。

标签: r data.table


【解决方案1】:

这是一种稍微快一点、更紧凑的方法(1.9.3+ 版):

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

【讨论】:

  • 我的错!我认为这会复制整个表两次(一次通过DT[!is.na(value)],另一次通过X[Y]),这对于典型的宽数据集来说是有问题的。不是这样吗(至少对于DT[!is.na(value)])?
  • 好的。 Y 中的子集列不会改变任何内容。但是,似乎(DT[, filled4 := DT[!is.na(value), list(date,id,value)][DT, value, roll = T]] 比您在广泛数据库中的答案更快
  • 嗨@eddi。当我用 data.table 1.12.3 和 OP 的示例数据尝试你的答案时,它会出错 (Error in vecseq(f__, len__, if (allow.cartesian || notjoin || !anyDuplicated(f__, :)。你知道为什么吗?干杯
【解决方案2】:

现在有一种原生的data.table 填充缺失值的方法(截至1.12.4)。

这个问题催生了一个 github issue,它最近因创建函数 nafillsetnafill 而关闭。您现在可以使用

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

也可以用一个常数值或下一个观察结果来填充NA

与问题中的方法的一个不同之处在于,这些功能目前仅适用于 NA 而不是 NaNis.naTRUE 对于 NaN - 这是 planned 将在下一个修复通过额外的参数释放。

我没有参与该项目,但我看到虽然 github issue 链接在这里,但没有其他链接,所以我代表未来的访问者回答。

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

【讨论】:

    猜你喜欢
    • 2018-07-31
    • 1970-01-01
    • 2020-11-14
    • 2018-07-15
    • 2020-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多