Странное поведение для data.frames без имен столбцов

Существует непредвиденное поведение для data.frames без имен столбцов. Следующее работает, как ожидалось:

df <- data.frame(a = 1:5, b = 5:9)
df + 1
##   a  b
## 1 2  6
## 2 3  7
## 3 4  8

но если мы удалим имена столбцов, то поведение странно:

names(df) <- NULL
df + 1
## data frame with 0 columns and 0 rows

То же самое происходит, если имена удаляются с помощью unname, setNames. Любые идеи о том, почему это происходит, и это (по какой-то причине) ожидаемое поведение?

Редактирование: Таким образом, документировано, что безымянные data.frame имеют неподдерживаемые результаты (спасибо @neilfws, @Suren), но меня также интересует, почему это происходит. Я пытаюсь найти фактический код (?), Который делает этот простой пример тормозом.

Ответ 1

В документации для data.frame говорится:

Имена столбцов должны быть не пустыми, а попытки использовать пустые имена будут иметь неподдерживаемые результаты.

Таким образом, ожидается, что результат не может быть желательным, если имена столбцов пустые.

Ответ 2

Я думаю, что в конечном итоге это происходит из-за того, что R рассматривает объект data.frame как список со специфическими атрибутами:

## A list with no attributes
list_no_attr1 <- list(c(1,2,3), c(3,2,1))

## The attributes and class of the list
attributes(list_no_attr1)
#> NULL
class(list_no_attr1)
#> "list"

Затем мы можем вручную добавить все атрибуты data.frame без изменения структуры list:

## Adding the names to the list (not in the attributes)
list2 <- list_no_attr1
attr(list2, "names") <- c("A", "B")

## The attributes and class of the list
attributes(list2)
#> $names
#> [1] "A" "B"
class(list2)
#> "list"

## Adding the "row.names" attributes
list3 <- list2
attr(list3, "row.names") <- c("1", "2", "3")

## The attributes and class of the list
attributes(list3)
#> $names
#> [1] "A" "B"
#> $row.names
#> [1] "1" "2" "3"

class(list3)
#> "list"

Это все еще список. Теперь, когда мы изменим класс объекта на "data.frame" и затем он будет использовать метод S3 для data.frame для print и всех других связанных функций

## Adding a data.frame class attribute
list_data_frame <- list3
attr(list_data_frame, "class") <- "data.frame"

## The attributes and class of the list
attributes(list_data_frame)
#> $names
#> [1] "A" "B"
#> $row.names
#> [1] "1" "2" "3"
#> $class
#> [1] "data.frame"

class(list_data_frame)
#> "data.frame"

Теперь это будет напечатано как правильный data.frame. Обратите внимание, что он работает точно так же, как и вокруг, и может преобразовать data.frame обратно в list если мы удалим атрибут класса.

## The dataframe
data_frame <-  data.frame("A" = c(1,2,3), "B" = c(3,2,1))
## The attributes and class of the list
attributes(data_frame)
#> $names
#> [1] "A" "B"
#> $row.names
#> [1] "1" "2" "3"
#> $class
#> [1] "data.frame"

class(data_frame)
#> "data.frame"

## "Converting" into a list
attr(data_frame, "class") <- NULL

attributes(data_frame)
#> $names
#> [1] "A" "B"
#> $row.names
#> [1] "1" "2" "3"

class(data_frame)
#> "list"

Конечно, он работает только в том случае, если элементы в списке имеют одинаковую длину:

## Creating an unequal list with data.frame attributes
wrong_list <- list(c(1,2,3), c(3,2,1,0))
attr(wrong_list, "names") <- c("A", "B")
attr(wrong_list, "row.names") <- c("1", "2", "3")
attr(wrong_list, "class") <- "data.frame"

wrong_list
#>   A B
#> 1 1 3
#> 2 2 2
#> 3 3 1
#> Warning message:
#> In format.data.frame(x, digits = digits, na.encode = FALSE) :
#>   corrupt data frame: columns will be truncated or padded with NAs

И это также row.names ошибкам при отсутствии names и атрибутов row.names как указано в других комментариях и ответах на этот вопрос:

## A list coerced into a data.frame without the right attributes
wrong_list <- list(c(1,2,3), c(3,2,1))
attr(wrong_list, "class") <- "data.frame"
wrong_list
#> NULL
#> <0 rows> (or 0-length row.names)