2011-09-24 6 views
8

Tengo antecedentes de Java y Python y estoy aprendiendo R recientemente.¿Cómo maneja R el objeto en la llamada de función?

Hoy he encontrado que R parece manejar objetos de forma bastante diferente a Java y Python.

Por ejemplo, el siguiente código:

x <- c(1:10) 
print(x) 
sapply(1:10,function(i){ 
      x[i] = 4 
     }) 
print(x) 

El código da el siguiente resultado:

[1] 1 2 3 4 5 6 7 8 9 10 
[1] 1 2 3 4 5 6 7 8 9 10 

Pero espero que la segunda línea de salida sea todo '4' ya he modificado el vector en la función Sapply.

¿Significa esto que R hace copias de objetos en llamada de función en lugar de referencia a los objetos?

Respuesta

18

x se define en el entorno global, no en su función.

Si intenta modificar un objeto que no es local, como x en una función, entonces R hace una copia del objeto y modifica la copia por lo que cada vez que se ejecuta su función anónima se hace una copia de x y su componente i-ésimo se establece en 4. Cuando la función sale, la copia que se realizó desaparece para siempre. El original x no se modifica.

Si tuviéramos que escribir x[i] <<- i o si tuviéramos que escribir x[i] <- 4; assign("x", x, .GlobalEnv), entonces R lo escribiría de nuevo. Otra manera de escribir de nuevo sería establecer e, por ejemplo, con el medio ambiente que x se almacena en y hacer esto:

e <- environment() 
sapply(1:10, function(i) e$x[i] <- 4) 

o posiblemente esto:

sapply(1:10, function(i, e) e$x[i] <- 4, e = environment()) 

Normalmente uno no escribe tales código en R. Más bien se produce el resultado que la salida de la función así:

x <- sapply(1:10, function(i) 4) 

(en realidad, en este caso se podría escribir x[] <- 4)

añadido:.

Uso de la proto package uno podría hacer esto donde método f coloca la i-ésima componente de la propiedad x a 4.

library(proto) 

p <- proto(x = 1:10, f = function(., i) .$x[i] <- 4) 

for(i in seq_along(p$x)) p$f(i) 
p$x 

añadido:.

Agregado por encima de otra opción en la que se pasa explícitamente el entorno que x se almacena en

+0

Gracias! Pero ¿por qué no uno escribe ese código en R? ¿Hay algún riesgo potencial o solo una convención? Creo que es bastante normal modificar objetos globales en una función en otros idiomas. –

+3

En las funciones de idiomas funcionales no pueden tener efectos secundarios. R no es tan estricto, pero sigue siendo cierto que las funciones R limitan los efectos secundarios. Es mejor trabajar de la manera que se pretendía que fuera en lugar de tratar de escribir como si estuvieras escribiendo en otro idioma. Hay varios sistemas de objetos (S3, S4, Clases de referencia). S3 es el más utilizado. S4 es mucho más complejo. Las clases de referencia son una adición reciente. Es posible que desee explorar las clases de referencia, en particular. También hay algunos paquetes aportados por los usuarios que ofrecen diferentes paradigmas: proto y R.oo (y posiblemente otros). –

+0

@Spirit Plus puede usar 'parent.frame (3)' en lugar de '.GlobalEnv' para almacenar x en un cierre en el que se ejecuta sapply, lo que sería mucho más seguro. (¿Por qué marco de función 3? 1-anónimo, marco de 2 sapply, recinto de 3 sapply) – mbq

7

Sí, tienes razón. Compruebe la definición de lenguaje R: 4.3.3 Argument Evaluation

AFAIK, R realmente no copia los datos hasta que intenta modificarlos, siguiendo así la semántica Copy-on-write.

+0

Gracias Anatoliy! Pero, ¿los procesos de copia tomarían demasiado tiempo y memoria si los datos copiados son realmente grandes? ¿O no copia los datos, simplemente neutraliza el efecto de modificación al final de la llamada de función? –

+0

Hay una copia, pero la "x" dentro de la función no es el mismo objeto que la que está fuera de la función. Hay entornos y tiene una x en el entorno de llamada y una x diferente en el entorno de la función. Solo mediante la asignación del resultado serán visibles los cambios en el entorno de llamada. –

0

Tiene que asignar la salida de sapply a un objeto, de lo contrario, simplemente desaparece. (En realidad se puede recuperarlo, ya que también se le asigna a .Last.value)

x <- c(1:10) 
print(x) 
[1] 1 2 3 4 5 6 7 8 9 10 
x <- sapply(1:10,function(i){ 
      x[i] = 4 
     }) 
print(x) 
[1] 4 4 4 4 4 4 4 4 4 4 
+0

Lo siento, pero esto no responde la pregunta y es confuso. – mbq

+0

Ahora yo soy el confundido. El OP creó un vector de 4 y luego no hizo nada con él. Si quería que "x" cambiara, necesitaba usar una operación de asignación. Pensé que respondí exactamente la pregunta. –

+0

Parece sugerir que 'v <-sapply (..., function (...) {... v ...})' construye de alguna manera las exportaciones 'v' del entorno de funciones al padre. No mencionó que la pregunta era bastante directa sobre si R hace una llamada por copia o llamada por referencia. – mbq

3

El x que está dentro de la función anónima es no la x en el entorno global (su espacio de trabajo). Es una copia de x, local para la función anónima. No es tan simple decir que R copia objetos en llamadas a funciones; R se esforzará por no copiar si puede, aunque una vez que modifique algo R tendrá que copiar el objeto.

Como @DWin señala, esta versión copiada de x que ha sido modificado es devuelto por la llamada sapply(), la salida solicitada no es lo que me sale:

> x <- c(1:10) 
> print(x) 
[1] 1 2 3 4 5 6 7 8 9 10 
> sapply(1:10,function(i){ 
+    x[i] = 4 
+   }) 
[1] 4 4 4 4 4 4 4 4 4 4 
> print(x) 
[1] 1 2 3 4 5 6 7 8 9 10 

Claramente, el código no hizo casi lo que pensaste que sería El problema es que la salida de sapply() no se asignó a un objeto y, por lo tanto, se imprime y luego se descarta.

La razón por la que su código incluso funciona se debe a las reglas de alcance de R. Realmente debería pasar a una función como argumentos de cualquier objeto que la función necesite. Sin embargo, si R no puede encontrar un objeto local para la función, buscará en el entorno primario un objeto que coincida con el nombre, y luego el elemento primario de ese entorno, si corresponde, hasta llegar al entorno global, el espacio de trabajo. Por lo tanto, su código funciona porque finalmente encontró un x para trabajar, pero se copió inmediatamente, esa copia volvió al final de la llamada sapply().

Esta copia toma tiempo y memoria en muchos casos. Esta es una de las razones por las cuales las personas piensan que los bucles for son lentos en R; no asignan almacenamiento a un objeto antes de llenarlo con un bucle. Si no asigna almacenamiento, R tiene que modificar/copiar el objeto para agregar el siguiente resultado del ciclo.

Otra vez sin embargo, no siempre es así de simple, en todas partes en R, por ejemplo, con los ambientes, en los que una copia de un entorno realmente sólo se refiere a la versión original:

> a <- new.env() 
> a 
<environment: 0x1af2ee0> 
> b <- 4 
> assign("b", b, env = a) 
> a$b 
[1] 4 
> c <- a ## copy the environment to `c` 
> assign("b", 7, env = c) ## assign something to `b` in env `c` 
> c$b ## as expected 
[1] 7 
> a$b ## also changed `b` in `a` as `a` and `c` are actually the same thing 
[1] 7 

Si usted entiende este tipo de cosas, leyendo el manual R Language Definition que cubre muchos de los detalles de lo que sucede debajo del capó en R.

+0

¡Uy! - Escribí esto hace horas (mañana, hora del Reino Unido) pero debe haberse desviado y no hacer clic en el botón Enviar. –

0

Si desea cambiar un objeto "global" dentro de una función, puede utilizar una asignación no local.

x <- c(1:10) 
# [1] 1 2 3 4 5 6 7 8 9 10 
print(x) 
sapply(1:10,function(i){ 
      x[i] <<- 4 
     }) 
print(x) 
# [1] 4 4 4 4 4 4 4 4 4 4 

Aunque en este caso particular, usted podría tener más compacta como x[]<-4

Eso es, por cierto, una de las características interesantes de R - en lugar de sapply(1:10,function(i) x[i] <<- 4 o for(i in 1:10) x[i]<-4 (for no es una función, por lo que no necesita <<- aquí) puede escribir x[]<-4 :)

Cuestiones relacionadas