Jakie są różnice między operatorami przypisania „=” i „<-” w R?

712

Jakie są różnice między operatorami przypisania =a <-R?

Wiem, że operatorzy różnią się nieco, jak pokazuje ten przykład

x <- y <- 5
x = y = 5
x = y <- 5
x <- y = 5
# Error in (x <- y) = 5 : could not find function "<-<-"

Ale czy to jedyna różnica?

csgillespie
źródło
45
Jak wspomniano tutaj, pochodzenie <-symbolu pochodzi od starych klawiatur APL, które faktycznie miały jeden <-klawisz.
joran

Odpowiedzi:

96

Jakie są różnice między operatorami przypisania =a <-R?

Jak pokazuje twój przykład =i <-mają nieco inny priorytet dla operatora (który określa kolejność obliczeń, gdy są mieszane w tym samym wyrażeniu). W rzeczywistości ?Syntaxw R podaje następującą tabelę pierwszeństwa operatorów, od najwyższej do najniższej:

…
‘-> ->>’           rightwards assignment
‘<- <<-’           assignment (right to left)=’                assignment (right to left)

Ale czy to jedyna różnica?

Ponieważ pytałeś o operatory przypisania : tak, to jedyna różnica. Jednakże wybaczono by ci, że wierzysz inaczej. Nawet dokumentacja R ?assignOpstwierdzi, że istnieje więcej różnic:

Operator <-może być używany w dowolnym miejscu, podczas gdy operator =jest dozwolony tylko na najwyższym poziomie (np. W pełnym wyrażeniu wpisanym w wierszu polecenia) lub jako jedno z podwyrażeń na usztywnionej liście wyrażeń.

Nie podkreślmy zbyt dobrze: dokumentacja R jest (subtelnie) błędna [ 1 ] . Łatwo to pokazać: musimy po prostu znaleźć kontrprzykład =operatora, który nie jest (a) na najwyższym poziomie, ani (b) podwyrażenie na usztywnionej liście wyrażeń (tj {…; …}.). - Bez ceregieli:

x
# Error: object 'x' not found
sum((x = 1), 2)
# [1] 3
x
# [1] 1

Wyraźnie wykonaliśmy zadanie, używając =poza kontekstami (a) i (b). Dlaczego więc dokumentacja podstawowej funkcji języka R jest błędna przez dziesięciolecia?

To dlatego, że w składni R symbol =ma dwa różne znaczenia, które rutynowo się łączą:

  1. Pierwsze znaczenie to operator przypisania . To wszystko, o czym do tej pory rozmawialiśmy.
  2. Drugim znaczeniem nie jest operator, lecz token składniowy, który sygnalizuje nazwany argument przekazujący wywołanie funkcji. W przeciwieństwie do =operatora, nie wykonuje żadnych działań w czasie wykonywania, zmienia jedynie sposób, w jaki wyrażenie jest analizowane.

Zobaczmy.

W dowolnym kodzie ogólnej formy…

‹function_name›(‹argname› = ‹value›,)
‹function_name›(‹args›, ‹argname› = ‹value›,)

... The =jest znak, który definiuje nazwany argumentem uchwala: jest to nie operator przypisania. Ponadto =jest całkowicie zabronione w niektórych kontekstach składniowych:

if (‹var› = ‹value›) …
while (‹var› = ‹value›) …
for (‹var› = ‹value› in ‹value2›) …
for (‹var1› in ‹var2› = ‹value›) …

Każdy z nich spowoduje błąd „nieoczekiwany” = „w ‹bla›”.

W każdym innym kontekście =odnosi się do wywołania operatora przypisania. W szczególności samo umieszczenie nawiasów wokół podwyrażenia powoduje, że dowolne z powyższych (a) jest ważne i (b) przypisanie . Na przykład następujące zadania wykonuje przypisanie:

median((x = 1 : 10))

Ale również:

if (! (nf = length(from))) return()

Teraz możesz sprzeciwić się, że taki kod jest okropny (i możesz mieć rację). Ale wziąłem ten kod z base::file.copyfunkcji (zastąpienie <-z =) - jest to wzór wszechobecne w dużej części kodzie rdzeń R.

Oryginalne wyjaśnienie przez John Chambers , którego dokumentacja R jest prawdopodobnie w oparciu o właściwie wyjaśnia to poprawnie:

[ =przypisanie jest dozwolone] tylko w dwóch miejscach gramatyki: na najwyższym poziomie (jako kompletny program lub wyrażenie wpisane przez użytkownika); a po odizolowaniu od otaczającej struktury logicznej za pomocą nawiasów klamrowych lub dodatkowej pary nawiasów.


Wyznanie: skłamałem wcześniej. Tam jest jeden dodatkowy różnica między =i <-operatorów: nazywają różne funkcje. Domyślnie te funkcje robią to samo, ale można zastąpić każdą z nich osobno, aby zmienić zachowanie. Natomiast <-i ->(przypisanie od lewej do prawej), chociaż odrębne pod względem składniowym, zawsze wywołuje tę samą funkcję. Przesłonięcie jednego powoduje również zastąpienie drugiego. Wiedza o tym jest rzadko praktyczna, ale może być wykorzystana do zabawy z shenaniganami .

Konrad Rudolph
źródło
1
Jeśli chodzi o pierwszeństwo i błędy w dokumencie R., pierwszeństwo ?jest w rzeczywistości pomiędzy =i <-, co ma ważne konsekwencje przy nadpisywaniu ? i praktycznie żadne inne.
Moody_Mudskipper
@Moody_Mudskipper, który jest dziwaczny! Wygląda na to, że masz rację, ale zgodnie z kodem źródłowym ( main/gram.y) priorytet ?jest poprawnie udokumentowany i jest niższy niż zarówno, jak =i <-.
Konrad Rudolph
Nie mówię w C, ale przypuszczam, że =otrzymają specjalne traktowanie przed zbudowaniem parsowania. Być może związane z argumentami funkcji, ma sens, foo(x = a ? b)że szukamy =przed analizą reszty wyrażenia.
Moody_Mudskipper
@Moody_Mudskipper Poprosiłem r-devel
Konrad Rudolph
2
@Moody_Mudskipper FWIW zostało to ostatecznie naprawione w wersji 4.0.0.
Konrad Rudolph
661

Różnica między operatorami przypisania jest wyraźniejsza, gdy używasz ich do ustawiania wartości argumentu w wywołaniu funkcji. Na przykład:

median(x = 1:10)
x   
## Error: object 'x' not found

W takim przypadku xjest deklarowany w ramach funkcji, więc nie istnieje w obszarze roboczym użytkownika.

median(x <- 1:10)
x    
## [1]  1  2  3  4  5  6  7  8  9 10

W takim przypadku xjest zadeklarowany w obszarze roboczym użytkownika, dzięki czemu można go używać po zakończeniu wywołania funkcji.


Istnieje ogólna preferencja wśród społeczności R, aby używać <-przypisania (innego niż w sygnaturach funkcji) dla zgodności z (bardzo) starymi wersjami S-Plus. Pamiętaj, że spacje pomagają wyjaśnić sytuacje takie jak

x<-3
# Does this mean assignment?
x <- 3
# Or less than?
x < -3

Większość R IDE ma skróty klawiaturowe, aby <-ułatwić pisanie. Ctrl+ =w Architect, Alt+ -w RStudio ( Option+ -pod macOS), Shift+ -(podkreślenie) w emacs + ESS.


Jeśli wolisz pisać =, aby <-jednak móc korzystać z bardziej wspólny symbol przypisania dla kodu publicznie wydany (na CRAN, na przykład), a następnie można użyć jednej z tidy_*funkcji w formatRpakiecie, aby automatycznie zastąpić =z <-.

library(formatR)
tidy_source(text = "x=1:5", arrow = TRUE)
## x <- 1:5

Odpowiedź na pytanie „Dlaczego x <- y = 5zgłasza błąd, ale nie x <- y <- 5?” to „To zależy od magii zawartej w parserze”. Składnia R zawiera wiele niejednoznacznych przypadków , które należy rozwiązać w ten czy inny sposób. Analizator składni wybiera rozstrzyganie bitów wyrażenia w różnych porządkach w zależności od tego, =czy <-zostało użyte.

Aby zrozumieć, co się dzieje, musisz wiedzieć, że przypisanie po cichu zwraca przypisaną wartość. Możesz to zobaczyć wyraźniej, na przykład poprzez wyraźne drukowanie print(x <- 2 + 3).

Po drugie, jest wyraźniej, jeśli do przypisania użyjemy notacji przedrostkowej. Więc

x <- 5
`<-`(x, 5)  #same thing

y = 5
`=`(y, 5)   #also the same thing

Analizator składni interpretuje x <- y <- 5jako

`<-`(x, `<-`(y, 5))

Możemy spodziewać się, że x <- y = 5będzie wówczas

`<-`(x, `=`(y, 5))

ale tak naprawdę jest interpretowane jako

`=`(`<-`(x, y), 5)

Jest tak, ponieważ =ma niższy priorytet niż <-, jak pokazano na stronie ?Syntaxpomocy.

Richie Cotton
źródło
4
Jest to również wspomniane w rozdziale 8.2.26 Piekła R autorstwa Patricka Burnsa (i tak nie ja, ale zalecenie)
Uwe
3
Jednak median((x = 1:10))ten sam efekt co median(x <- 1:10).
Francesco Napolitano
2
tak naprawdę nie uważam ich za skróty, w każdym razie naciskasz tę samą liczbę klawiszy
yosemite_k
5
Właśnie zdałem sobie sprawę, że twoje wyjaśnienie, w jaki sposób x <- x = 5jest interpretowane, jest nieco błędne: w rzeczywistości R interpretuje to jako ​`<-<-`(x, y = 5, value = 5)(co samo w sobie jest mniej więcej równoważne tmp <- x; x <- `<-<-`(tmp, y = 5, value = 5)). Yikes!
Konrad Rudolph
4
… I właśnie zdałem sobie sprawę, że pierwsza część tej odpowiedzi jest niepoprawna i niestety dość myląca, ponieważ utrwala powszechne nieporozumienie: sposób, =w jaki używasz w wywołaniu funkcji , nie wykonuje przypisania i nie jest operatorem przypisania. Jest to całkowicie odrębne sparsowane wyrażenie R, które akurat używa tego samego znaku. Ponadto wyświetlany kod nie „deklaruje” xw zakresie funkcji. W deklaracji funkcji wykonuje powiedział oświadczenie. Wywołanie funkcji nie działa (staje się nieco bardziej skomplikowane w przypadku nazwanych ...argumentów).
Konrad Rudolph
103

Przewodnik po stylu R firmy Google upraszcza ten problem, zakazując przypisania „=”. Niezły wybór.

https://google.github.io/styleguide/Rguide.xml

Instrukcja R zawiera szczegółowy opis wszystkich 5 operatorów przypisania.

http://stat.ethz.ch/R-manual/R-pched/library/base/html/assignOps.html

Nosredna
źródło
133
Minusem przypadkowego przypisania, x<-ykiedy x < -ymiał na myśli, denerwuje mnie tak bardzo, że osobiście wolę =. Uzależnienie twojego kodu od obecności białych znaków nie wydaje mi się dobre. Można sugerować odstępy jako porady dotyczące stylu, ale aby kod działał inaczej, niezależnie od tego, czy jest tam spacja, czy nie? Co się stanie, jeśli sformatujesz kod lub użyjesz funkcji wyszukiwania i zamiany, białe znaki mogą czasem zniknąć, a kod nie działa. To nie jest problem =. IIUC, zakaz =jest równoznaczny z wymaganiem „ <- ”; tj. 3 znaki łącznie ze spacją, nie tylko „ <-”.
Matt Dowle,
12
Zauważ, że każdy inny niż 0 jest brany TRUEpod uwagę przez R. Więc jeśli zamierzasz sprawdzić, czy xjest mniejszy -y, możesz napisać, if (x<-y)który nie będzie ostrzegał ani nie popełnił błędu, i wydaje się, że działa dobrze. Ale tylko FALSEwtedy y=0.
Matt Dowle,
4
Jeśli zakazujesz =i używasz <- , trudno argumentować, że dodatkowy krok grep "[^<]<-[^ ]" *.Rnie jest potrzebny. =nie potrzebuje takiego grep.
Matt Dowle
34
Po co ranić oczy i palec, <-jeśli możesz użyć =? W 99,99% przypadków =jest w porządku. Czasem jednak potrzebujesz <<-innej historii.
Fernando
10
Skupienie się na <- jest być może jedną z kulawych przyczyn braku + = i - =.
Chris
37

x = y = 5jest równoważne x = (y = 5), ponieważ operatory przypisania „grupują” od prawej do lewej, co działa. Znaczenie: przypisz 5 do y, pozostawiając cyfrę 5; a następnie przypisz 5 do x.

To nie to samo (x = y) = 5, co nie działa! Znaczenie: przypisz wartość ydo x, pozostawiając wartość y; a następnie przypisz 5 do, umm ..., co dokładnie?

Po zmieszaniu różnych rodzajów operatorów przypisania <-wiąże się mocniej niż =. Tak x = y <- 5jest interpretowane jako x = (y <- 5), co ma sens.

Niestety x <- y = 5jest interpretowany jako (x <- y) = 5, co jest przypadkiem, który nie działa!

Zobacz ?Syntaxi ?assignOpsdla pierwszeństwa (wiązanie) i grupowania reguł.

Steve Pitchers
źródło
Tak, jak powiedziała odpowiedź Konrada Rudolfa , <- <<-znajduje się powyżej = tabeli pierwszeństwa, co oznacza, że <-zostanie ona wykonana jako pierwsza. Tak więc x <- y = 5należy wykonać jako (x <- y) = 5.
Nick Dong
1
@Nick Dong Tak, rzeczywiście. Pomocnie, tabela priorytetów operatora jest jednoznacznie udokumentowana w ? Składni {base} .
Steve Pitchers
33

Według Johna Chambersa operator =jest dozwolony tylko na „najwyższym poziomie”, co oznacza, że ​​nie jest dozwolony w strukturach kontrolnych takich jak if, co powoduje, że następujący błąd programowania jest nielegalny.

> if(x = 0) 1 else x
Error: syntax error

Jak pisze: „Odrzucenie nowego formularza przypisania [=] w wyrażeniach kontrolnych pozwala uniknąć błędów programistycznych (takich jak powyższy przykład), które są bardziej prawdopodobne w przypadku równego operatora niż w przypadku innych przypisań S.”

Możesz to zrobić, jeśli jest „odizolowany od otaczającej struktury logicznej za pomocą nawiasów klamrowych lub dodatkowej pary nawiasów”, więc if ((x = 0)) 1 else xdziałałoby.

Zobacz http://developer.r-project.org/equalAssign.html

Aaron opuścił Stack Overflow
źródło
11
To powszechny błąd, który x==0prawie zawsze ma na myśli.
Aaron opuścił Stack Overflow
14
Ach, tak, przeoczyłem, że powiedziałeś „błąd programowania”. To właściwie dobra wiadomość, że powoduje to błąd. I dobry powód, aby preferować x=0jako zadanie x<-0!
Steve Pitchers
7
Tak, fajnie, że powoduje to błąd, choć wyciągam inną lekcję na temat tego, co preferować; Wybieram używanie =jak najmniej, ponieważ =i ==wyglądam tak podobnie.
Aaron opuścił stos przepełnienia
2
Sposób przedstawienia tego przykładu jest dla mnie taki dziwny. if(x = 0) 1 else xzgłasza błąd, pomagając mi znaleźć i naprawić błąd.if(x <- 1) 1 else xnie zgłasza błędu i jest bardzo mylące.
Gregor Thomas
3
To znaczy, naprawdę pomocny moduł sprawdzania błędów rzuciłby tam błąd i powiedziałby „masz bezużyteczny kod, który zawsze zwróci elsewartość, czy chciałeś napisać ją w ten sposób?”, Ale może to być marzenie o fajce…
TylerH
26

Operatory <-i =przypisują do środowiska, w którym są oceniane. Operator<- może być używany w dowolnym miejscu, podczas gdy operator =jest dozwolony tylko na najwyższym poziomie (np. W pełnym wyrażeniu wpisanym w wierszu polecenia) lub jako jedno z podwyrażeń na usztywnionej liście wyrażeń.

Haim Evgi
źródło
8
Myślę, że „najwyższy poziom” oznacza raczej na poziomie instrukcji niż na poziomie wyrażenia. Tak więc samo x <- 42w sobie jest stwierdzeniem; w if (x <- 42) {}byłoby wyrazem, a nie jest ważny. Żeby było jasne, nie ma to nic wspólnego z tym, czy jesteś w środowisku globalnym, czy nie.
Steve Pitchers
1
To: „operator = jest dozwolony tylko na najwyższym poziomie” jest powszechnie uznawanym nieporozumieniem i całkowicie błędnym.
Konrad Rudolph
To nie jest prawda - na przykład to działa, mimo że przypisanie nie jest pełnym wyrażeniem:1 + (x = 2)
Pavel Minaev
1
Aby wyjaśnić komentarze Konrada Rudolpha i Pawła Minajewa, uważam, że jest zbyt silny, aby powiedzieć, że jest całkowicie błędny, ale istnieje wyjątek, który występuje, gdy jest „odizolowany od otaczającej struktury logicznej, za pomocą nawiasów klamrowych lub dodatkowej pary nawiasów”.
Aaron opuścił Stack Overflow
Lub function() x = 1, repeat x = 1, if (TRUE) x = 1....
Moody_Mudskipper
6

Może to również przyczynić się do zrozumienia różnicy między tymi dwoma operatorami:

df <- data.frame(
      a = rnorm(10),
      b <- rnorm(10)
)

Dla pierwszego elementu R przypisał wartości i prawidłową nazwę, natomiast nazwa drugiego elementu wygląda nieco dziwnie.

str(df)
# 'data.frame': 10 obs. of  2 variables:
#  $ a             : num  0.6393 1.125 -1.2514 0.0729 -1.3292 ...
#  $ b....rnorm.10.: num  0.2485 0.0391 -1.6532 -0.3366 1.1951 ...

Wersja R 3.3.2 (31.10.2016); macOS Sierra 10.12.1

Denis Rasulev
źródło
6
czy możesz podać bardziej szczegółowe wyjaśnienie, dlaczego tak się dzieje / co się tutaj dzieje? (wskazówka: data.framepróbuje użyć nazwy podanej zmiennej jako nazwy elementu w ramce danych)
Ben Bolker
Pomyślałem, czy to może być błąd? A jeśli tak, jak i gdzie to zgłosić?
Denis Rasulev
7
to nie błąd. Próbowałem wskazać odpowiedź na powyższy komentarz. Podczas ustawiania nazwy elementu R użyje odpowiednika make.names("b <- rnorm(10)").
Ben Bolker