【问题标题】:subsetting a data.table using !=<some non-NA> excludes NA too使用 !=<some non-NA> 对 data.table 进行子集化也排除了 NA
【发布时间】:2013-04-19 18:14:52
【问题描述】:

我有一个 data.table,其中有一列包含 NAs。我想删除该列具有特定值的行(恰好是"")。但是,我的第一次尝试也导致我丢失了 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,它似乎工作方式不同,在子集时保持NAs:

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

【问题讨论】:

  • 好的,我刚刚找到了一个更好的方法,x[is.na(a)|a!=""],但这也很麻烦,我怀疑我处理错了。
  • 我的实质性问题已由前两个答案解决。我仍然想知道这里是否有与标准data.frames不同的行为的原因。似乎 Matthew 将其设计为仅显示评估为 True 的行,而 data.frames 显示评估为 True 或(逻辑)NA 的行...?
  • 顺便说一句,注意到你写了x &lt;- data.table(a);x。不错的小东西,你可以写(x &lt;- data.table(a)),当命令os结束时,r会打印出x。
  • @Frank 你更喜欢data.frame 打印NA 行的方式??我一直认为输出很糟糕!
  • @haki:哦,对了;忘记了。谢谢。

标签: r data.table


【解决方案1】:

如你所见,原因如下:

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

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

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

【讨论】:

  • 啊,setkey 方法也很吸引人,但我更喜欢使用 %in%,正如 Arun 建议的那样。谢谢。同样,我刚刚意识到x[!(a=="")] 也有效。
【解决方案2】:

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

您应该使用%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)

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

现在,在part 3 中检查is.logical。所以,在这里,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)],这是期望的结果。下面是相关代码sn-p:

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

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

【讨论】:

  • 哇,我从未尝试过。我只是想 ==%in% 工作方式相同。我想这与在 data.table 的 i 参数中重新定义 == 有关吗?
  • 谢谢!很有意思。我仍然没有遵循的一个步骤是“NAFALSE”取代,但我可能只需要更仔细地阅读您的答案和代码。我是否可以这样解读:“当i=!(arg) 其中arg 评估为逻辑时,它会找到满足arg 的行并仅返回互补行”?此外,是否有任何其他类型的i 参数(除了否定逻辑)我应该知道/考虑它是否在应用substitute 后转换为call?我对substitute 不是很熟悉。再次感谢!
  • 如果您已经阅读了我的上一条评论,我现在明白为什么/何时 NA 会被 False 取代。明白了。
  • 好东西,除了我会说d[a != ""] 按预期工作而d[!(a == "")] 没有
  • @eddi,唯一的问题是,如果您了解 NA 逻辑的作用,它不是不良行为。那么,你为什么不继续在 R-devel 上写下这个“坏行为”呢?
【解决方案3】:

Matthew 的背景回答:

考虑到这个问题突出显示的!= 上的NA 的行为不是故意的。初衷确实是和[.data.framew.r.t不同。 ==NA 我相信每个人都对此感到满意。例如,FAQ 2.17 有:

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

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

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

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

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

DT[col!=""] should include NA

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

【讨论】:

  • Matthew, DF[!is.na(ColA) &amp; !is.na(ColB) &amp; ColA==ColB,] 并不是真正必要的/唯一的方法。可以写DF[which(DF$ColA == DF$ColB), ]NA 对 FALSE 的解释违背了 R 对数据分析(未知/缺失数据)的许多“统计方面”,你不会说吗?虽然,这可能在邮件列表中更合适。
  • 我刚刚在set.seed(45); DF &lt;- as.data.frame(matrix(sample(c(1,2,3,NA), 2e6, replace=TRUE), ncol=2)); DT &lt;- data.table(DF)which 上测试了system.time(DF[which(DF$V1 == DF$V2), ])system.time(DT[V1 == V2]) 似乎更快...有什么想法吗?
  • 即使DT[which(V1 == V2)] 也比DT[V1 == V2] 快。
  • @Arun 非常感谢,FR#4652 现已提交以解决该速度问题。已经在邮件列表中回复了关于这里的第一点:r.789695.n4.nabble.com/…
猜你喜欢
  • 2014-02-02
  • 1970-01-01
  • 2019-01-12
  • 2017-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-24
  • 1970-01-01
相关资源
最近更新 更多