R 中的 xtab 和聚合之间 na.action 的不一致
Inconsistency of na.action between xtabs and aggregate in R
我有以下 data.frame:
x <- data.frame(A = c("Y", "Y", "Z", NA),
B = c(NA, TRUE, FALSE, TRUE),
C = c(TRUE, TRUE, NA, FALSE))
我需要用 xtabs
计算以下 table:
A B C
Y 1 2
Z 0 0
<NA> 1 0
,确实 returns table 我需要:
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
但是,na.action = na.pass
returns一个不同的table:
xtabs(formula = cbind(B, C) ~ A,
data = x,
addNA = TRUE,
na.action = na.pass)
A B C
Y 2
Z 0
<NA> 1 0
但是 xtabs
的文档说:
na.action
When it is na.pass and formula has a left hand side (with counts),
sum(*, na.rm = TRUE) is used instead of sum(*) for the counts.
与 aggregate
、na.action = na.pass
returns 预期结果(以及 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
虽然我通过 xtabs
获得了我需要的 table,但我无法从文档中理解 xtabs
中 na.action
的行为。所以我的问题是:
na.action
在xtabs
中的行为是否与文档一致?除非我遗漏了什么,否则 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.pass
和 na.action = NULL
。 na.action = na.pass
和 na.action = NULL
是如何工作的?
如果不描述 xtabs
的工作原理,就很难给出规范的答案。如果我们逐步查看其源代码的要点,我们将清楚地看到发生了什么。
经过一些基本的类型检查后,对 xtabs
的调用在内部工作,方法是首先使用 stats::model.frame
创建公式中包含的所有变量的数据框,正是为了这个 na.action
传递参数.
它的做法相当巧妙。 xtabs
首先复制你通过 match.call
对它的调用,像这样:
m <- match.call(expand.dots = FALSE)
然后它去掉不需要传递给stats::model.frame
的参数,像这样:
m$... <- m$exclude <- m$drop.unused.levels <- m$sparse <- m$addNA <- NULL
正如帮助文件中所承诺的那样,如果 addNA
为 TRUE
而缺少 na.action
,则现在默认为 na.pass
:
if (addNA && missing(na.action))
m$na.action <- quote(na.pass)
然后它将要调用的函数从xtabs
更改为stats::model.frame
,如下所示:
m[[1L]] <- quote(stats::model.frame)
所以对象 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)
请注意,您的 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>
请注意,这与您通过 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>
但是,如果您通过 na.action = na.omit
,您将只剩下一行,因为只有第 2 行没有 NA
个值。
在任何情况下,"model frame" 结果都存储在变量 mf
中。然后将其拆分为自变量 - 在您的情况下为 A 列和响应变量 - 在您的情况下 cbind(B, C)
.
响应存储在y
中,变量存储在by
中:
i <- attr(attr(mf, "terms"), "response")
by <- mf[-i]
y <- mf[[i]]
现在,处理 by
以确保每个自变量都是一个因子,并且如果您指定 addNA = TRUE
:[=81,任何 NA
值都将转换为因子水平=]
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]
})
现在我们来到了关键点。 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")
请注意,如果您通过了 na.action = na.pass
,那么如果您跟踪这段代码,na.rm
将是 FALSE
。
最后,我们来到 xtabs
table 是使用 tapply
中的 sum
构建的部分,而 tapply
本身也在 lapply
中.
lapply(as.data.frame(y), tapply, by, sum, na.rm = na.rm, default = 0L)
您可以看到 na.rm
变量用于确定是否在尝试对列求和之前从列中删除 NA
。这个 lapply
的结果然后被强制到最终的交叉表中。
那么这如何回答您的问题?
当文档说如果您不传递 na.action
,它将默认为 na.pass
,这是真的。但是,na.action
在两个地方使用:一次是在调用 model.frame
时,一次是确定 na.rm
的值。从源代码中可以清楚地看出,如果 na.action
是 na.pass
,那么 na.rm
将是 FALSE
,因此您将错过任何包含 NA
值。这和帮助文件里写的相反
绕过这个的唯一方法是传递 na.action = NULL
,因为这将允许 model.frame
保留 NA
值,但也会导致 sum
函数默认至 na.rm
.
TL;DR xtabs
的文档在这一点上是错误的。
对不起,我现在才加入。确实,xtabs() 的最后六个修改都是我做的,所以我也必须在这里承担责任。
深入研究 xtabs()
的所有变体和分支总是需要一些时间,而我还没有花时间(这次;当然在当时..)。
但你终于得到了答案:
是的,R 代码或文档中存在错误(而且 "or" 包含在内......;-)
我目前的直觉是指向帮助(文件)而不是实现中的错误
R 的 bugzilla 是我们应该详细了解这一点的地方,尤其是因为那是 "wired" 到 R Core 团队的频道。
--> 跟进:https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17770.
我有以下 data.frame:
x <- data.frame(A = c("Y", "Y", "Z", NA),
B = c(NA, TRUE, FALSE, TRUE),
C = c(TRUE, TRUE, NA, FALSE))
我需要用 xtabs
计算以下 table:
A B C
Y 1 2
Z 0 0
<NA> 1 0
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
但是,na.action = na.pass
returns一个不同的table:
xtabs(formula = cbind(B, C) ~ A,
data = x,
addNA = TRUE,
na.action = na.pass)
A B C
Y 2
Z 0
<NA> 1 0
但是 xtabs
的文档说:
na.action
When it is na.pass and formula has a left hand side (with counts), sum(*, na.rm = TRUE) is used instead of sum(*) for the counts.
与 aggregate
、na.action = na.pass
returns 预期结果(以及 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
虽然我通过 xtabs
获得了我需要的 table,但我无法从文档中理解 xtabs
中 na.action
的行为。所以我的问题是:
na.action
在xtabs
中的行为是否与文档一致?除非我遗漏了什么,否则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.pass
和na.action = NULL
。na.action = na.pass
和na.action = NULL
是如何工作的?
如果不描述 xtabs
的工作原理,就很难给出规范的答案。如果我们逐步查看其源代码的要点,我们将清楚地看到发生了什么。
经过一些基本的类型检查后,对 xtabs
的调用在内部工作,方法是首先使用 stats::model.frame
创建公式中包含的所有变量的数据框,正是为了这个 na.action
传递参数.
它的做法相当巧妙。 xtabs
首先复制你通过 match.call
对它的调用,像这样:
m <- match.call(expand.dots = FALSE)
然后它去掉不需要传递给stats::model.frame
的参数,像这样:
m$... <- m$exclude <- m$drop.unused.levels <- m$sparse <- m$addNA <- NULL
正如帮助文件中所承诺的那样,如果 addNA
为 TRUE
而缺少 na.action
,则现在默认为 na.pass
:
if (addNA && missing(na.action))
m$na.action <- quote(na.pass)
然后它将要调用的函数从xtabs
更改为stats::model.frame
,如下所示:
m[[1L]] <- quote(stats::model.frame)
所以对象 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)
请注意,您的 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>
请注意,这与您通过 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>
但是,如果您通过 na.action = na.omit
,您将只剩下一行,因为只有第 2 行没有 NA
个值。
在任何情况下,"model frame" 结果都存储在变量 mf
中。然后将其拆分为自变量 - 在您的情况下为 A 列和响应变量 - 在您的情况下 cbind(B, C)
.
响应存储在y
中,变量存储在by
中:
i <- attr(attr(mf, "terms"), "response")
by <- mf[-i]
y <- mf[[i]]
现在,处理 by
以确保每个自变量都是一个因子,并且如果您指定 addNA = TRUE
:[=81,任何 NA
值都将转换为因子水平=]
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]
})
现在我们来到了关键点。 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")
请注意,如果您通过了 na.action = na.pass
,那么如果您跟踪这段代码,na.rm
将是 FALSE
。
最后,我们来到 xtabs
table 是使用 tapply
中的 sum
构建的部分,而 tapply
本身也在 lapply
中.
lapply(as.data.frame(y), tapply, by, sum, na.rm = na.rm, default = 0L)
您可以看到 na.rm
变量用于确定是否在尝试对列求和之前从列中删除 NA
。这个 lapply
的结果然后被强制到最终的交叉表中。
那么这如何回答您的问题?
当文档说如果您不传递 na.action
,它将默认为 na.pass
,这是真的。但是,na.action
在两个地方使用:一次是在调用 model.frame
时,一次是确定 na.rm
的值。从源代码中可以清楚地看出,如果 na.action
是 na.pass
,那么 na.rm
将是 FALSE
,因此您将错过任何包含 NA
值。这和帮助文件里写的相反
绕过这个的唯一方法是传递 na.action = NULL
,因为这将允许 model.frame
保留 NA
值,但也会导致 sum
函数默认至 na.rm
.
TL;DR xtabs
的文档在这一点上是错误的。
对不起,我现在才加入。确实,xtabs() 的最后六个修改都是我做的,所以我也必须在这里承担责任。
深入研究 xtabs()
的所有变体和分支总是需要一些时间,而我还没有花时间(这次;当然在当时..)。
但你终于得到了答案:
是的,R 代码或文档中存在错误(而且 "or" 包含在内......;-)
我目前的直觉是指向帮助(文件)而不是实现中的错误
R 的 bugzilla 是我们应该详细了解这一点的地方,尤其是因为那是 "wired" 到 R Core 团队的频道。
--> 跟进:https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17770.