Jak używasz „<< -” (przypisanie zakresu) w R?

140

Właśnie skończyłem czytać o scopingu we wstępie R i jestem bardzo ciekawy <<-zadania.

Podręcznik zawierał jeden (bardzo interesujący) przykład <<-, który, jak sądzę, zrozumiałem. Wciąż brakuje mi kontekstu, kiedy może to być przydatne.

Dlatego chciałbym przeczytać od Ciebie przykłady (lub linki do przykładów), kiedy użycie <<-może być interesujące / przydatne. Jakie mogą być niebezpieczeństwa związane z używaniem go (wydaje się, że łatwo jest stracić orientację) i wszelkie wskazówki, którymi możesz się podzielić.

Tal Galili
źródło

Odpowiedzi:

196

<<-jest najbardziej przydatna w połączeniu z zamknięciami w celu utrzymania stanu. Oto sekcja z mojej ostatniej pracy:

Zamknięcie to funkcja zapisana przez inną funkcję. Zamknięcia są tak nazywane, ponieważ obejmują środowisko funkcji nadrzędnej i mają dostęp do wszystkich zmiennych i parametrów tej funkcji. Jest to przydatne, ponieważ pozwala nam mieć dwa poziomy parametrów. Jeden poziom parametrów (rodzic) kontroluje działanie funkcji. Drugi poziom (dziecko) wykonuje pracę. Poniższy przykład pokazuje, jak można wykorzystać ten pomysł do wygenerowania rodziny funkcji potęgowych. Funkcja nadrzędna ( power) tworzy funkcje potomne ( squarei cube), które faktycznie wykonują ciężką pracę.

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

Możliwość zarządzania zmiennymi na dwóch poziomach umożliwia również utrzymanie stanu między wywołaniami funkcji, umożliwiając funkcji modyfikowanie zmiennych w środowisku jej rodzica. Kluczem do zarządzania zmiennymi na różnych poziomach jest operator przypisania podwójnej strzałki <<-. W przeciwieństwie do zwykłego przypisania pojedynczej strzałki ( <-), które zawsze działa na bieżącym poziomie, operator podwójnej strzałki może modyfikować zmienne na poziomach nadrzędnych.

Umożliwia to utrzymanie licznika, który rejestruje, ile razy funkcja została wywołana, jak pokazano w poniższym przykładzie. Za każdym razem, gdy new_counterjest uruchamiany, tworzy środowisko, inicjuje licznik iw tym środowisku, a następnie tworzy nową funkcję.

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

Nowa funkcja to zamknięcie, a jej otoczenie to otaczające środowisko. Kiedy zamknięcia counter_onei counter_twosą uruchamiane, każdy z nich modyfikuje licznik w swoim otaczającym środowisku, a następnie zwraca bieżącą liczbę.

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

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
hadley
źródło
4
Hej, to jest nierozwiązane zadanie R na Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Cóż, to było ...
Karsten W.
1
Czy byłaby potrzeba zawarcia więcej niż jednego zamknięcia w jednej funkcji nadrzędnej? Właśnie wypróbowałem jeden fragment, wygląda na to, że wykonano tylko ostatnie zamknięcie ...
mckf111
Czy istnieje alternatywny znak równości dla znaku „<< -”?
Genom
38

Dobrze jest myśleć o tym <<-jako o ekwiwalencie assign(jeśli ustawisz inheritsparametr w tej funkcji na TRUE). Korzyścią assignjest to, że pozwala na określenie większej liczby parametrów (np środowisko), więc wolę używać assignciągu <<-w większości przypadków.

Użycie <<-i assign(x, value, inherits=TRUE)oznacza, że ​​„otaczające środowiska podanego środowiska są przeszukiwane do momentu napotkania zmiennej 'x'”. Innymi słowy, będzie przechodzić przez środowiska po kolei, dopóki nie znajdzie zmiennej o tej nazwie i przypisze ją do niej. Może to być w zakresie funkcji lub w środowisku globalnym.

Aby zrozumieć, do czego służą te funkcje, musisz również zrozumieć środowiska języka R (np. Używanie search).

Regularnie używam tych funkcji, gdy prowadzę dużą symulację i chcę zapisać wyniki pośrednie. Pozwala to na utworzenie obiektu poza zakresem danej funkcji lub applypętli. Jest to bardzo pomocne, szczególnie jeśli masz jakiekolwiek obawy, że duża pętla zakończy się nieoczekiwanie (np. Rozłączenie bazy danych), w którym to przypadku możesz stracić wszystko w procesie. Byłoby to równoważne z zapisywaniem wyników do bazy danych lub pliku podczas długotrwałego procesu, z wyjątkiem tego, że zamiast tego przechowuje wyniki w środowisku języka R.

Moje podstawowe ostrzeżenie: bądź ostrożny, ponieważ teraz pracujesz ze zmiennymi globalnymi, zwłaszcza gdy używasz <<-. Oznacza to, że możesz skończyć z sytuacjami, w których funkcja używa wartości obiektu ze środowiska, gdy spodziewasz się, że używa wartości, która została dostarczona jako parametr. Jest to jedna z głównych rzeczy, których programowanie funkcjonalne stara się unikać (patrz efekty uboczne ). Unikam tego problemu, przypisując moje wartości do unikalnych nazw zmiennych (za pomocą wklejania z ustawionymi lub unikalnymi parametrami), które nigdy nie są używane w funkcji, ale są używane tylko do buforowania i na wypadek, gdybym musiał później odzyskać (lub zrobić kilka meta -analiza wyników pośrednich).

Shane
źródło
3
Dzięki Tal. Mam bloga, chociaż tak naprawdę go nie używam. Nigdy nie mogę dokończyć postu, ponieważ nie chcę publikować niczego, chyba że jest idealny, a po prostu nie mam na to czasu ...
Shane
2
Pewien mądry człowiek powiedział mi kiedyś, że nie jest ważne, aby być doskonałym - tylko wyróżniającym się - kim jesteś, i Twoje stanowiska też będą. Ponadto - czasami czytelnicy pomagają ulepszyć tekst za pomocą komentarzy (tak się dzieje z moim blogiem). Mam nadzieję, że któregoś dnia ponownie się zastanowisz :)
Tal Galili
9

Jednym miejscem, w którym korzystałem, <<-były proste GUI używające tcl / tk. Niektóre z początkowych przykładów mają to - ponieważ musisz dokonać rozróżnienia między zmiennymi lokalnymi i globalnymi dla zachowania stanu. Zobacz na przykład

 library(tcltk)
 demo(tkdensity)

który używa <<-. Inaczej zgadzam się z Markiem :) - może pomóc wyszukiwarka Google.

Dirk Eddelbuettel
źródło
Co ciekawe, jakoś nie mogę znaleźć tkdensityw R 3.6.0.
NelsonGon
1
Pakiet tcltk jest dostarczany z R: github.com/wch/r-source/blob/trunk/src/library/tcltk/demo/ ...
Dirk Eddelbuettel
5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
lcgong
źródło
11
To jest dobry przykład tego, gdzie nie używać <<-. W tym przypadku pętla for byłaby wyraźniejsza.
hadley
4

W związku z tym chciałbym zwrócić uwagę, że <<-operator będzie zachowywał się dziwnie, gdy zostanie zastosowany (nieprawidłowo) w pętli for (mogą być też inne przypadki). Biorąc pod uwagę następujący kod:

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

można by się spodziewać, że funkcja zwróci oczekiwaną sumę, 6, ale zamiast tego zwraca 0, z utworzoną zmienną globalną mySumi przypisaną jej wartością 3. Nie mogę w pełni wyjaśnić, co się tutaj dzieje, ale z pewnością treść funkcji for pętla nie jest nowym „poziomem” zakresu. Zamiast tego wydaje się, że R wygląda poza fortestfunkcją, nie może znaleźć mySumzmiennej do przypisania, więc tworzy ją i przypisuje wartość 1, za pierwszym razem w pętli. W kolejnych iteracjach RHS w przypisaniu musi odnosić się do (niezmienionej) mySumzmiennej wewnętrznej , podczas gdy LHS odnosi się do zmiennej globalnej. Dlatego każda iteracja nadpisuje wartość zmiennej globalnej na wartość tej iteracji i, stąd przy wyjściu z funkcji ma wartość 3.

Mam nadzieję, że to komuś pomoże - dzisiaj zdumiało mnie to na kilka godzin! (BTW, po prostu wymień<<- z <-utworów, a funkcja zgodnie z oczekiwaniami).

Matthew Wise
źródło
2
w twoim przykładzie lokalna mySumnigdy nie jest zwiększana, ale tylko globalna mySum. Stąd w każdej iteracji pętli for, globalny mySumotrzymuje wartość 0 + i. Możesz to śledzić za pomocą debug(fortest).
ClementWalter
Nie ma to nic wspólnego z tym, że jest pętlą for; odnosisz się do dwóch różnych zakresów. Po prostu używaj <-wszędzie konsekwentnie w funkcji, jeśli chcesz tylko zaktualizować zmienną lokalną wewnątrz funkcji.
smci
Lub użyj << - wszędzie @smci. Chociaż najlepiej unikać globali.
Statystyki uczenia się na przykładzie
3

<<-Operator może być również przydatna dla klas Reference pisząc metod referencyjnych . Na przykład:

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
źródło