如何加速R data.table中缺少的搜索过程

时间:2015-06-23 08:07:51

标签: 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,空白和Dot(NA,“。”,“”)作为缺失值?

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:8)

==%in%已优化为自动使用二进制搜索(NEW FEATURE:Auto indexing)。要使用它,我们必须确保:

a)我们使用dt[...]代替set(),因为它尚未在set()#1196中实施。

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

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

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中的表达式,该表达式使用自动索引来非常快速地创建索引并使用该索引使用二分搜索。

  

注意:如有必要,您可以在for循环结束时添加set2key(dt, NULL),以便在使用后立即删除创建的索引(以节省空间)。

与此次比赛相比,@ akrun最快的答案需要 6.33秒,这是加速的4.2倍。

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

拨打[.data.table 100次可能会很昂贵。在set()中实施自动编制索引时,可以很好地比较效果。

答案 1 :(得分:3)

你可以循环“错过”&#39;列并创建相应的标记&#39; set的列。

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