在大型data.table中取代NAs的最快方法

我有一个很大的data.table ,许多缺失的值分散在整个〜200K行和200列。 我想尽可能有效地将这些NA值重新编码为零。

我看到两个选项:
1:转换为data.frame,并使用这样的东西
2:一些很酷的data.table子设置命令

我会很满意types1的一个相当有效的解决scheme。转换为data.frame,然后返回到data.table不会太长。

这里有一个使用data.table的解决scheme:= operator,build立在Andrie和Ramnath的答案之上。

 require(data.table) # v1.6.6 require(gdata) # v2.8.2 set.seed(1) dt1 = create_dt(2e5, 200, 0.1) dim(dt1) [1] 200000 200 # more columns than Ramnath's answer which had 5 not 200 f_andrie = function(dt) remove_na(dt) f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un) f_dowle = function(dt) { # see EDIT later for more elegant solution na.replace = function(v,value=0) { v[is.na(v)] = value; v } for (i in names(dt)) eval(parse(text=paste("dt[,",i,":=na.replace(",i,")]"))) } system.time(a_gdata = f_gdata(dt1)) user system elapsed 18.805 12.301 134.985 system.time(a_andrie = f_andrie(dt1)) Error: cannot allocate vector of size 305.2 Mb Timing stopped at: 14.541 7.764 68.285 system.time(f_dowle(dt1)) user system elapsed 7.452 4.144 19.590 # EDIT has faster than this identical(a_gdata, dt1) [1] TRUE 

请注意,f_dowle通过引用更新了dt1。 如果需要本地副本,则需要对copy函数进行显式调用以创build整个数据集的本地副本。 data.table的setkeykey<-:=不写时拷贝。

接下来,让我们看看f_dowle花费的时间。

 Rprof() f_dowle(dt1) Rprof(NULL) summaryRprof() $by.self self.time self.pct total.time total.pct "na.replace" 5.10 49.71 6.62 64.52 "[.data.table" 2.48 24.17 9.86 96.10 "is.na" 1.52 14.81 1.52 14.81 "gc" 0.22 2.14 0.22 2.14 "unique" 0.14 1.36 0.16 1.56 ... snip ... 

在那里,我会专注于na.replaceis.na ,那里有几个vector副本和vector扫描。 这些可以通过编写一个小的na.replace C函数很容易地消除,该函数通过向量中的引用来更新NA 。 那至less将我认为的20秒减半。 R包中是否有这样的function?

f_andrie失败的原因可能是因为它复制了整个dt1 ,或者几次创build一个与整个dt1一样大的逻辑matrix。 其他两种方法一次只能在一列上工作(尽pipe我只是简单地看了一下NAToUnknown )。

编辑 (Ramnath在评论中要求的更优雅的解决scheme):

 f_dowle2 = function(DT) { for (i in names(DT)) DT[is.na(get(i)), (i):=0] } system.time(f_dowle2(dt1)) user system elapsed 6.468 0.760 7.250 # faster, too identical(a_gdata, dt1) [1] TRUE 

我希望我这样做,开始!

EDIT2 (超过1年后,现在)

还有set() 。 如果有很多列循环,这可以更快,因为它避免了在循环中调用[,:=,]的(小)开销。 set是一个loopable := 。 看到?set

 f_dowle3 = function(DT) { # either of the following for loops # by name : for (j in names(DT)) set(DT,which(is.na(DT[[j]])),j,0) # or by number (slightly faster than by name) : for (j in seq_len(ncol(DT))) set(DT,which(is.na(DT[[j]])),j,0) } 

这是一个在gdata包中使用NAToUnknown的解决scheme。 我使用了Andrie的解决scheme来创build一个庞大的数据表,并且还包括与Andrie的解决scheme进行时间比较。

 # CREATE DATA TABLE dt1 = create_dt(2e5, 200, 0.1) # FUNCTIONS TO SET NA TO ZERO f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un) f_Andrie = function(dt) remove_na(dt) # COMPARE SOLUTIONS AND TIMES system.time(a_gdata <- f_gdata(dt1)) user system elapsed 4.224 2.962 7.388 system.time(a_andrie <- f_Andrie(dt1)) user system elapsed 4.635 4.730 20.060 identical(a_gdata, g_andrie) TRUE 

这是我能想到的最简单的一个:

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

这是有效的,不需要编写function和其他胶水代码。

为了完整起见,用0replaceNAs的另一种方法是使用

 f_rep <- function(dt) { dt[is.na(dt)] <- 0 return(dt) } 

为了比较结果和时间,我已经采用了迄今为止提到的所有方法。

 set.seed(1) dt1 <- create_dt(2e5, 200, 0.1) dt2 <- dt1 dt3 <- dt1 system.time(res1 <- f_gdata(dt1)) User System verstrichen 3.62 0.22 3.84 system.time(res2 <- f_andrie(dt1)) User System verstrichen 2.95 0.33 3.28 system.time(f_dowle2(dt2)) User System verstrichen 0.78 0.00 0.78 system.time(f_dowle3(dt3)) User System verstrichen 0.17 0.00 0.17 system.time(res3 <- f_unknown(dt1)) User System verstrichen 6.71 0.84 7.55 system.time(res4 <- f_rep(dt1)) User System verstrichen 0.32 0.00 0.32 identical(res1, res2) & identical(res2, res3) & identical(res3, res4) & identical(res4, dt2) & identical(dt2, dt3) [1] TRUE 

所以新方法比f_dowle3但比所有其他方法更快。 但说实话,这是对我的data.table语法的直觉,我不知道为什么这个工作。 有人可以启发我吗?

 library(data.table) DT = data.table(a=c(1,"A",NA),b=c(4,NA,"B")) DT ab 1: 1 4 2: A NA 3: NA B DT[,lapply(.SD,function(x){ifelse(is.na(x),0,x)})] ab 1: 1 4 2: A 0 3: 0 B 

仅供参考,与gdata或data.matrix相比较慢,但仅使用data.table包,可处理非数字条目。

我的理解是R中快速操作的秘诀就是利用vector(或者数组,这是引擎盖下的vector)。

在这个解决scheme中,我使用了一个data.matrix ,它是一个array但其行为有点像data.frame 。 因为它是一个数组,所以可以使用一个非常简单的vectorreplace来代替NA

一个辅助函数来移除NA 。 本质是一行代码。 我只是这样做来衡量执行时间。

 remove_na <- function(x){ dm <- data.matrix(x) dm[is.na(dm)] <- 0 data.table(dm) } 

一个小帮助函数来创build给定大小的data.table

 create_dt <- function(nrow=5, ncol=5, propNA = 0.5){ v <- runif(nrow * ncol) v[sample(seq_len(nrow*ncol), propNA * nrow*ncol)] <- NA data.table(matrix(v, ncol=ncol)) } 

在一个小样本上演示:

 library(data.table) set.seed(1) dt <- create_dt(5, 5, 0.5) dt V1 V2 V3 V4 V5 [1,] NA 0.8983897 NA 0.4976992 0.9347052 [2,] 0.3721239 0.9446753 NA 0.7176185 0.2121425 [3,] 0.5728534 NA 0.6870228 0.9919061 NA [4,] NA NA NA NA 0.1255551 [5,] 0.2016819 NA 0.7698414 NA NA remove_na(dt) V1 V2 V3 V4 V5 [1,] 0.0000000 0.8983897 0.0000000 0.4976992 0.9347052 [2,] 0.3721239 0.9446753 0.0000000 0.7176185 0.2121425 [3,] 0.5728534 0.0000000 0.6870228 0.9919061 0.0000000 [4,] 0.0000000 0.0000000 0.0000000 0.0000000 0.1255551 [5,] 0.2016819 0.0000000 0.7698414 0.0000000 0.0000000 
 > DT = data.table(a=LETTERS[c(1,1:3,4:7)],b=sample(c(15,51,NA,12,21),8,T),key="a") > DT ab 1: A 12 2: A NA 3: B 15 4: C NA 5: D 51 6: E NA 7: F 15 8: G 51 > DT[is.na(b),b:=0] > DT ab 1: A 12 2: A 0 3: B 15 4: C 0 5: D 51 6: E 0 7: F 15 8: G 51 >