Currently, data.frame provides and argument row.names that not only allows for supplying row names to be used for the new data frame but also allows for specifying a column whose values to use as row names:
| row.names |
NULL or a single integer or character string specifying a column to be used as row names, or a character or integer vector giving the row names for the data frame. |
However, in the case of a single row data frame, there is no way to distinguish these two meanings, leading to inconsistent behavior:
data.frame(id=paste0("row", 1:2), val=1:2) |> data.frame(row.names="id")
# val
# row1 1
# row2 2
data.frame(id=paste0("row", 1:1), val=1:1) |> data.frame(row.names="id")
# id val
# id row1 1
To prevent the resulting edge-case bugs, developers have to avoid this feature and instead use code such as rownames(df) <- df$id; df$id <- NULL. or :
col_as_rownames <- function(data, col.as.row.names) {
stopifnot(length(col.as.row.names)==1)
idx <- if(is.numeric(col.as.row.names)) col.as.row.names else which(colnames(data) == col.as.row.names)
data.frame(data[, -idx, drop = FALSE], row.names=data[, idx])
}
data.frame(id=paste0("row", 1:2), val=1:2) |> col_as_rownames("id")
data.frame(id=paste0("row", 1:1), val=1:1) |> col_as_rownames("id")
Currently,
data.frameprovides and argumentrow.namesthat not only allows for supplying row names to be used for the new data frame but also allows for specifying a column whose values to use as row names:However, in the case of a single row data frame, there is no way to distinguish these two meanings, leading to inconsistent behavior:
To prevent the resulting edge-case bugs, developers have to avoid this feature and instead use code such as
rownames(df) <- df$id; df$id <- NULL.or :?base::data.frameshould be updated to caution against usingrow.nameswith a column name or index and point to an safe alternativebase::data.frameshould provide an argumentcol.as.row.namesor so to achieve the same in a simple and safe way.