【问题标题】:Replace NA with 0, only in numeric columns in data.table仅在 data.table 中的数字列中将 NA 替换为 0
【发布时间】:2016-09-20 08:50:30
【问题描述】:

我有一个包含不同数据类型列的 data.table。我的目标是仅选择数字列并将这些列中的 NA 值替换为 0。 我知道用零替换 na 值是这样的:

DT[is.na(DT)] <- 0

要仅选择数字列,我找到了这个解决方案,效果很好:

DT[, as.numeric(which(sapply(DT,is.numeric))), with = FALSE]

我可以通过分配来实现我想要的

DT2 <- DT[, as.numeric(which(sapply(DT,is.numeric))), with = FALSE]

然后做:

DT2[is.na(DT2)] <- 0

但我当然希望通过引用修改我的原始 DT。但是,使用以下内容:

DT[, as.numeric(which(sapply(DT,is.numeric))), with = FALSE]
                 [is.na(DT[, as.numeric(which(sapply(DT,is.numeric))), with = FALSE])]<- 0

我明白了

[.data.table([...] i 中的错误类型(矩阵)无效”

我错过了什么? 非常感谢任何帮助!

【问题讨论】:

  • 您缺少 data.tables 的基本语法,而 DT[...] &lt;- y 没有这些语法。尝试阅读小插曲github.com/Rdatatable/data.table/wiki/Getting-started 对于您认为需要采取的每个步骤,这是一种比“寻找解决方案”更有效的学习方式。下面的答案甚至不需要您找到的 with=FALSE 技巧。
  • 感谢您的建议。您能否详细说明基本语法错误“...不执行 DT[...]
  • 不应像DT[...] &lt;- y 那样使用数据表,其中... 是您的想法。使用:=set 完成分配,而不是使用&lt;-。箭头方式实际上在特殊情况下确实有效,因为表格被修改了,但它不能通过引用工作(最后我检查过),所以不是惯用的。要使用 data.tables,您必须学习他们的一些习语。如果你还不知道我所说的 := 是什么意思,那是查看这些小插曲的好理由。
  • a) 在顶部一次性计算列列表numeric_cols &lt;- which(sapply(DT,is.numeric)) 会更有效,而不是在每个 j 表达式中为每个组计算。 b) 然后只需引用DT[, numeric_cols] c) 是的,在 j 表达式中放置一个函数调用很棘手,并且经常会引起语法错误。

标签: r data.table numeric na


【解决方案1】:

我们可以使用set

for(j in seq_along(DT)){
    set(DT, i = which(is.na(DT[[j]]) & is.numeric(DT[[j]])), j = j, value = 0)
 }

或者为数字列创建一个索引,循环遍历它并将set NA 值设置为 0

ind <-   which(sapply(DT, is.numeric))
for(j in ind){
    set(DT, i = which(is.na(DT[[j]])), j = j, value = 0)
}

数据

set.seed(24)
DT <- data.table(v1= c(NA, 1:4), v2 = c(NA, LETTERS[1:4]), v3=c(rnorm(4), NA))

【讨论】:

  • set( ..., j = j, ...) 是什么意思?所有列?当然,我们只需要在数字列的子集上执行set(),正如 OP 所要求的那样?
  • @smci 并非所有列。在代码中,我得到了ind,它获取了numeric 列的列索引,因此,它只是循环遍历这些列
  • 好的。为什么不能避免循环,通过使用ind 索引到names(DT) 以获得列名的list 并将其作为set() 的j 参数传递?我猜想找到 NA 的表达式需要是 2D 的。好吧,我猜set() 已经相当快了。
  • @smci 不确定我不明白你的问题。 j 可以采用列名或列索引。这里,'ind' 是索引。
  • 为什么不能避免循环 for(j in ind) { ... set(..., j=j, ...) } ?你不能直接做set(DT, j=ind)吗?我认为你可以,但是 j 循环的唯一原因是 i 表达式为特定 j 查找 NA 行发生了变化。
【解决方案2】:

我想探索并可能改进@akrun 上面给出的出色答案。这是他在示例中使用的数据:

library(data.table)

set.seed(24)
DT <- data.table(v1= c(NA, 1:4), v2 = c(NA, LETTERS[1:4]), v3=c(rnorm(4), NA))
DT

#>    v1   v2         v3
#> 1: NA <NA> -0.5458808
#> 2:  1    A  0.5365853
#> 3:  2    B  0.4196231
#> 4:  3    C -0.5836272
#> 5:  4    D         NA

以及他建议使用的两种方法:

fun1 <- function(x){
  for(j in seq_along(x)){
  set(x, i = which(is.na(x[[j]]) & is.numeric(x[[j]])), j = j, value = 0)
  }
}

fun2 <- function(x){
  ind <-   which(sapply(x, is.numeric))
  for(j in ind){
    set(x, i = which(is.na(x[[j]])), j = j, value = 0)
  }
}

我认为上面的第一种方法真的很天才,因为它利用了 NA 被键入的事实。

首先,即使.SDi参数中不可用,也可以用get()拉取列名,所以我想我可以这样子分配data.table

fun3 <- function(x){
  nms <- names(x)[sapply(x, is.numeric)]
  for(j in nms){
    x[is.na(get(j)), (j):=0]
  }
}

一般情况下,当然是依靠.SD.SDcols 只对数字列起作用

fun4 <- function(x){
  nms <- names(x)[sapply(x, is.numeric)]
  x[, (nms):=lapply(.SD, function(i) replace(i, is.na(i), 0)), .SDcols=nms]  
}

但后来我对自己说:“嘿,谁说我们不能一直使用 base R 来进行这种操作。这是简单的 lapply() 和条件语句,包裹在 setDT()

fun5 <- function(x){
setDT(
  lapply(x, function(i){
    if(is.numeric(i))
         i[is.na(i)]<-0
    i
  })
)
}

最后,我们可以使用相同的条件思想来限制我们应用set()的列

fun6 <- function(x){
  for(j in seq_along(x)){
    if (is.numeric(x[[j]]) )
      set(x, i = which(is.na(x[[j]])), j = j, value = 0)
  }
}

以下是基准:

microbenchmark::microbenchmark(
  for.set.2cond = fun1(copy(DT)),
  for.set.ind = fun2(copy(DT)),
  for.get = fun3(copy(DT)),
  for.SDcol = fun4(copy(DT)),
  for.list = fun5(copy(DT)),
  for.set.if =fun6(copy(DT))
)

#> Unit: microseconds
#>           expr     min      lq     mean   median       uq      max neval cld
#>  for.set.2cond  59.812  67.599 131.6392  75.5620 114.6690 4561.597   100 a  
#>    for.set.ind  71.492  79.985 142.2814  87.0640 130.0650 4410.476   100 a  
#>        for.get 553.522 569.979 732.6097 581.3045 789.9365 7157.202   100   c
#>      for.SDcol 376.919 391.784 527.5202 398.3310 629.9675 5935.491   100  b 
#>       for.list  69.722  81.932 137.2275  87.7720 123.6935 3906.149   100 a  
#>     for.set.if  52.380  58.397 116.1909  65.1215  72.5535 4570.445   100 a  

【讨论】:

    【解决方案3】:

    您需要 tidyverse purrr 函数 map_ififelse 才能在一行代码中完成这项工作。

    library(tidyverse)
    set.seed(24)
    DT <- data.table(v1= sample(c(1:3,NA),20,replace = T), v2 = sample(c(LETTERS[1:3],NA),20,replace = T), v3=sample(c(1:3,NA),20,replace = T))
    

    下面的单行代码采用具有数字和非数字列的 DT 并仅对数字列进行操作以将 NA 替换为 0:

    DT %>% map_if(is.numeric,~ifelse(is.na(.x),0,.x)) %>% as.data.table
    

    因此,tidyverse 有时可能不如 data.table 冗长:-)

    【讨论】:

    • 请问为什么我的回答被否决了?没用吗?
    猜你喜欢
    • 2017-10-27
    • 2014-08-06
    • 2021-07-02
    • 2016-01-09
    • 2012-10-21
    • 2020-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多