Come si usa "<< -" (assegnazione di ambito) in R?


140

Ho appena finito di leggere di scoping nell'introduzione di R , e sono molto curioso riguardo al <<-compito.

Il manuale mostrava un esempio (molto interessante) per <<-cui sento di aver capito. Quello che mi manca ancora è il contesto in cui questo può essere utile.

Quindi ciò che mi piacerebbe leggere da te sono esempi (o collegamenti ad esempi) su quando l'uso di <<-può essere interessante / utile. Quali potrebbero essere i pericoli del suo utilizzo (sembra facile perdere la traccia di) e tutti i suggerimenti che potresti voler condividere.

Risposte:


196

<<-è più utile insieme alle chiusure per mantenere lo stato. Ecco una sezione di un mio recente articolo:

Una chiusura è una funzione scritta da un'altra funzione. Le chiusure sono così chiamate perché racchiudono l'ambiente della funzione genitore e possono accedere a tutte le variabili e parametri in quella funzione. Questo è utile perché ci consente di avere due livelli di parametri. Un livello di parametri (il genitore) controlla il funzionamento della funzione. L'altro livello (il bambino) fa il lavoro. L'esempio seguente mostra come utilizzare questa idea per generare una famiglia di funzioni di potenza. La funzione genitore ( power) crea funzioni figlio ( squaree cube) che svolgono effettivamente il duro lavoro.

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 capacità di gestire le variabili a due livelli consente anche di mantenere lo stato attraverso le invocazioni di funzioni consentendo a una funzione di modificare le variabili nell'ambiente del suo genitore. La chiave per la gestione delle variabili a diversi livelli è l'operatore di assegnazione della doppia freccia <<-. A differenza della solita assegnazione di una freccia singola (<- ) che funziona sempre al livello corrente, l'operatore a doppia freccia può modificare le variabili nei livelli padre.

Ciò consente di mantenere un contatore che registra quante volte è stata chiamata una funzione, come mostra l'esempio seguente. Ogni volta che new_counterviene eseguito, crea un ambiente, inizializza il contatore iin questo ambiente e quindi crea una nuova funzione.

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

La nuova funzione è una chiusura e il suo ambiente è l'ambiente di chiusura. Quando vengono chiuse counter_onee chiuse counter_two, ognuna modifica il contatore nel suo ambiente di chiusura e quindi restituisce il conteggio corrente.

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

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

4
Ehi, questo è un compito irrisolto di R su Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Bene, era ...
Karsten W.

1
Ci sarebbe bisogno di racchiudere più di 1 chiusure in una funzione genitore? Ho appena provato uno snippet, sembra che sia stata eseguita solo l'ultima chiusura ...
mckf111

Esiste un'alternativa di segno uguale al segno "<< -"?
Genom

38

Aiuta a pensare <<-come equivalente a assign(se si imposta il inheritsparametro in quella funzione su TRUE). Il vantaggio di assignè che permette di specificare ulteriori parametri (per esempio l'ambiente), quindi preferisco usare assignover<<- nella maggior parte dei casi.

Utilizzando <<-eassign(x, value, inherits=TRUE) significa che "gli ambienti racchiusi dell'ambiente fornito vengono cercati fino a quando non viene rilevata la variabile 'x'." In altre parole, continuerà a percorrere gli ambienti in ordine fino a quando non troverà una variabile con quel nome e lo assegnerà a quello. Questo può rientrare nell'ambito di una funzione o nell'ambiente globale.

Per capire cosa fanno queste funzioni, è necessario comprendere anche gli ambienti R (ad es. Utilizzo search).

Uso regolarmente queste funzioni quando eseguo una simulazione di grandi dimensioni e voglio salvare risultati intermedi. Ciò consente di creare l'oggetto al di fuori dell'ambito di una determinata funzione o applyciclo. Questo è molto utile, soprattutto se hai qualche preoccupazione per un loop di grandi dimensioni che termina inaspettatamente (ad esempio una disconnessione del database), nel qual caso potresti perdere tutto nel processo. Ciò equivarrebbe a scrivere i risultati in un database o in un file durante un lungo processo, tranne per il fatto che memorizza i risultati all'interno dell'ambiente R.

Il mio avvertimento principale con questo: fai attenzione perché ora stai lavorando con variabili globali, specialmente quando lo usi <<-. Ciò significa che si può finire con situazioni in cui una funzione utilizza un valore oggetto dall'ambiente, quando ci si aspettava che ne stesse usando uno fornito come parametro. Questa è una delle cose principali che la programmazione funzionale cerca di evitare (vedi effetti collaterali ). Evito questo problema assegnando i miei valori a nomi di variabili univoci (utilizzando incolla con un set o parametri univoci) che non vengono mai utilizzati all'interno della funzione, ma utilizzati solo per la memorizzazione nella cache e nel caso in cui ho bisogno di recuperare in seguito (o fare qualche meta -analisi sui risultati intermedi).


3
Grazie Tal. Ho un blog, anche se non lo uso davvero. Non posso mai finire un post perché non voglio pubblicare nulla a meno che non sia perfetto, e non ho tempo per quello ...
Shane

2
Un uomo saggio una volta mi ha detto che non è importante essere perfetti - solo in piedi - quello che sei, e così saranno i tuoi post. Inoltre, a volte i lettori aiutano a migliorare il testo con i commenti (è quello che succede con il mio blog). Spero che un giorno riconsidererai :)
Tal Galili,

9

Un posto dove ho usato <<-era nelle semplici GUI usando tcl / tk. Alcuni degli esempi iniziali lo hanno - in quanto è necessario fare una distinzione tra variabili locali e globali per la condizione di stato. Vedi per esempio

 library(tcltk)
 demo(tkdensity)

quale usa <<-. Altrimenti concordo con Marek :) - una ricerca su Google può aiutare.


Interessante, in qualche modo non riesco a trovare tkdensityin R 3.6.0.
Nelson Gon,


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

11
Questo è un buon esempio di dove non usare <<-. Un ciclo for sarebbe più chiaro in questo caso.
Hadley,

4

A questo proposito, vorrei sottolineare che l' <<-operatore si comporterà in modo strano quando applicato (in modo errato) all'interno di un ciclo for (potrebbero esserci anche altri casi). Dato il seguente codice:

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

potresti aspettarti che la funzione restituisca la somma prevista, 6, ma invece restituisce 0, con una variabile globale mySumcreata e assegnata il valore 3. Non posso spiegare completamente cosa sta succedendo qui, ma sicuramente il corpo di un for il loop non è un nuovo "livello" di ambito. Invece, sembra che R appaia al di fuori della fortestfunzione, non riesca a trovare una mySumvariabile a cui assegnare, quindi ne crea una e assegna il valore 1, la prima volta attraverso il ciclo. Nelle iterazioni successive, l'RHS nell'assegnazione deve fare riferimento alla mySumvariabile interna (invariata) mentre l'LHS si riferisce alla variabile globale. Pertanto ogni iterazione sovrascrive il valore della variabile globale sul valore di quella iterazione di i, quindi ha il valore 3 all'uscita dalla funzione.

Spero che questo aiuti qualcuno - questo mi ha lasciato per un paio d'ore oggi! (A proposito, basta sostituire <<-con <-e la funzione funziona come previsto).


2
nel tuo esempio, il locale mySumnon viene mai incrementato ma solo il globale mySum. Quindi ad ogni iterazione del ciclo for, il globale mySumottiene il valore 0 + i. Puoi seguirlo con debug(fortest).
ClementWalter,

Non ha nulla a che fare con il fatto che è un for-loop; stai facendo riferimento a due diversi ambiti. Basta usare <-ovunque in modo coerente all'interno della funzione se si desidera aggiornare solo la variabile locale all'interno della funzione.
smci,

Oppure usa << - ovunque @smci. Anche se è meglio evitare i globi.
Apprendimento delle statistiche con l'esempio del

3

L' <<-operatore può anche essere utile per le classi di riferimento durante la scrittura di metodi di riferimento . Per esempio:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.