Ошибка в документации? Несоответствие na.action между xtabs и агрегатом в R - PullRequest
5 голосов
/ 16 апреля 2020

У меня есть следующий data.frame:

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

И мне нужно вычислить следующую таблицу с xtabs:

A      B C
  Y    1 2
  Z    0 0
  <NA> 1 0

Мне сказали использовать 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

Однако 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

Но документация xtabs говорит:

na.action
Если значение равно na.pass, а формула имеет левую часть (со счетчиками), вместо суммы используется сумма (*, na.rm = TRUE) (*) для отсчетов.

С 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

Хотя я получаю таблицу Мне нужно с xtabs, я не понимаю поведение na.action в xtabs из документации. Итак, мои вопросы:

  • Соответствует ли поведение 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?

1 Ответ

5 голосов
/ 27 апреля 2020

Трудно дать канонический ответ, не описав, как работает xtabs. Если мы пройдемся по основным пунктам его исходного кода, мы ясно увидим, что происходит.

После некоторой базовой проверки типа c вызов 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 является вызовом (и также является автономным представлением), который в вашем случае выглядит следующим образом:

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.

В любом случае, результат "рамки модели" сохраняется в переменной mf. Затем он разделяется на независимую (ые) переменную (и), - в вашем случае, столбец A и переменную ответа - в вашем случае cbind(B, C).

Ответ сохраняется в y, а переменная в by:

        i <- attr(attr(mf, "terms"), "response")
        by <- mf[-i]
        y <- mf[[i]]

Теперь 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]
    })

Теперь мы подошли к сути. 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 построена с использованием sum внутри tapply, которая сама находится внутри lapply.

lapply(as.data.frame(y), tapply, by, sum, na.rm = na.rm, default = 0L)

Вы можете видеть, что переменная na.rm используется, чтобы определить, следует ли удалять NA s из столбцов, прежде чем пытаться их суммировать. Результат этого 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 неверна в этом пункте.

...