data.table setnames 在调用环境中修改对象
data.table setnames modifying object in calling environment
编辑可能的重复项:
proposed link 有助于理解问题的来源(我已经将其作为第一条评论发布)。
然而,我的问题是如何解决在函数中调用 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
没有
如果我制作了一个副本并且 "pretend to change" 但不这样做怎么办
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
更通用,当然也更有效
我认为这是预期的行为,因为 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
时,它正在更改 df1
和 df2
指向的对象。
要更改此设置,您可以创建自己的函数来显式复制 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
未被修改。
受@mike-h 解决方案的启发,这里有一个函数,它的行为类似于 setnames,但如果您在定义输入的环境之外使用 setnames,它会创建一个副本。
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
编辑可能的重复项:
proposed link 有助于理解问题的来源(我已经将其作为第一条评论发布)。
然而,我的问题是如何解决在函数中调用 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
没有
如果我制作了一个副本并且 "pretend to change" 但不这样做怎么办
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
我认为这是预期的行为,因为 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
时,它正在更改 df1
和 df2
指向的对象。
要更改此设置,您可以创建自己的函数来显式复制 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
未被修改。
受@mike-h 解决方案的启发,这里有一个函数,它的行为类似于 setnames,但如果您在定义输入的环境之外使用 setnames,它会创建一个副本。
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