使用!= <某些非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 2
) seq_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 != ""]
或者您可以在a
上a
并执行以下操作:
setkey(x, a) x[!J("")]
马修的背景答案:
这个问题突出显示了!=
NA
在NA
上的行为并不打算考虑这个问题。 原来的意图确实和[.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
为非NA
但colB
为NA
。 类似地, DT[colA!=colB]
应该包括colA或colB是NA
但不是两者的行。 也许DT[colA==colB]
应该包括colA
和colB
都是NA
(我认为目前它不是)。