data.table setnames在调用环境中修改对象

时间:2017-07-11 13:39:16

标签: r data.table pass-by-reference

关于可能重复的编辑:

proposed link有助于了解问题的来源(我已将其作为第1条评论发布)。 但是我的问题是如何解决在函数中调用setnames的具体问题,而不仅仅是了解正在发生的事情,而不是直接在那里解决。使用copy可能是一种选择,可能还有其他选择。

到目前为止,这个问题有2条赞成没有评论,请说出来如果我可以改进它。

data.tables::setnames通过引用修改值,在下面的情况下,它似乎会导致意外行为(至少对我来说意外)。

我找到了一种处理它的丑陋方式,但可能有更好的系统方法,所以我希望你的建议。

奇怪的行为

df1 <- data.frame(a=1,b="x")
f1 <- function(df2){
  setnames(df2,"b","c")
  df2
}
f1(df1)
#   a c
# 1 1 x
df1
#   a c
# 1 1 x

df1已被修改

如果我复制该怎么办?

df1 <- data.frame(a=1,b="x")
f1 <- function(df2){
 df3 <- df2
 setnames(df3,"b","c")
 df3
}
f1(df1)
#   a c
# 1 1 x
df1
#   a c
# 1 1 x

没了

如果我制作副本并“假装改变”但不要

df1 <- data.frame(a=1,b="x")
f1 <- function(df2){
  df3 <- subset(df2)    # note: it doesn't work with `identity`, EDIT: we can also use `data.table::copy`
  setnames(df3,"b","c")
  df3
}
f1(df1)
#   a c
# 1 1 x
df1
#   a b
# 1 1 x

它有效

我该怎么做?这是一个错误吗?

编辑:我发现data.table有一个copy函数比我使用subset

更通用,更有效率

2 个答案:

答案 0 :(得分:2)

我认为这是预期的行为,因为df2指向与df1相同的对象。您可以通过以下方式看到:

library(pryr)
library(data.table)

df1 <- data.frame(a=1,b="x")
address(df1)
#[1] "0000000002892318" (will be different for others)

f_address<-function(df2) print(address(df2))
f_address(df1)
#[1] "0000000002892318"

由于setnames会按引用更改输入,因此在更改df2时,它会更改df1df2指向的对象。

要更改此设置,您可以创建自己的功能,明确复制df1然后修改它:

setnames_copy <- function(x, old, new){ 
  y <- copy(x)
  setnames(y, old, new) 
}

f2 <- function(df2){
  setnames_copy(df2, "b", "c")
}
df3 <- f2(df1)
df3
#  a c
#1 1 x

df1
#  a b
#1 1 x

如您所见,df1未经修改。

答案 1 :(得分:0)

受@ mike-h解决方案的启发,这里的功能类似于设置名称,除非您在定义输入的环境之外使用setnames创建副本。 / p>

library(pryr)
setnames2 <- function(`$$`,old,new,verbose = FALSE){
  var_name <- as.character(substitute(`$$`))
  calling_env_objects  <- sapply(ls(envir=parent.frame(n=2)),. %>% get(envir=parent.frame(n=2)) %>% address)
  copied_object <- calling_env_objects[calling_env_objects == address(`$$`)]
  if(length(copied_object) ==0){          # if the address isn't used in the calling environment's calling environment
    if(verbose){cat("use regular setnames\n")}
    setnames(`$$`,old,new)
  } else if (length(copied_object) ==1){  # if the address is already in use in the calling environment's calling environment
    if(verbose){cat("create copy then use setnames on it and assign to relevant environment\n")}
    y <- copy(`$$`);setnames(y, old, new);assign(var_name,y,envir=parent.frame())
  } else {print("you shouldn't go around naming objects `$$`...")}
}

df1 <- data.frame(a=1,b="x")
f3 <- function(df2){
  setnames2(df2,"b","c",verbose=TRUE)
  print(df2)
}
f3(df1)
# create copy then use setnames on it and assign to relevant environment
#   a c
# 1 1 x
df1
#   a b
# 1 1 x    

f4 <- function(df2){
  df2 <- copy(df2)
  setnames2(df2,"b","c",verbose=TRUE)
  print(df2)
}
f4(df1)
# use regular setnames
#   a c
# 1 1 x
df1
#   a b
# 1 1 x