Транспонирование идентичных объектов
Сегодня у меня появился странный результат.
Чтобы воспроизвести его, рассмотрите следующие фреймы данных:
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(...)
вызов соответственно.)
Мои вопросы:
Обратите внимание, что другие функции информации не обнаруживают различий между 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
.