Транспонирование идентичных объектов

Сегодня у меня появился странный результат.

Чтобы воспроизвести его, рассмотрите следующие фреймы данных:

x <- data.frame(x=1:3, y=11:13)
y <- x[1:3, 1:2] 

Они должны быть и на самом деле идентичны:

identical(x,y)
# [1] TRUE

Применение t() к indentical объектам должно давать тот же результат, но:

identical(t(x),t(y))
# [1] FALSE

Разница заключается в именах столбцов:

colnames(t(x))
# NULL
colnames(t(y))
# [1] "1" "2" "3"

Учитывая это, если вы хотите стек y по столбцам, вы получите то, что ожидаете:

stack(as.data.frame(t(y)))
#   values ind
# 1      1   1
# 2     11   1
# 3      2   2
# 4     12   2
# 5      3   3
# 6     13   3

а

stack(as.data.frame(t(x)))
#     values ind
# 1      1  V1
# 2     11  V1
# 3      2  V2
# 4     12  V2
# 5      3  V3
# 6     13  V3

В последнем случае as.data.frame() не находит исходные имена столбцов и автоматически генерирует их.

Претендент находится в as.matrix(), называемом t():

rownames(as.matrix(x))
# NULL
rownames(as.matrix(y))
# [1] "1" "2" "3"

Обходным путем является установка rownames.force:

rownames(as.matrix(x, rownames.force=TRUE))
# [1] "1" "2" "3"
rownames(as.matrix(y, rownames.force=TRUE))
# [1] "1" "2" "3"
identical(t(as.matrix(x, rownames.force=TRUE)), 
          t(as.matrix(y, rownames.force=TRUE)))
# [1] TRUE

(и переписать stack(...) вызов соответственно.)

Мои вопросы:

  • Почему as.matrix() обрабатывает по-разному x и y и

  • как вы можете рассказать о различии между ними?

Обратите внимание, что другие функции информации не обнаруживают различий между x, y:

identical(attributes(x), attributes(y))
# [1] TRUE
identical(str(x), str(y))
# ...
#[1] TRUE

Комментарии к решениям

Конрад Рудольф дает краткое, но эффективное объяснение поведения, описанного выше (см. также mt1022  для более подробной информации).

Короче Конрад показывает, что:

a) x и y являются внутренне разными; b) "identical тоже слишком слабо по умолчанию", чтобы уловить эту внутреннюю разницу.

Теперь, если вы берете подмножество T для набора S, которое имеет все элементы S, то S и T являются точно такими же объектами. Итак, если вы берете фрейм данных y, который имеет все строки и столбцы x, то x и y должны быть точно такими же объектами. К сожалению x \neq y!
Это поведение не только противоречит интуиции, но и запутывается, то есть разница не очевидна, но внутренняя и даже функция по умолчанию identical не могут ее видеть.

Другим естественным принципом является то, что перенос двух идентичных (матричноподобных) объектов создает одинаковые объекты. Опять же, это нарушается тем, что перед транспонированием identical является "слишком слабым"; после транспонирования по умолчанию identical достаточно, чтобы увидеть разницу.

ИМХО такое поведение (даже если это не ошибка) является неправильным поведением для научного языка, такого как R.
Надеюсь, этот пост привлечет некоторое внимание, и команда R рассмотрит его пересмотр.

Ответы

Ответ 1

identical по умолчанию слишком слаб, но вы можете изменить это:

> identical(x, y, attrib.as.set = FALSE)
[1] FALSE

Причину можно найти, осмотрев объекты более подробно:

> dput(x)
structure(list(x = 1:3, y = 11:13), .Names = c("x", "y"), row.names = c(NA,
-3L), class = "data.frame")
> dput(y)
structure(list(x = 1:3, y = 11:13), .Names = c("x", "y"), row.names = c(NA,
3L), class = "data.frame")

Обратите внимание на различные атрибуты row.names:

> .row_names_info(x)
[1] -3
> .row_names_info(y)
[1] 3

Из документации мы можем подсчитать, что отрицательное число подразумевает автоматические имена ростов (для x), тогда как имена строк y ar arent автоматические. И as.matrix рассматривает их по-разному.

Ответ 2

Как и в комментарии, x и y не являются строго одинаковыми. Когда мы вызываем t в data.frame, t.data.frame будет выполняться:

function (x) 
{
    x <- as.matrix(x)
    NextMethod("t")
}

Как мы видим, он вызывает as.matrix, т.е. as.matrix.data.frame:

function (x, rownames.force = NA, ...) 
{
    dm <- dim(x)
    rn <- if (rownames.force %in% FALSE) 
        NULL
    else if (rownames.force %in% TRUE) 
        row.names(x)
    else if (.row_names_info(x) <= 0L) 
        NULL
    else row.names(x)
...

Как комментирует @oropendola, возврат .row_names_info из x и y различен, и вышеприведенная функция - это то, где разница вступает в силу.

Тогда почему y имеет разные rownames? Давайте посмотрим на [.data.frame, я добавил комментарий в ключевые строки:

{
    ... # many lines of code
    xx <- x  #!! this is where xx is defined
    cols <- names(xx)
    x <- vector("list", length(x))
    x <- .Internal(copyDFattr(xx, x))  # This is where I am not sure about
    oldClass(x) <- attr(x, "row.names") <- NULL
    if (has.j) {
        nm <- names(x)
        if (is.null(nm)) 
            nm <- character()
        if (!is.character(j) && anyNA(nm)) 
            names(nm) <- names(x) <- seq_along(x)
        x <- x[j]
        cols <- names(x)
        if (drop && length(x) == 1L) {
            if (is.character(i)) {
                rows <- attr(xx, "row.names")
                i <- pmatch(i, rows, duplicates.ok = TRUE)
            }
            xj <- .subset2(.subset(xx, j), 1L)
            return(if (length(dim(xj)) != 2L) xj[i] else xj[i, 
                                                            , drop = FALSE])
        }
        if (anyNA(cols)) 
            stop("undefined columns selected")
        if (!is.null(names(nm))) 
            cols <- names(x) <- nm[cols]
        nxx <- structure(seq_along(xx), names = names(xx))
        sxx <- match(nxx[j], seq_along(xx))
    }
    else sxx <- seq_along(x)
    rows <- NULL ## this is where rows is defined, as we give numeric i, the following
    ## if block will not be executed
    if (is.character(i)) {
        rows <- attr(xx, "row.names")
        i <- pmatch(i, rows, duplicates.ok = TRUE)
    }
    for (j in seq_along(x)) {
        xj <- xx[[sxx[j]]]
        x[[j]] <- if (length(dim(xj)) != 2L) 
            xj[i]
        else xj[i, , drop = FALSE]
    }
    if (drop) {
        n <- length(x)
        if (n == 1L) 
            return(x[[1L]])
        if (n > 1L) {
            xj <- x[[1L]]
            nrow <- if (length(dim(xj)) == 2L) 
                dim(xj)[1L]
            else length(xj)
            drop <- !mdrop && nrow == 1L
        }
        else drop <- FALSE
    }
    if (!drop) { ## drop is False for our case
        if (is.null(rows)) 
            rows <- attr(xx, "row.names")  ## rows changed from NULL to 1,2,3 here
        rows <- rows[i]
        if ((ina <- anyNA(rows)) | (dup <- anyDuplicated(rows))) {
            if (!dup && is.character(rows)) 
                dup <- "NA" %in% rows
            if (ina) 
                rows[is.na(rows)] <- "NA"
            if (dup) 
                rows <- make.unique(as.character(rows))
        }
        if (has.j && anyDuplicated(nm <- names(x))) 
            names(x) <- make.unique(nm)
        if (is.null(rows)) 
            rows <- attr(xx, "row.names")[i]
        attr(x, "row.names") <- rows  ## this is where the rownames of x changed
        oldClass(x) <- oldClass(xx)
    }
    x
}

мы можем видеть, что y получает свои имена чем-то вроде attr(x, 'row.names'):

> attr(x, 'row.names')
[1] 1 2 3

Итак, когда мы создали y с [.data.frame, он получает атрибуты row.names, отличные от x, из которых row.names являются автоматическими и отображаются с отрицательным знаком в результатах dput.


Изменить

Собственно, это указано в руководстве row.names:

Примечание

row.names похож на имена ростов для массивов, и у него есть метод, который вызывает имена ростов для аргумента массива.

Имена строк формы 1: n для n > 2 хранятся внутри компактного форму, которая может быть видна из кода C или путем отпарки, но не через row.names или attr (x, "row.names" ). Кроме того, некоторые имена этого сортировка отмечены как "автоматические и обрабатываются по-разному as.matrix и data.matrix(и, возможно, другие функции).

Итак, attr не различает автоматический row.names (как и для x) и явный interger row.names (как и для y), тогда как это распознается as.matrix через внутреннее представление .row_names_info.