R中xtabs和aggregate之间na.action的不一致

Tho*_*mas 5 aggregate r counting na

我有以下 data.frame:

x <- data.frame(A = c("Y", "Y", "Z", NA),
                B = c(NA, TRUE, FALSE, TRUE),
                C = c(TRUE, TRUE, NA, FALSE))
Run Code Online (Sandbox Code Playgroud)

我需要计算下表xtabs

A      B C
  Y    1 2
  Z    0 0
  <NA> 1 0
Run Code Online (Sandbox Code Playgroud)

我被告知要使用na.action = NULL,它确实返回了我需要的表:

xtabs(formula = cbind(B, C) ~ A,
      data = x,
      addNA = TRUE,
      na.action = NULL)

A      B C
  Y    1 2
  Z    0 0
  <NA> 1 0
Run Code Online (Sandbox Code Playgroud)

但是,na.action = na.pass返回一个不同的表:

xtabs(formula = cbind(B, C) ~ A,
      data = x,
      addNA = TRUE,
      na.action = na.pass)

A       B  C
  Y        2
  Z     0   
  <NA>  1  0
Run Code Online (Sandbox Code Playgroud)

但是文档xtabs说:

na.action
当它是 na.pass 并且公式具有左侧(带计数)时,使用 sum(*, na.rm = TRUE) 而不是 sum(*) 来计算计数。

使用aggregate,na.action = na.pass返回预期结果(还有na.action = NULL):

aggregate(formula = cbind(B, C) ~ addNA(A),
          data = x,
          FUN = sum,
          na.rm = TRUE,
          na.action = na.pass) # same result with na.action = NULL

  addNA(A) B C
1            Y 1 2
2            Z 0 0
3         <NA> 1 0
Run Code Online (Sandbox Code Playgroud)

虽然我得到了我需要的表xtabs,但我不了解文档中的na.actionin行为xtabs。所以我的问题是:

  • 是的行为,na.actionxtabs与文档保持一致?除非我遗漏了什么,否则na.action = na.pass不会导致sum(*, na.rm = TRUE).
  • na.action = NULL某处有记录吗?
  • xtabs源代码中有na.rm <- identical(naAct, quote(na.omit)) || identical(naAct, na.omit) || identical(naAct, "na.omit"). 但我什么也没看到na.action = na.passna.action = NULL。如何做na.action = na.passna.action = NULL工作?

All*_*ron 6

在不描述xtabs工作原理的情况下,很难给出规范的答案。如果我们逐步了解其源代码的要点,我们就会清楚地看到发生了什么。

在进行一些基本的类型检查之后,调用 以xtabs首先使用stats::model.frame为公式中包含的所有变量创建一个数据框,从而在内部工作,并且正是为此na.action传递了参数。

它这样做的方式非常聪明。xtabs首先复制您通过 拨打的电话match.call,如下所示:

m <- match.call(expand.dots = FALSE)
Run Code Online (Sandbox Code Playgroud)

然后它去掉不需要传递的参数,stats::model.frame如下所示:

m$... <- m$exclude <- m$drop.unused.levels <- m$sparse <- m$addNA <- NULL
Run Code Online (Sandbox Code Playgroud)

正如帮助文件中所承诺的,如果addNATRUEna.action缺失,它现在将默认为na.pass

    if (addNA && missing(na.action)) 
        m$na.action <- quote(na.pass)
Run Code Online (Sandbox Code Playgroud)

然后它将要调用的函数更改xtabsstats::model.frame如下所示:

m[[1L]] <- quote(stats::model.frame)
Run Code Online (Sandbox Code Playgroud)

所以这个对象m是一个调用(也是一个独立的 reprex),在你的情况下看起来像这样:

stats::model.frame(formula = cbind(B, C) ~ A, data = list(A = structure(c(1L, 
1L, 2L, NA), .Label = c("Y", "Z"), class = "factor"), B = c(NA, TRUE, FALSE, TRUE), 
C = c(TRUE, TRUE, NA, FALSE)), na.action = NULL)
Run Code Online (Sandbox Code Playgroud)

请注意,您na.action = NULL已传递给此调用。这具有将所有NA值保留在框架中的效果。当上面的调用被评估时,它给出了这个数据框:

eval(m)
#>   cbind(B, C).B cbind(B, C).C    A
#> 1            NA          TRUE    Y
#> 2          TRUE          TRUE    Y
#> 3         FALSE            NA    Z
#> 4          TRUE         FALSE <NA>
Run Code Online (Sandbox Code Playgroud)

请注意,这与您通过时获得的结果相同na.action = na.pass

stats::model.frame(formula = cbind(B, C) ~ A, data = list(A = structure(c(1L, 
1L, 2L, NA), .Label = c("Y", "Z"), class = "factor"), B = c(NA, TRUE, FALSE, TRUE), 
C = c(TRUE, TRUE, NA, FALSE)), na.action = na.pass)
#>   cbind(B, C).B cbind(B, C).C    A
#> 1            NA          TRUE    Y
#> 2          TRUE          TRUE    Y
#> 3         FALSE            NA    Z
#> 4          TRUE         FALSE <NA>
Run Code Online (Sandbox Code Playgroud)

但是,如果您通过了na.action = na.omit,您将只剩下一行,因为只有第 2 行没有NA值。

在任何情况下,“模型框架”结果都存储在变量 中mf。然后将其拆分为自变量(在您的情况下为 A 列)和响应变量(在您的情况下)cbind(B, C)

响应存储在 中y,变量存储在by

        i <- attr(attr(mf, "terms"), "response")
        by <- mf[-i]
        y <- mf[[i]]
Run Code Online (Sandbox Code Playgroud)

现在,by处理以确保每个自变量都是一个因子,并且NA如果您已指定,则任何值都将转换为因子水平addNA = TRUE

    by <- lapply(by, function(u) {
        if (!is.factor(u)) 
            u <- factor(u, exclude = exclude)
        else if (has.exclude) 
            u <- factor(as.character(u), levels = setdiff(levels(u), 
                exclude), exclude = NULL)
        if (addNA) 
            u <- addNA(u, ifany = TRUE)
        u[, drop = drop.unused.levels]
    })
Run Code Online (Sandbox Code Playgroud)

现在我们来到了症结。所述na.action被再次使用,以确定如何NA在响应变量值将被计算。在您的情况下,由于您通过了na.action = NULL,您将看到naAct将获得存储在 中的值getOption("na.action"),如果您从未更改过它,则应将其设置为na.omit。这反过来会导致变量的值na.rm,TRUE

    naAct <- if (!is.null(m$na.action)) {
        m$na.action
    }else {getOption("na.action", default = quote(na.omit))}
    na.rm <- identical(naAct, quote(na.omit)) || identical(naAct, 
        na.omit) || identical(naAct, "na.omit")
Run Code Online (Sandbox Code Playgroud)

请注意,如果您通过了na.action = na.pass,那么如果您跟踪这段代码,na.rm就会是FALSE

最后,我们来到xtabs使用suminside a构建表的部分tapply,它本身在 a 中lapply

m <- match.call(expand.dots = FALSE)
Run Code Online (Sandbox Code Playgroud)

您可以看到该na.rm变量用于确定是否NA在尝试对列求和之前从列中删除s。然后将其结果lapply强制到最终的交叉表中。


那么这如何回答你的问题呢?

当文档说如果您不传递 时na.action,它将默认为na.pass。但是,na.action用在两个地方:一次在调用中model.frame,一次用于确定 的值na.rm。从源代码中可以清楚地看出,如果na.actionna.passna.rm则将是FALSE,因此您将错过任何包含NA值的响应组的计数。这与帮助文件中所写的相反。

解决这个问题的唯一方法是传递na.action = NULL,因为这将允许model.frame保留NA值,但也会导致sum函数默认为na.rm


TL;DRxtabs在这一点上的文档是错误的。