18

我有一个带有NAs 列的 data.table。我想删除该列具有特定值(恰好是"")的行。但是,我的第一次尝试也导致我丢失了带有NAs 的行:

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

看了之后?`!=`,我发现了一个可以工作的衬里,但是很痛苦:

> 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在子集时保留 s:

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

3 回答 3

18

为您的问题提供解决方案:

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

a %in% ""
# [1] FALSE  TRUE FALSE

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

要找出为什么会发生这种情况data.table

(相对于data.frame

如果您查看function 下data.table文件的源代码,则会发现一组该检查参数。其中之一是:data.table.R"[.data.table"if-statementsi

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  
    }
    ....
}

为了解释这种差异,我在这里粘贴了重要的一段代码。我还将它们标记为 3 个部分。

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

首先,part 1评估为 class 的对象call。if 语句的第二部分part 2返回 FALSE。之后,call被“评估”为c(TRUE, FALSE, NA)。然后part 3被执行。因此,NA被替换为FALSE(逻辑循环的最后一行)。

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

part 1再次返回呼叫。但是,part 2评估为 TRUE 并因此设置:

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

这就是魔法发生的地方。否定现在已被删除。请记住,这仍然是类call的对象。所以这被评估(使用eval)再次合乎逻辑。因此,(a=="")评估为c(FALSE, TRUE, NA)

现在,这是is.logicalpart 3. 所以,在这里,NA被替换为FALSE. 因此变成,c(FALSE, TRUE, FALSE)。在稍后的某个时间点, awhich(c(F,T,F))被执行,这导致 2 这里。因为notjoin = TRUE(from 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 i.e. 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.
}

鉴于此,我认为语法存在一些不一致之处。如果我设法抽出时间来阐述问题,那么我很快就会写一篇文章。

于 2013-04-25T18:31:19.283 回答
4

马修的背景回答:

!=这个问题突出显示的行为NA不是故意的,考虑一下。初衷确实是和 wrt 不同,[.data.frame相信大家都很满意。例如,FAQ 2.17 有:==NA

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

这种便利是通过 dint of 实现的:

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

动机不仅是方便,而且是速度,因为每个!, is.na,&==本身都是矢量扫描,每个结果都有相关的内存分配(在介绍小插图中解释)。因此,尽管x[is.na(a) | a!=""]这是一个可行的解决方案,但它正是我试图避免在 data.table 中需要的逻辑类型。x[!a %in% ""]稍微好一点;即 2 次扫描 (%in%!) 而不是 3 次 ( is.na,|!=)。但真的 x[a != ""]应该NA在一次扫描中完成弗兰克的预期(包括)。

提交的新功能请求链接回此问题:

DT[col!=""] 应该包括 NA

感谢 Frank、Eddi 和 Arun。如果我没有正确理解,请随时纠正,否则最终会做出改变。它需要以考虑复合表达式的方式完成;例如,DT[colA=="foo" & colB!="bar"]应该排除带有NAin的行,colA但包括colANA但是colB的行NA。同样,DT[colA!=colB]应该包括 colA 或 colB 是NA但不是两者都存在的行。也许DT[colA==colB]应该包括两者都存在的行colAcolBNA相信它目前没有)。

于 2013-06-09T10:52:16.470 回答
3

正如您已经弄清楚的那样,这就是原因:

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

你可以做你已经想出来的事情,x[is.na(a) | a != ""]或者你可以setkey继续a做以下事情:

setkey(x, a)
x[!J("")]
于 2013-04-25T18:25:46.773 回答