¿Cómo se usa "<< -" (asignación de alcance) en R?

140

Acabo de leer sobre el alcance en la introducción de R , y tengo mucha curiosidad sobre la <<-tarea.

El manual mostró un ejemplo (muy interesante) para <<-, que creo que entendí. Lo que aún me falta es el contexto de cuándo esto puede ser útil.

Entonces, lo que me gustaría leer de usted son ejemplos (o enlaces a ejemplos) sobre cuándo el uso de <<-puede ser interesante / útil. Cuáles podrían ser los peligros de usarlo (parece fácil perder el rastro) y cualquier consejo que desee compartir.

Tal Galili
fuente

Respuestas:

196

<<-es más útil junto con cierres para mantener el estado. Aquí hay una sección de un artículo mío reciente:

Un cierre es una función escrita por otra función. Los cierres se denominan así porque encierran el entorno de la función principal y pueden acceder a todas las variables y parámetros de esa función. Esto es útil porque nos permite tener dos niveles de parámetros. Un nivel de parámetros (el padre) controla cómo funciona la función. El otro nivel (el niño) hace el trabajo. El siguiente ejemplo muestra cómo puede usar esta idea para generar una familia de funciones de poder. La función principal ( power) crea funciones secundarias ( squarey cube) que realmente hacen el trabajo duro.

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

La capacidad de administrar variables en dos niveles también hace posible mantener el estado en las invocaciones de funciones al permitir que una función modifique variables en el entorno de su padre. La clave para administrar variables en diferentes niveles es el operador de asignación de doble flecha <<-. A diferencia de la asignación de flecha única habitual ( <-) que siempre funciona en el nivel actual, el operador de flecha doble puede modificar las variables en los niveles principales.

Esto permite mantener un contador que registra cuántas veces se ha llamado a una función, como muestra el siguiente ejemplo. Cada vez que new_counterse ejecuta, crea un entorno, inicializa el contador ien este entorno y luego crea una nueva función.

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

La nueva función es un cierre, y su entorno es el entorno envolvente. Cuando se ejecutan los cierres counter_oney counter_two, cada uno modifica el contador en su entorno cerrado y luego devuelve el conteo actual.

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

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
Hadley
fuente
44
Hola, esta es una tarea R sin resolver en Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Bueno, fue ...
Karsten W.
1
¿Sería necesario encerrar más de 1 cierres en una función principal? Acabo de probar un fragmento, parece que solo se ejecutó el último cierre ...
mckf111
¿Existe alguna alternativa de signo igual al signo "<< -"?
Genom
38

Es útil pensar que <<-es equivalente a assign(si establece el inheritsparámetro en esa función en TRUE). La ventaja de assignes que le permite especificar más parámetros (por ejemplo, el medio ambiente), así que prefiero utilizar assignmás <<-en la mayoría de los casos.

El uso de <<-y assign(x, value, inherits=TRUE)significa que "los entornos del entorno suministrado se buscan hasta que se encuentra la variable 'x'". En otras palabras, seguirá pasando por los entornos en orden hasta que encuentre una variable con ese nombre, y se la asignará a eso. Esto puede estar dentro del alcance de una función, o en el entorno global.

Para comprender lo que hacen estas funciones, también debe comprender los entornos R (por ejemplo, el uso search).

Regularmente uso estas funciones cuando ejecuto una simulación grande y quiero guardar resultados intermedios. Esto le permite crear el objeto fuera del alcance de la función o applybucle dado . Eso es muy útil, especialmente si le preocupa que un ciclo grande termine inesperadamente (por ejemplo, una desconexión de la base de datos), en cuyo caso podría perder todo en el proceso. Esto sería equivalente a escribir sus resultados en una base de datos o archivo durante un proceso de larga ejecución, excepto que está almacenando los resultados dentro del entorno R.

Mi advertencia principal con esto: tenga cuidado porque ahora está trabajando con variables globales, especialmente cuando lo usa <<-. Eso significa que puede terminar con situaciones en las que una función está usando un valor de objeto del entorno, cuando esperaba que estuviera usando uno que se proporcionó como parámetro. Esta es una de las cosas principales que la programación funcional intenta evitar (ver efectos secundarios ). Evito este problema asignando mis valores a nombres de variables únicos (usando pegar con un conjunto o parámetros únicos) que nunca se usan dentro de la función, pero solo se usan para el almacenamiento en caché y en caso de que necesite recuperarme más tarde (o hacer algún meta -análisis sobre los resultados intermedios).

Shane
fuente
3
Gracias tal. Tengo un blog, aunque realmente no lo uso. Nunca puedo terminar una publicación porque no quiero publicar nada a menos que sea perfecto, y simplemente no tengo tiempo para eso ...
Shane
2
Un hombre sabio me dijo una vez que no es importante ser perfecto, solo sobresaliente, lo que eres, y también lo serán tus publicaciones. Además, a veces los lectores ayudan a mejorar el texto con los comentarios (eso es lo que sucede con mi blog). Espero que algún día lo reconsideres :)
Tal Galili
9

Un lugar donde lo usé <<-fue en GUI simples usando tcl / tk. Algunos de los ejemplos iniciales lo tienen, ya que necesita hacer una distinción entre las variables locales y globales para la capacidad de estado. Ver por ejemplo

 library(tcltk)
 demo(tkdensity)

que usos <<-. De lo contrario, estoy de acuerdo con Marek :): una búsqueda en Google puede ayudar.

Dirk Eddelbuettel
fuente
Interesante, de alguna manera no puedo encontrar tkdensityen R 3.6.0.
NelsonGon
1
El paquete tcltk se envía con R: github.com/wch/r-source/blob/trunk/src/library/tcltk/demo/…
Dirk Eddelbuettel el
5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
lcgong
fuente
11
Este es un buen ejemplo de dónde no usar <<-. Un bucle for sería más claro en este caso.
hadley
4

Sobre este tema, me gustaría señalar que el <<-operador se comportará de manera extraña cuando se aplique (incorrectamente) dentro de un bucle for (también puede haber otros casos). Dado el siguiente código:

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

puede esperar que la función devuelva la suma esperada, 6, pero en cambio devuelve 0, con una variable global que mySumse crea y se le asigna el valor 3. No puedo explicar completamente lo que está sucediendo aquí, pero ciertamente el cuerpo de a para loop no es un nuevo 'nivel' de alcance. En cambio, parece que R mira fuera de la fortestfunción, no puede encontrar una mySumvariable para asignar, por lo que crea una y asigna el valor 1, la primera vez a través del ciclo. En iteraciones posteriores, el RHS en la asignación debe referirse a la mySumvariable interna (sin cambios) mientras que el LHS se refiere a la variable global. Por lo tanto, cada iteración sobrescribe el valor de la variable global al valor de esa iteración i, por lo tanto, tiene el valor 3 al salir de la función.

Espero que esto ayude a alguien, ¡esto me dejó perplejo durante un par de horas hoy! (Por cierto, simplemente reemplace <<-con <-y la función funciona como se esperaba).

Matthew Wise
fuente
2
en su ejemplo, lo local mySumnunca se incrementa sino solo lo global mySum. Por lo tanto, en cada iteración del ciclo for, el global mySumobtiene el valor 0 + i. Puedes seguir esto con debug(fortest).
ClementWalter
No tiene nada que ver con que sea un bucle for; estás haciendo referencia a dos ámbitos diferentes. Simplemente use en <-todas partes de manera consistente dentro de la función si solo desea actualizar la variable local dentro de la función.
smci
O use << - en todas partes @smci. Aunque es mejor evitar los globales.
Estadísticas de aprendizaje por ejemplo
3

El <<-operador también puede ser útil para las clases de referencia al escribir métodos de referencia . Por ejemplo:

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
Carlos Cinelli
fuente