Wie verwendet man "<< -" (Scoping-Zuweisung) in R?

140

Ich habe gerade das Scoping im R-Intro gelesen und bin sehr gespannt auf die <<-Aufgabe.

Das Handbuch zeigte ein (sehr interessantes) Beispiel, für <<-das ich mich verstanden zu fühlen glaube. Was mir noch fehlt, ist der Kontext, in dem dies nützlich sein kann.

Was ich gerne von Ihnen lesen würde, sind Beispiele (oder Links zu Beispielen), wann die Verwendung von <<-interessant / nützlich sein kann. Was könnten die Gefahren bei der Verwendung sein (es sieht leicht aus, den Überblick zu verlieren) und welche Tipps Sie möglicherweise teilen möchten.

Tal Galili
quelle

Antworten:

195

<<-ist am nützlichsten in Verbindung mit Verschlüssen, um den Zustand aufrechtzuerhalten. Hier ist ein Abschnitt aus einem kürzlich erschienenen Artikel von mir:

Ein Abschluss ist eine Funktion, die von einer anderen Funktion geschrieben wurde. Closures werden so genannt, weil sie die Umgebung der übergeordneten Funktion einschließen und auf alle Variablen und Parameter in dieser Funktion zugreifen können. Dies ist nützlich, da wir zwei Parameterebenen haben können. Eine Parameterebene (die übergeordnete) steuert die Funktionsweise der Funktion. Die andere Ebene (das Kind) erledigt die Arbeit. Das folgende Beispiel zeigt, wie mit dieser Idee eine Familie von Potenzfunktionen generiert werden kann. Die übergeordnete Funktion ( power) erstellt untergeordnete Funktionen ( squareund cube), die tatsächlich die harte Arbeit erledigen.

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

Die Möglichkeit, Variablen auf zwei Ebenen zu verwalten, ermöglicht es auch, den Status über Funktionsaufrufe hinweg aufrechtzuerhalten, indem eine Funktion Variablen in der Umgebung ihres übergeordneten Elements ändern kann. Der Schlüssel zum Verwalten von Variablen auf verschiedenen Ebenen ist der Doppelpfeil-Zuweisungsoperator <<-. Im Gegensatz zur üblichen Einzelpfeilzuweisung ( <-), die immer auf der aktuellen Ebene funktioniert, kann der Doppelpfeiloperator Variablen in übergeordneten Ebenen ändern.

Auf diese Weise kann ein Zähler verwaltet werden, der aufzeichnet, wie oft eine Funktion aufgerufen wurde, wie das folgende Beispiel zeigt. Bei jeder new_counterAusführung wird eine Umgebung erstellt, der Zähler iin dieser Umgebung initialisiert und anschließend eine neue Funktion erstellt.

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

Die neue Funktion ist ein Abschluss, und ihre Umgebung ist die einschließende Umgebung. Wenn die Verschlüsse counter_oneund counter_twoausgeführt werden, die jeweils ändert der Zähler in seiner umschließenden Umgebung und gibt dann die aktuelle Zählung.

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

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
Hadley
quelle
4
Hey, das ist eine ungelöste R-Aufgabe auf Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Nun, es war ...
Karsten W.
1
Müsste es mehr als 1 Verschlüsse in einer übergeordneten Funktion einschließen? Ich habe nur einen Ausschnitt ausprobiert, es scheint, dass nur der letzte Abschluss ausgeführt wurde ...
mckf111
Gibt es eine Gleichheitsalternative zum "<< -" - Zeichen?
Genom
38

Es ist hilfreich, sich das <<-als äquivalent zu vorstellen assign(wenn Sie den inheritsParameter in dieser Funktion auf setzen TRUE). Der Vorteil assignist , dass es Ihnen erlaubt , mehr Parameter (zB Umwelt) zu spezifizieren, so dass ich verwenden lieber assignüber <<-in den meisten Fällen.

Verwenden von <<-und assign(x, value, inherits=TRUE)bedeutet, dass "umschließende Umgebungen der bereitgestellten Umgebung durchsucht werden, bis die Variable 'x' angetroffen wird." Mit anderen Worten, es wird die Umgebungen so lange durchlaufen, bis es eine Variable mit diesem Namen findet, und es wird dieser zugewiesen. Dies kann im Rahmen einer Funktion oder in der globalen Umgebung erfolgen.

Um zu verstehen, was diese Funktionen tun, müssen Sie auch R-Umgebungen verstehen (z search. B. mit ).

Ich verwende diese Funktionen regelmäßig, wenn ich eine große Simulation ausführe und Zwischenergebnisse speichern möchte. Auf diese Weise können Sie das Objekt außerhalb des Bereichs der angegebenen Funktion oder applySchleife erstellen . Dies ist sehr hilfreich, insbesondere wenn Sie Bedenken haben, dass eine große Schleife unerwartet endet (z. B. eine Trennung der Datenbank). In diesem Fall können Sie dabei alles verlieren. Dies entspricht dem Schreiben Ihrer Ergebnisse in eine Datenbank oder Datei während eines langen Prozesses, außer dass die Ergebnisse stattdessen in der R-Umgebung gespeichert werden.

Meine Hauptwarnung dazu: Seien Sie vorsichtig, da Sie jetzt mit globalen Variablen arbeiten, insbesondere bei der Verwendung <<-. Dies bedeutet, dass Sie in Situationen geraten können, in denen eine Funktion einen Objektwert aus der Umgebung verwendet, wenn Sie erwartet haben, dass sie einen als Parameter angegebenen Wert verwendet. Dies ist eines der wichtigsten Dinge, die durch funktionale Programmierung vermieden werden sollen (siehe Nebenwirkungen ). Ich vermeide dieses Problem, indem ich meine Werte eindeutigen Variablennamen zuweise (mithilfe von Einfügen mit einem Satz oder eindeutigen Parametern), die niemals in der Funktion verwendet werden, sondern nur zum Zwischenspeichern und für den Fall, dass ich sie später wiederherstellen muss (oder ein Meta erstellen muss) -Analyse der Zwischenergebnisse).

Shane
quelle
3
Danke Tal. Ich habe einen Blog, obwohl ich ihn nicht wirklich benutze. Ich kann einen Beitrag nie beenden, weil ich nichts veröffentlichen möchte, es sei denn, es ist perfekt, und ich habe einfach keine Zeit dafür ...
Shane
2
Ein weiser Mann hat mir einmal gesagt, es sei nicht wichtig, perfekt zu sein - nur im Stehen - was du bist, und deine Beiträge auch. Außerdem helfen Leser manchmal dabei, den Text mit den Kommentaren zu verbessern (das passiert mit meinem Blog). Ich hoffe, eines Tages werden Sie es sich noch einmal überlegen :)
Tal Galili
9

Ein Ort, an dem ich verwendet habe, <<-waren einfache GUIs mit tcl / tk. Einige der ersten Beispiele haben es - da Sie für die Zustandsfülle zwischen lokalen und globalen Variablen unterscheiden müssen. Siehe zum Beispiel

 library(tcltk)
 demo(tkdensity)

welche verwendet <<-. Ansonsten stimme ich Marek zu :) - eine Google-Suche kann helfen.

Dirk Eddelbuettel
quelle
Interessant, ich kann irgendwie nicht tkdensityin R 3.6.0 finden.
NelsonGon
1
Das tcltk-Paket wird mit R: github.com/wch/r-source/blob/trunk/src/library/tcltk/demo/…
geliefert
5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
lcgong
quelle
11
Dies ist ein gutes Beispiel dafür, wo Sie es nicht verwenden sollten <<-. Eine for-Schleife wäre in diesem Fall klarer.
Hadley
4

Zu diesem Thema möchte ich darauf hinweisen, dass sich der <<-Operator seltsam verhält, wenn er (falsch) innerhalb einer for-Schleife angewendet wird (es kann auch andere Fälle geben). Gegeben den folgenden Code:

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

Sie können erwarten, dass die Funktion die erwartete Summe 6 zurückgibt, aber stattdessen 0 zurückgibt, wobei eine globale Variable mySumerstellt und der Wert 3 zugewiesen wird. Ich kann nicht vollständig erklären, was hier vor sich geht, aber sicherlich den Körper eines for Schleife ist kein neuer Bereich 'Ebene'. Stattdessen scheint R außerhalb der fortestFunktion mySumzu schauen , kann keine Variable zum Zuweisen finden, erstellt also eine und weist beim ersten Durchlaufen der Schleife den Wert 1 zu. Bei nachfolgenden Iterationen muss sich die RHS in der Zuweisung auf die (unveränderte) innere mySumVariable beziehen, während sich die LHS auf die globale Variable bezieht. Daher überschreibt jede Iteration den Wert der globalen Variablen auf den Wert dieser Iteration von i, daher hat sie beim Beenden der Funktion den Wert 3.

Hoffe das hilft jemandem - das hat mich heute für ein paar Stunden verblüfft! (BTW, ersetzen Sie einfach <<-mit <-und die Funktion funktioniert wie erwartet).

Matthew Wise
quelle
2
In Ihrem Beispiel wird das Lokale mySumnie erhöht, sondern nur das Globale mySum. Daher mySumerhält der Globale bei jeder Iteration der for-Schleife den Wert 0 + i. Sie können dies mit folgen debug(fortest).
ClementWalter
Es hat nichts damit zu tun, dass es sich um eine For-Schleife handelt. Sie verweisen auf zwei verschiedene Bereiche. Verwenden Sie einfach <-überall innerhalb der Funktion konsistent, wenn Sie nur die lokale Variable innerhalb der Funktion aktualisieren möchten.
smci
Oder verwenden Sie << - überall @smci. Obwohl am besten Globals zu vermeiden.
Lernstatistiken am Beispiel
3

Der <<-Operator kann auch für Referenzklassen beim Schreiben von Referenzmethoden hilfreich sein . Beispielsweise:

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
quelle