如何加快 R data.table 中丢失的搜索过程

How to speed up missing search process in a R data.table

我正在编写一个用于缺失值处理的通用函数。数据可以有 Char、numeric、factor 和 integer 类型的列。数据示例如下

dt<-data.table(
  num1=c(1,2,3,4,NA,5,NA,6),
  num3=c(1,2,3,4,5,6,7,8),
  int1=as.integer(c(NA,NA,102,105,NA,300,400,700)),
  int3=as.integer(c(1,10,102,105,200,300,400,700)),
  cha1=c('a','b','c',NA,NA,'c','d','e'),
  cha3=c('xcda','b','c','miss','no','c','dfg','e'),
  fact1=c('a','b','c',NA,NA,'c','d','e'),
  fact3=c('ad','bd','cc','zz','yy','cc','dd','ed'),
  allm=as.integer(c(NA,NA,NA,NA,NA,NA,NA,NA)),
  miss=as.character(c("","",'c','miss','no','c','dfg','e')),
  miss2=as.integer(c('','',3,4,5,6,7,8)),
  miss3=as.factor(c(".",".",".","c","d","e","f","g")),
  miss4=as.factor(c(NA,NA,'.','.','','','t1','t2')),
  miss5=as.character(c(NA,NA,'.','.','','','t1','t2'))  
)

我使用这段代码来标记缺失值:

dt[,flag:=ifelse(is.na(miss5)|!nzchar(miss5),1,0)]

但结果很慢,另外我必须添加逻辑也可以考虑“。”作为失踪。 所以我打算写这个用于缺失值识别

dt[miss5 %in% c(NA,'','.'),flag:=1]

但是在 600 万条记录集上,运行 需要将近 1 秒,而

dt[!nzchar(miss5),flag:=1]  takes close 0.14 secod to run.

我的问题是,我们能否有一个代码,在其中花费的时间尽可能少,同时我们可以查找值 NA、blank 和 Dot(NA,".","") 作为缺失值?

非常感谢任何帮助。

您可以遍历 'miss' 列并使用 set 创建相应的 'flag' 列。

library(data.table)#v1.9.5+
ind <- grep('^miss', names(dt))
nm1 <- sub('miss', 'flag',names(dt)[ind])
dt[,(nm1) := 0]
for(j in seq_along(ind)){
     set(dt, i=which(dt[[ind[j]]] %in% c('.', '', NA)),j= nm1[j], value=1L)
  }

基准

set.seed(24)
df1 <- as.data.frame(matrix(sample(c(NA,0:9), 6e6*5, replace=TRUE), ncol=5))
set.seed(23)
df2 <- as.data.frame(matrix(sample(c('.','', letters[1:5]), 6e6*5,
   replace=TRUE), ncol=5))
set.seed(234)
i1 <- sample(10)
dfN <- setNames(cbind(df1, df2)[i1], paste0('miss',1:10))
dt <- as.data.table(dfN)

system.time({
 ind <- grep('^miss', names(dt))
 nm1 <- sub('miss', 'flag',names(dt)[ind])
 dt[,(nm1) := 0L]
 for(j in seq_along(ind)){
  set(dt, i=which(dt[[ind[j]]] %in% c('.', '', NA)), j= nm1[j], value=1L)
  }
 }
)
#user  system elapsed 
#  8.352   0.150   8.496 

system.time({
 m1 <- matrix(0, nrow=6e6, ncol=10)
 m2 <- sapply(seq_along(dt), function(i) {
   ind <- which(dt[[i]] %in% c('.', '', NA))
    replace(m1[,i], ind, 1L)})
  cbind(dt, m2)})
 #user  system elapsed 
 # 14.227   0.362  14.582   

==%in% 已优化为自动使用二进制搜索(新功能:自动索引)。要使用它,我们必须确保:

a) 我们使用 dt[...] 而不是 set(),因为它尚未在 set()#1196.

中实现

b) 当 RHS 到 %in% 的 SEXPTYPE 比 LHS 高时,自动索引重新路由到基 R 以确保正确的结果(因为二进制搜索总是强制 RHS)。因此,对于整数列,我们需要确保只传入 NA 而不是 ".""".

使用@ak运行的数据,这里是代码和运行时间:

in_col = grep("^miss", names(dt), value=TRUE)
out_col = gsub("^miss", "flag", in_col)
system.time({
    dt[, (out_col) := 0L]
    for (j in seq_along(in_col)) {
        if (class(.subset2(dt, in_col[j])) %in% c("character", "factor")) {
            lookup = c("", ".", NA)
        } else lookup = NA
        expr = call("%in%", as.name(in_col[j]), lookup)
        tt = dt[eval(expr), (out_col[j]) := 1L]
    }
})
#    user  system elapsed 
#   1.174   0.295   1.476 

工作原理:

a) 我们首先将所有输出列初始化为 0。

b) 然后,对于每一列,我们检查它的类型并相应地创建 lookup

c) 然后我们为 i - miss(.) %in% lookup

创建相应的表达式

d) 然后我们评估 i 中的表达式,它将使用 自动索引 非常快速地创建索引并使用该索引快速找到匹配的索引使用 二进制搜索.

Note: If necessary, you can add a set2key(dt, NULL) at the end of for-loop so that the created indices are removed immediately after use (to save space).

与此 运行 相比,@ak运行 的最快答案需要 6.33 秒,即 ~4.2 倍加速。

更新: 在 400 万行和 100 列上,大约需要 9.2 秒。每列约 0.092 秒。

调用 [.data.table 100 次可能会很昂贵。 set()实现自动索引后,比较一下性能就好了。