为何用names()<-修改列名后data.table未按引用传递?
问题描述
我们知道R语言有4种提供可变对象或按引用传递语义的数据类型,data.table是其中之一(参考《R最佳编码实践》第11章)。以下代码中,创建了一个为输入data.table添加列的函数,由于data.table默认按引用传递,原对象应被修改。创建两个相同的data.table,分别用names()<-和setnames()方法将列名改为大写后传入函数,结果dt1未新增newvar列,dt2却正常新增,请问这是为什么?
library(data.table) f <- function(x){ x[,newvar:=2020] } dt1 <- data.table(a=1:20,b=21:40) dt2 <- data.table(a=1:20,b=21:40) names(dt1) <- toupper(names(dt1)) setnames(dt2,toupper(names(dt2))) names(dt1) class(dt1) str(dt1) names(dt2) class(dt2) str(dt2) f(dt1) f(dt2) names(dt1) names(dt2)
原因解析
核心差异在于两种列名修改方式对原data.table对象的处理逻辑完全不同:
names()<-会创建新对象,切断引用链:
基础R的names()<-是通用赋值操作,它本质上会生成一个新的data.table对象,再把这个新对象重新赋值给dt1变量。此时全局环境中的dt1已经指向了新的内存地址,和最初创建的data.table实例没有关联了。当函数f尝试通过引用修改时,实际修改的是函数内部临时指向的旧实例,全局环境的dt1不会同步变化。setnames()是原地修改,保留引用关系:setnames()是data.table包专属的原地修改函数,它直接在原data.table对象的内存空间里修改列名,不会创建新对象,dt2始终指向同一个实例。所以函数f通过引用添加列时,全局环境中的dt2会直接反映出修改结果。
可以用address(dt1)和address(dt2)查看内存地址验证:修改列名后,dt1的地址会发生变化,而dt2的地址保持不变。
内容的提问来源于stack exchange,提问作者MEcon
相关产品推荐
相关产品推荐

