我刚刚在 R 中弄湿了我的脚,并惊讶地发现一个函数不会修改一个对象,至少看起来这是默认值。例如,我编写了一个函数,只是为了在表格的一个标签上粘贴一个星号;它在函数内部工作,但表本身没有改变。(我主要来自Ruby)
那么,在 R 中使用函数更改对象的正常、公认的方法是什么?如何在表格标题中添加星号?
替换整个对象:
myTable = title.asterisk(myTable)
使用变通方法通过引用调用(例如,在TszKin Julian?
使用函数以外的结构?对象方法?
您遇到问题的原因是您将对象传递到函数的本地命名空间中。这是关于 R 的伟大/可怕的事情之一:它允许隐式变量声明,然后随着命名空间变得更深而实现取代。
这会影响您,因为函数会在当前命名空间中创建一个新命名空间。我假设对象“myTable”最初是在全局命名空间中创建的,但是当它被传递到函数“title.asterisk”时,一个新的函数本地命名空间现在具有一个具有相同属性的对象。这像这样工作:
title.asterisk <- function(myTable){ do some stuff to 'myTable' }
在这种情况下,函数“title.asterisk”不会对全局对象“myTable”进行任何更改。相反,会创建一个同名的本地对象,因此本地对象会取代全局对象。如果我们title.asterisk(myTable)
以这种方式调用函数,函数只会对局部变量进行更改。
有两种直接修改全局对象的方法(以及许多间接方法)。
选项 1:正如您提到的,第一个是让函数返回对象并覆盖全局对象,如下所示:
title.asterisk <- function(myTable){
do some stuff to 'myTable'
return(myTable)
}
myTable <- title.asterisk(myTable)
这没关系,但是您仍然使您的代码有点难以理解,因为实际上有两个不同的“myTable”对象,一个是全局对象,一个是函数本地对象。许多编码人员通过添加句点“。”来澄清这一点。在可变参数前面,如下所示:
title.asterisk <- function(.myTable){
do some stuff to '.myTable'
return(.myTable)
}
myTable <- title.asterisk(myTable)
好的,现在我们有一个视觉提示,这两个变量是不同的。这很好,因为当我们稍后尝试调试代码时,我们不想依赖诸如命名空间取代之类的不可见的东西。它只会让事情变得比他们必须要做的更难。
选项 2:您可以只从函数中修改对象。当您想要对对象进行破坏性编辑并且不希望内存膨胀时,这是更好的选择。如果您正在进行破坏性编辑,则无需保存原始副本。此外,如果您的对象足够大,您不想在不需要时复制它。要对全局命名空间对象进行编辑,只需不要将其传递给函数或从函数中声明它。
title.asterisk <- function(){ do some stuff to 'myTable' }
现在我们从函数中直接编辑对象“myTable”。我们没有传递对象的事实使我们的函数寻找更高级别的命名空间来尝试解析变量名。瞧,它发现了一个更高的“myTable”对象!函数中的代码对对象进行更改。
需要考虑的注意事项:我讨厌调试。我的意思是我真的很讨厌调试。这在 R 中对我来说意味着一些事情:
正如您所指出的,这两种范式正在替换整个对象,或者编写“替换”函数,例如
`updt<-` <- function(x, ..., value) {
## x is the object to be manipulated, value the object to be assigned
x$lbl <- paste0(x$lbl, value)
x
}
和
> d <- data.frame(x=1:5, lbl=letters[1:5])
> d
x lbl
1 1 a
2 2 b
3 3 c
> updt(d) <- "*"
> d
x lbl
1 1 a*
2 2 b*
3 3 c*
例如,这是$<-
--就地更新访问的元素的行为$
。这是一个相关的问题。可以将替换函数视为语法糖
updt1 <- function(x, ..., value) {
x$lbl <- paste0(x$lbl, value)
x
}
d <- updt1(d, value="*")
但在我看来,“句法糖”这个标签并不能真正体现所涉及的中心范式。它支持方便的就地更新,这与 R 通常维护的更改时复制错觉不同,它实际上是更新对象的“R”方式(而不是使用?ReferenceClasses
,例如,它具有更多其他语言的感觉,但会让期望更改时复制语义的 R 用户感到惊讶)。
对于将来寻找一种简单方法(不知道是否更合适)来解决此问题的任何人:
在函数内部创建对象以临时保存要更改的对象的修改版本。用于deparse(substitute())
获取已传递给函数参数的变量的名称,然后用于assign()
覆盖您的对象。您将需要使用envir = parent.frame()
insideassign()
让您的对象在函数外部的环境中定义。
(MyTable <- 1:10)
[1] 1 2 3 4 5 6 7 8 9 10
title.asterisk <- function(table) {
tmp.table <- paste0(table, "*")
name <- deparse(substitute(table))
assign(name, tmp.table, envir = parent.frame())
}
(title.asterisk(MyTable))
[1] “1*” “2*” “3*” “4*” “5*” “6*” “7*” “8*” “9*” “10*”
在定义对象时使用括号比定义然后打印更有效(对我来说,更好看)。