5

我刚刚在 R 中弄湿了我的脚,并惊讶地发现一个函数不会修改一个对象,至少看起来这是默认值。例如,我编写了一个函数,只是为了在表格的一个标签上粘贴一个星号;它在函数内部工作,但表本身没有改变。(我主要来自Ruby)

那么,在 R 中使用函数更改对象的正常、公认的方法是什么?如何在表格标题中添加星号?

  • 替换整个对象:myTable = title.asterisk(myTable)

  • 使用变通方法通过引用调用(例如,在TszKin Julian?

  • 使用函数以外的结构?对象方法?

4

3 回答 3

26

您遇到问题的原因是您将对象传递到函数的本地命名空间中。这是关于 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 中对我来说意味着一些事情:

  • 我将几乎所有内容都包装在一个函数中。当我编写代码时,一旦我得到一个工作,我将它包装在一个函数中并将它放在一边。我大量使用'.' 为我所有的函数参数添加前缀,并且对它所在的命名空间的任何本机内容不使用前缀。
  • 我尽量不要从函数内部修改全局对象。我不喜欢这会导致什么。如果需要修改一个对象,我会在声明它的函数中对其进行修改。这通常意味着我有多层函数调用函数,但这使我的工作既模块化又易于理解。
  • 我注释了我的所有代码,解释了每一行或每一块的用途。这似乎有点无关,但我发现这三件事对我来说是一起的。一旦你开始在函数中包装编码,你会发现自己想要重用更多的旧代码。这就是好的评论进来的地方。对我来说,这是一个必要的部分。
于 2013-03-19T12:09:37.987 回答
10

正如您所指出的,这两种范式正在替换整个对象,或者编写“替换”函数,例如

`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 用户感到惊讶)。

于 2013-03-19T11:29:05.203 回答
1

对于将来寻找一种简单方法(不知道是否更合适)来解决此问题的任何人:

在函数内部创建对象以临时保存要更改的对象的修改版本。用于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*”

在定义对象时使用括号比定义然后打印更有效(对我来说,更好看)。

于 2018-12-02T09:21:50.733 回答