使用!= <some non-na =“”>对data.table进行子集化也排除了NA </some>

时间:2013-04-25 18:10:36

标签: r data.table

我有一个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

在查看?`!=`之后,我找到了一个有效的衬垫,但这很痛苦:

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

3 个答案:

答案 0 :(得分:15)

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

您应该使用%in%。它会返回一个逻辑向量。

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

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

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

(与data.frame相对)

如果您查看函数data.table下的data.table.R文件上的"[.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  
    }
    ....
}

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

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

首先,part 1计算出类call的对象。 part 2中if语句的第二部分返回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)

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

现在,我在is.logical中检查了part 3。所以,在这里,NA被替换为FALSE。因此它成为,c(FALSE, TRUE, FALSE)。稍后,执行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 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.
}

考虑到这一点,我认为语法有些不一致。如果我有时间来制定问题,那么我很快就会写一篇文章。

答案 1 :(得分:3)

正如您已经想到的那样,这就是原因:

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

你可以做你已经想到的事情,即x[is.na(a) | a != ""]setkey a,并执行以下操作:

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

答案 2 :(得分:3)

马修的背景答案:

此问题突出显示!=NA [.data.frame的行为并非打算考虑它。最初的意图确实与== w.r.t不同。 NADT[ColA==ColB]我相信每个人都对此感到满意。例如,FAQ 2.17有:

  

DF[!is.na(ColA) & !is.na(ColB) & ColA==ColB,]DT[c(TRUE,NA,FALSE)]

更简单

通过以下方式实现这种便利:

  

NAFALSE视为DF[c(TRUE,NA,FALSE)],但NA   为每个NA

返回!

动机不仅仅是方便而是速度,因为每个is.na&==x[is.na(a) | a!=""]本身都是矢量扫描,每个都有相关的内存分配。他们的结果(在介绍小插图中解释)。因此虽然x[!a %in% ""]是一个可行的解决方案,但它正是我试图避免在data.table中需要的逻辑类型。 %in%略胜一筹;即,2次扫描(!is.na)而不是3次(|!=x[a != ""])。但是真的NA应该在一次扫描中做到Frank所期望的(包括DT[colA=="foo" & colB!="bar"])。

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

DT[col!=""] should include NA

感谢Frank,Eddi和Arun。如果我没有正确理解,请随意纠正,否则最终会做出改变。它需要以考虑复合表达的方式完成;例如,NA应排除colA中包含colA的行,但包括NA为非colBNADT[colA!=colB]的行}。同样,NA应包含colA或colB为DT[colA==colB]但不包含两者的行。也许colA应该包含colBNA都是{{1}}的行(我相信它目前没有)。