为什么as.data.frame()转换tibble时会忽略row.names参数?
as.data.frame()转换普通data.frame与tibble行为不一致的原因 问题复现
使用as.data.frame()转换对象时,传入row.names参数给普通data.frame可以正常设置自定义行名,但转换tibble时该参数完全不生效,复现代码如下:
library(tibble) aa <- data.frame(x = 1:3, y = letters[1:3]) # 普通base R数据框 bb <- as_tibble(aa) # Tibble对象 # 转换普通数据框:输出携带自定义行名,符合预期 dd <- as.data.frame(aa, row.names = aa$y) attributes(dd)$row.names ## [1] "a" "b" "c" # 转换Tibble:输出无自定义行名,行为不符合预期 ee <- as.data.frame(bb, row.names = bb$y) attributes(ee)$row.names ## [1] 1 2 3 is.character(bb$y) # 确认传入的row.names是符合要求的字符向量 ## [1] TRUE
源码层面排查
顺着S3方法派发逻辑查源码,就能定位到直接原因:两类对象调用as.data.frame()时,实际执行的是完全不同的S3方法。
library(sloop) # 查看tibble转换时的方法派发链路 s3_dispatch(as.data.frame(bb, row.names = bb$y)) ## => as.data.frame.tbl_df ## as.data.frame.tbl ## * as.data.frame.data.frame ## * as.data.frame.default # 查看tibble对应转换方法的源码 s3_get_method(as.data.frame.tbl_df) ## function (x, row.names = NULL, optional = FALSE, ...) ## { ## class(x) <- tibble_class ## unname <- which(!map_lgl(x, is_bare_list)) ## x[unname] <- map(.subset(x, unname), vectbl_set_names, NULL) ## class(x) <- "data.frame" ## x ## } ## <bytecode: 0x000001e57b5e3840> ## <environment: namespace:tibble> # 查看普通数据框转换时的方法派发链路 s3_dispatch(as.data.frame(aa, row.names = aa$y)) ## => as.data.frame.data.frame ## * as.data.frame.default # 查看普通数据框对应转换方法的源码 s3_get_method(as.data.frame.data.frame) ## function (x, row.names = NULL, ...) ## { ## cl <- oldClass(x) ## i <- match("data.frame", cl) ## if (i > 1L) ## class(x) <- cl[-(1L:(i - 1L))] ## if (!is.null(row.names)) { ## nr <- .row_names_info(x, 2L) ## if (length(row.names) == nr) ## attr(x, "row.names") <- row.names ## else stop(sprintf(ngettext(nr, "invalid 'row.names', length %d for a data frame with %d row", ## "invalid 'row.names', length %d for a data frame with %d rows"), ## length(row.names), nr), domain = NA) ## } ## x ## } ## <bytecode: 0x000001e57b5ee0a8> ## <environment: namespace:base>
对比两份源码就能看到:tibble包提供的as.data.frame.tbl_df()方法虽然形参里写了row.names = NULL,但整个函数体根本没用到这个参数,自然不会给输出结果设置自定义行名;而base R提供的as.data.frame.data.frame()方法会主动读取row.names参数,给结果设置对应的行名属性。
设计逻辑解释
这个差异不是代码bug,是tibble包的刻意设计,核心原因是tibble从设计原则上就不认可行名这个特性:
- tibble遵循整洁数据规范,所有和观测相关的信息都应该存为普通数据列。行名是base R数据框遗留的历史设计缺陷:本质是把一列数据存在了对象的特殊属性里,不遵守普通列的索引、拼接、修改规则,做数据处理的时候很容易莫名其妙丢失行名、或者出现行名匹配错位,踩坑概率极高。
- tibble本身就完全不支持自定义行名,不管用什么方式给tibble设置行名都会被直接忽略,它的行名永远是默认的1、2、3……整数位置索引。
- 既然开发团队认为行名本身就不该被使用,自然不会在
as.data.frame.tbl_df()方法里实现处理row.names参数的逻辑,哪怕形参里留了这个参数位置,也只是为了匹配通用函数的形参规范避免报错,实际不会做任何处理。
如果确实需要给tibble转换出的普通数据框设置行名,在转换完成后单独赋值即可:
ee <- as.data.frame(bb) row.names(ee) <- bb$y
内容的提问来源于stack exchange,提问作者Shivam7898
相关产品推荐
相关产品推荐

