如何在 R 中使用“ < <-”(范围赋值) ?

我刚刚读完 R 序言中的范围,对 <<-的作业很好奇。

手册中展示了 <<-的一个(非常有趣的)例子,我觉得我理解了。我仍然缺少的是什么时候这可以是有用的上下文。

所以我想从你们那里读到的是关于什么时候使用 <<-可以变得有趣/有用的例子(或链接到例子)。使用它可能有什么危险(它看起来很容易忘记) ,以及任何你想要分享的技巧。

79400 次浏览

我使用 <<-的一个地方是在使用 tcl/tk 的简单 GUI 中。最初的一些示例中就有这样的例子——因为需要区分局部变量和全局变量的有状态性。举个例子

 library(tcltk)
demo(tkdensity)

它使用 <<-。否则我同意马雷克:)——谷歌搜索可以帮助。

f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")

<<-视为等价于 assign会有所帮助(如果将该函数中的 inherits参数设置为 TRUE)。assign的好处是它允许您指定更多的参数(例如环境) ,所以在大多数情况下,我更喜欢使用 assign而不是 <<-

使用 <<-assign(x, value, inherits=TRUE)意味着“搜索所提供环境的封闭环境,直到遇到变量‘ x’”换句话说,它将按顺序遍历整个环境,直到找到一个具有该名称的变量,并将其赋值给该变量。这可以在函数范围内,也可以在全局环境中。

为了理解这些函数的作用,您还需要理解 R 环境(例如使用 search)。

当我运行一个大型模拟时,我经常使用这些函数,我想保存中间结果。这允许您在给定函数或 apply循环的范围之外创建对象。这非常有帮助,特别是如果您担心一个大的循环意外结束(例如数据库断开) ,在这种情况下,您可能会失去整个过程中的一切。这相当于在长时间运行的过程中将结果写入数据库或文件,只不过它将结果存储在 R 环境中。

我的主要警告是: 要小心,因为您现在使用的是全局变量,特别是在使用 <<-时。这意味着最终可能出现这样的情况: 函数使用来自环境的对象值,而您希望它使用作为参数提供的对象值。这是函数式编程试图避免的主要问题之一(参见 副作用)。为了避免这个问题,我将我的值分配给一个唯一的变量名(使用带有集合或唯一参数的粘贴) ,这个变量名从来没有在函数中使用过,只是用于缓存,以防以后需要恢复(或者对中间结果进行一些元分析)。

<<-与闭包一起维护状态非常有用。下面是我最近一篇论文的一个部分:

闭包是由另一个函数编写的函数 所谓的因为他们 附上的环境的父母 中的所有变量和参数 这是很有用的,因为它允许我们有两个层次的 一级参数(父级)控制 函数工作。另一个级别(子级)完成工作 下面的例子展示了如何使用这个想法来生成一系列 父函数(power)创建子函数 (squarecube) ,实际上做艰苦的工作。

power <- function(exponent) {
function(x) x ^ exponent
}


square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16


cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

通过允许函数修改其父级环境中的变量,在两个级别上管理变量的能力也使得跨函数调用维护状态成为可能。在不同级别管理变量的关键是双箭头赋值操作符 <<-。不像通常的单箭头赋值(<-)总是在当前级别上工作,双箭头操作符可以修改父级别中的变量。

这样就可以维护一个计数器,记录调用函数的次数,如下面的示例所示。每次运行 new_counter时,它都会创建一个环境,在这个环境中初始化计数器 i,然后创建一个新函数。

new_counter <- function() {
i <- 0
function() {
# do something useful, then ...
i <<- i + 1
i
}
}

新函数是一个闭包,它的环境是一个封闭的环境。当运行闭包 counter_onecounter_two时,每个闭包都在其封闭环境中修改计数器,然后返回当前计数。

counter_one <- new_counter()
counter_two <- new_counter()


counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1

在这个问题上,我想指出的是,当在 for 循环中应用(错误地) <<-操作符时,它的行为会很奇怪(可能还有其他情况)。根据以下守则:

fortest <- function() {
mySum <- 0
for (i in c(1, 2, 3)) {
mySum <<- mySum + i
}
mySum
}

您可能期望函数返回预期的和,6,但是它返回0,创建一个全局变量 mySum并赋值3。我不能完全解释这里发生了什么,但是可以肯定的是,for 循环的主体是 没有,一个新的范围‘级别’。相反,似乎 R 看到的是 fortest函数之外,找不到要赋值的 mySum变量,所以创建一个变量并赋值1,这是第一次通过循环。在随后的迭代中,赋值中的 RHS 必须引用(不变的)内部 mySum变量,而 LHS 引用全局变量。因此,每个迭代都将全局变量的值重写为该迭代的 i值,因此它在从函数退出时具有值3。

希望这对某些人有所帮助——今天我被难住了好几个小时!(顺便说一句,只要用 <-替换 <<-,函数就能正常工作)。

<<-操作符对于 编写引用方法时的引用类也很有用。例如:

myRFclass <- setRefClass(Class = "RF",
fields = list(A = "numeric",
B = "numeric",
C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9

我使用它是为了在全局环境中更改 map ()中的一个对象。

a = c(1,0,0,1,0,0,0,0)

假设我想得到一个向量,它是 c (1,2,3,1,2,3,4,5) ,也就是说,如果有一个1,让它1,否则加1直到下一个1。

map(
.x = seq(1,(length(a))),
.f = function(x) {
a[x] <<- ifelse(a[x]==1, a[x], a[x-1]+1)
})
a
[1] 1 2 3 1 2 3 4 5