使用!= <某些非NA>子集化data.table也不包括NA

我有一个数据表。 我想删除那些列的特定值的行(这恰好是"" )。 但是,我的第一次尝试也导致我失去了NA的行:

 > a = c(1,"",NA) > x <- data.table(a);x a 1: 1 2: 3: NA > y <- x[a!=""];y a 1: 1 

看完了?`!=` ,我发现了一个可以工作的class轮,但是这很痛苦:

 > z <- x[!sapply(a,function(x)identical(x,""))]; z a 1: 1 2: NA 

我想知道是否有更好的方法来做到这一点? 另外,我没有看到扩展这个排除多个非NA值的好方法。 这是一个坏方法:

 > drop_these <- function(these,where){ + argh <- !sapply(where, + function(x)unlist(lapply(as.list(these),function(this)identical(x,this))) + ) + if (is.matrix(argh)){argh <- apply(argh,2,all)} + return(argh) + } > x[drop_these("",a)] a 1: 1 2: NA > x[drop_these(c(1,""),a)] a 1: NA 

我看着?J ,用data.frame试了一下,看起来工作方式不同,在子集化时保持NA

 > w <- data.frame(a,stringsAsFactors=F); w a 1 1 2 3 <NA> > d <- w[a!="",,drop=F]; d a 1 1 NA <NA> 

为您的问题提供解决scheme:

你应该使用%in% 。 它给你一个逻辑向量。

 a %in% "" # [1] FALSE TRUE FALSE x[!a %in% ""] # a # 1: 1 # 2: NA 

要找出data.table发生这种情况的data.table

(与data.frame data.frame

如果查看data.table文件中的data.table源代码,在函数"[.data.table" ,有一组if-statements来检查i参数。 其中之一是:

 if (!missing(i)) { # Part (1) isub = substitute(i) # Part (2) if (is.call(isub) && isub[[1L]] == as.name("!")) { notjoin = TRUE if (!missingnomatch) stop("not-join '!' prefix is present on i but nomatch is provided. Please remove nomatch."); nomatch = 0L isub = isub[[2L]] } ..... # "isub" is being evaluated using "eval" to result in a logical vector # Part 3 if (is.logical(i)) { # see DT[NA] thread re recycling of NA logical if (identical(i,NA)) i = NA_integer_ # avoids DT[!is.na(ColA) & !is.na(ColB) & ColA==ColB], just DT[ColA==ColB] else i[is.na(i)] = FALSE } .... } 

为了解释这个差异,我在这里粘贴了一些重要的代码。 我也把它们分成三部分

首先,为什么dt[a != ""]不能按预期的方式工作(通过OP)?

首先, part 1评估为类call的对象。 第二部分if语句的第二部分返回FALSE。 之后,这个call被“评估”给出c(TRUE, FALSE, NA) 。 然后执行part 3 。 所以, NA被replace为FALSE (逻辑循环的最后一行)。

为什么x[!(a== "")]按预期工作(由OP)?

part 1再次返回一个调用 。 但是, part 2评估为TRUE,因此设置:

 1) `notjoin = TRUE` 2) isub <- isub[[2L]] # which is equal to (a == "") without the ! (exclamation) 

那是魔术发生的地方。 目前,否定已被删除。 请记住,这仍然是课堂呼叫的对象。 所以这得到评估(使用eval )逻辑再次。 所以, (a=="")计算为c(FALSE, TRUE, NA)

现在,在part 3检查is.logical 。 所以,在这里, NA被replace为FALSE 。 因此它变成c(FALSE, TRUE, FALSE) 。 在某个点之后,执行which(c(F,T,F))一个which(c(F,T,F)) ,结果是2。 因为notjoin = TRUE (从part 2seq_len(nrow(x))[-2] = c(1,3)。 所以, x[!(a=="")]基本上返回所需结果x[c(1,3)] 。 以下是相关的代码片段:

 if (notjoin) { if (bywithoutby || !is.integer(irows) || is.na(nomatch)) stop("Internal error: notjoin but bywithoutby or !integer or nomatch==NA") irows = irows[irows!=0L] # WHERE MAGIC HAPPENS (returns c(1,3)) i = irows = if (length(irows)) seq_len(nrow(x))[-irows] else NULL # NULL meaning all rows ie seq_len(nrow(x)) # Doing this once here, helps speed later when repeatedly subsetting each column. R's [irows] would do this for each # column when irows contains negatives. } 

考虑到这一点,我认为在语法上有一些不一致之处。如果我有时间来解决这个问题,那么我很快就会写一篇文章。

正如你已经知道的那样,这是原因:

 a != "" #[1] TRUE NA FALSE 

你可以做你已经知道的,即x[is.na(a) | a != ""] x[is.na(a) | a != ""]或者您可以在aa并执行以下操作:

 setkey(x, a) x[!J("")] 

马修的背景答案:

这个问题突出显示了!= NANA上的行为并不打算考虑这个问题。 原来的意图确实和[.data.frame wrt ==NA ,我相信每个人都对此感到满意。 例如,FAQ 2.17有:

DT[ColA==ColB]DF[!is.na(ColA) & !is.na(ColB) & ColA==ColB,]

这种便利是通过以下途径实现的:

DT[c(TRUE,NA,FALSE)]NA视为FALSE ,但DF[c(TRUE,NA,FALSE)]为每个NA返回NA

动机不仅仅是方便而是速度,因为每一个!is.na& and ==本身就是vector扫描,每个结果都有相关的内存分配(在介绍小插曲中解释过)。 所以尽pipex[is.na(a) | a!=""] x[is.na(a) | a!=""]是一个工作的解决scheme,这正是我试图避免在data.table中需要的逻辑types。 x[!a %in% ""]略好; 即2次扫描( %in%! ),而不是3次( is.na|!= )。 但是真正的x[a != ""]应该在一次扫描中做Frank所期望的(包括NA )。

提交的新function请求链接回到这个问题:

DT [col!=“”]应该包含NA

感谢弗兰克,埃迪和阿伦。 如果我没有正确理解,可以随意纠正,否则最终会做出改变。 这将需要以考虑复合expression的方式来完成; 例如, DT[colA=="foo" & colB!="bar"]应排除colA具有NA行,但包括colA为非NAcolBNA 。 类似地, DT[colA!=colB]应该包括colA或colB是NA但不是两者的行。 也许DT[colA==colB]应该包括colAcolB都是NA (我认为目前它不是)。