Quelles sont les différences entre les opérateurs d'affectation «=» et «<-» dans R?

712

Quelles sont les différences entre les opérateurs d'affectation =et <-dans R?

Je sais que les opérateurs sont légèrement différents, comme le montre cet exemple

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

Mais est-ce la seule différence?

csgillespie
la source
45
Comme indiqué ici, les origines du <-symbole proviennent d'anciens claviers APL qui avaient en fait une seule <-clé.
joran

Réponses:

97

Quelles sont les différences entre les opérateurs d'affectation =et <-dans R?

Comme le montre votre exemple, =et <-ont une priorité d'opérateur légèrement différente (qui détermine l'ordre d'évaluation lorsqu'ils sont mélangés dans la même expression). En fait, ?Syntaxdans R donne la table de priorité d'opérateur suivante, du plus haut au plus bas:

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

Mais est-ce la seule différence?

Puisque vous posiez des questions sur les opérateurs d'affectation : oui, c'est la seule différence. Cependant, vous seriez pardonné de croire le contraire. Même la documentation R ?assignOpsaffirme qu'il y a plus de différences:

L'opérateur <-peut être utilisé n'importe où, alors qu'il =n'est autorisé qu'au niveau supérieur (par exemple, dans l'expression complète tapée à l'invite de commande) ou comme l'une des sous-expressions dans une liste d'expressions contreventée.

Ne précisons pas trop: la documentation R est (subtilement) fausse [ 1 ] . C'est facile à montrer: il suffit de trouver un contre-exemple de l' =opérateur qui n'est pas (a) au niveau supérieur, ni (b) une sous-expression dans une liste d'expressions contreventée (ie {…; …}). - Sans plus tarder:

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

De toute évidence, nous avons effectué une affectation, en utilisant =, en dehors des contextes (a) et (b). Alors, pourquoi la documentation d'une fonctionnalité de base du langage R est-elle erronée depuis des décennies?

C'est parce que dans la syntaxe de R, le symbole =a deux significations distinctes qui sont systématiquement confondues:

  1. La première signification est en tant qu'opérateur d'affectation . C'est tout ce dont nous avons parlé jusqu'à présent.
  2. La deuxième signification n'est pas un opérateur mais plutôt un jeton de syntaxe qui signale un argument nommé passant dans un appel de fonction. Contrairement à l' =opérateur, il n'exécute aucune action au moment de l'exécution, il modifie simplement la façon dont une expression est analysée.

Voyons voir.

Dans n'importe quel morceau de code de la forme générale…

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

… Le =est le jeton qui définit le passage d'argument nommé: ce n'est pas l'opérateur d'affectation. De plus, =est totalement interdit dans certains contextes syntaxiques:

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

N'importe lequel de ces éléments générera une erreur «inattendu '=' dans ‹bla›».

Dans tout autre contexte, =fait référence à l'appel d'opérateur d'affectation. En particulier, le simple fait de mettre des parenthèses autour de la sous-expression rend tout ce qui précède (a) valide et (b) une affectation . Par exemple, ce qui suit effectue l'affectation:

median((x = 1 : 10))

Mais aussi:

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

Maintenant, vous pourriez objecter qu'un tel code est atroce (et vous avez peut-être raison). Mais j'ai pris ce code de la base::file.copyfonction (en le remplaçant <-par =) - c'est un modèle omniprésent dans une grande partie de la base de code R principale.

L' explication originale de John Chambers , sur laquelle la documentation R est probablement basée, explique en fait cela correctement:

[ =affectation] n'est autorisée qu'à deux endroits de la grammaire: au niveau supérieur (en tant que programme complet ou expression saisie par l'utilisateur); et lorsqu'ils sont isolés de la structure logique environnante, par des accolades ou une paire supplémentaire de parenthèses.


Une confession: j'ai menti plus tôt. Il existe une différence supplémentaire entre les opérateurs =et <-: ils appellent des fonctions distinctes. Par défaut, ces fonctions font la même chose mais vous pouvez les remplacer séparément pour changer le comportement. En revanche, <-et ->(affectation de gauche à droite), bien que syntaxiquement distincts, appellent toujours la même fonction. Remplacer l'un remplace également l'autre. Le savoir est rarement pratique, mais il peut être utilisé pour des manigances amusantes .

Konrad Rudolph
la source
1
À propos de la priorité et des erreurs dans le document de R, la priorité de ?est en fait juste entre =et <-, ce qui a des conséquences importantes lors de la substitution ? , et pratiquement aucune autrement.
Moody_Mudskipper
@Moody_Mudskipper c'est bizarre! Vous semblez avoir raison, mais selon le code source ( main/gram.y), la priorité de ?est correctement documentée et est inférieure aux deux =et <-.
Konrad Rudolph
Je ne parle pas C mais je suppose que =vous obtenez un traitement spécial avant que l'arbre d'analyse soit construit. Peut-être lié aux arguments de fonction, il est logique que foo(x = a ? b)nous recherchions =avant d'analyser le reste de l'expression.
Moody_Mudskipper
@Moody_Mudskipper J'ai demandé à r-devel
Konrad Rudolph
2
@Moody_Mudskipper FWIW ceci est finalement corrigé dans 4.0.0.
Konrad Rudolph
661

La différence entre les opérateurs d'affectation est plus claire lorsque vous les utilisez pour définir une valeur d'argument dans un appel de fonction. Par exemple:

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

Dans ce cas, xest déclaré dans le cadre de la fonction, il n'existe donc pas dans l'espace de travail utilisateur.

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

Dans ce cas, xest déclaré dans l'espace de travail utilisateur, vous pouvez donc l'utiliser une fois l'appel de fonction terminé.


Il existe une préférence générale au sein de la communauté R pour l'utilisation <-pour l'affectation (autre que dans les signatures de fonction) pour la compatibilité avec les (très) anciennes versions de S-Plus. Notez que les espaces aident à clarifier des situations comme

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

La plupart des IDE R ont des raccourcis clavier pour <-faciliter la saisie. Ctrl+ =dans Architect, Alt+ -dans RStudio ( Option+ -sous macOS), Shift+ -(souligné) dans emacs + ESS.


Si vous préférez écrire =dans <-mais souhaitez utiliser le symbole d'affectation le plus courant pour le code publié publiquement (sur CRAN, par exemple), vous pouvez utiliser l'une des tidy_*fonctions du formatRpackage pour le remplacer automatiquement =par <-.

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

La réponse à la question "Pourquoi x <- y = 5jette une erreur mais pas x <- y <- 5?" est "C'est dû à la magie contenue dans l'analyseur". La syntaxe de R contient de nombreux cas ambigus qui doivent être résolus d'une manière ou d'une autre. L'analyseur choisit de résoudre les bits de l'expression dans des ordres différents selon qu'il a été utilisé =ou non <-.

Pour comprendre ce qui se passe, vous devez savoir que l'affectation renvoie silencieusement la valeur qui a été affectée. Vous pouvez le voir plus clairement en imprimant explicitement, par exemple print(x <- 2 + 3).

Deuxièmement, il est plus clair si nous utilisons la notation de préfixe pour l'affectation. Donc

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

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

L'analyseur interprète x <- y <- 5comme

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

On pourrait s'attendre à ce que ce x <- y = 5soit alors

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

mais en fait, il est interprété comme

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

Cela est dû au =fait que la priorité est inférieure à <-, comme indiqué sur la ?Syntaxpage d'aide.

Richie Cotton
la source
4
Ceci est également mentionné dans le chapitre 8.2.26 de The R Inferno de Patrick Burns (Pas moi mais une recommandation quand même)
Uwe
3
Cependant, median((x = 1:10))a le même effet que median(x <- 1:10).
Francesco Napolitano
2
je ne les considère pas vraiment comme des raccourcis, en tout cas vous appuyez sur le même nombre de touches
yosemite_k
5
Je viens de réaliser que votre explication sur la façon dont x <- x = 5est interprété est légèrement erronée: en réalité, R l'interprète comme ​`<-<-`(x, y = 5, value = 5)(ce qui lui-même est plus ou moins équivalent à tmp <- x; x <- `<-<-`(tmp, y = 5, value = 5)). Oui!
Konrad Rudolph
4
… Et je viens de réaliser que la toute première partie de cette réponse est incorrecte et, malheureusement, trompeuse car elle perpétue une idée fausse courante: la façon dont vous utilisez =dans un appel de fonction n'effectue pas d'affectation et n'est pas un opérateur d'affectation. C'est une expression R analysée entièrement distincte, qui se trouve utiliser le même caractère. De plus, le code que vous montrez ne «déclare» pas xdans le cadre de la fonction. La déclaration de fonction effectue ladite déclaration. L'appel de fonction ne fonctionne pas (il devient un peu plus compliqué avec des ...arguments nommés ).
Konrad Rudolph
103

Le guide de style R de Google simplifie le problème en interdisant le "=" pour l'attribution. Pas un mauvais choix.

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

Le manuel R rentre dans les moindres détails sur les 5 opérateurs d'affectation.

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

Nosredna
la source
133
L'inconvénient d'une affectation accidentelle par rapport à x<-yquand x < -yétait voulu, me contrarie tellement que je préfère personnellement =. Le fait que votre code dépende de la présence d'espaces blancs ne me semble pas bon. Vous pouvez suggérer l'espacement comme conseil de style, mais pour que votre code s'exécute différemment, qu'il y ait un espace ou non? Que se passe-t-il si vous reformatez votre code, ou utilisez la recherche et le remplacement, l'espace blanc peut parfois disparaître et le code tourne mal. Ce n'est pas un problème avec =. IIUC, interdire =équivaut à exiger " <- "; c'est-à-dire, 3 caractères incluant un espace, pas seulement " <-".
Matt Dowle
12
Notez que tout non-0 est pris TRUEen compte par R. Donc, si vous avez l'intention de tester si xest inférieur à -y, vous pouvez écrire if (x<-y)ce qui ne vous avertira pas ou ne fera pas d'erreur et semblera fonctionner correctement. Ce ne sera que FALSEquand y=0, cependant.
Matt Dowle
4
Si vous interdisez =et utilisez, <- il est difficile de prétendre qu'une étape supplémentaire de grep "[^<]<-[^ ]" *.Rn'est pas nécessaire. =n'a pas besoin d'un tel grep.
Matt Dowle
34
Pourquoi vous faire mal aux yeux et aux doigts <-si vous pouvez l'utiliser =? Dans 99,99% des temps, =c'est bien. Parfois, vous avez besoin <<-cependant, ce qui est une histoire différente.
Fernando
10
L'accent mis sur <- est peut-être l'une des raisons boiteuses de l'absence de + = et - =.
Chris
37

x = y = 5est équivalent à x = (y = 5), car les opérateurs d'affectation "groupent" de droite à gauche, ce qui fonctionne. Signification: attribuer 5 à y, en laissant le nombre 5; puis attribuez ce 5 à x.

Ce n'est pas la même chose que (x = y) = 5, qui ne fonctionne pas! Signification: attribuer la valeur de yà x, en laissant la valeur de y; puis attribuer 5 à, euh ..., quoi exactement?

Lorsque vous mélangez les différents types d'opérateurs d'affectation, les <-liaisons sont plus strictes que =. x = y <- 5Est donc interprété comme x = (y <- 5), ce qui est le cas qui a du sens.

Malheureusement, x <- y = 5est interprété comme (x <- y) = 5, ce qui est le cas qui ne fonctionne pas!

Voir ?Syntaxet ?assignOpspour les règles de priorité (liaison) et de regroupement.

Steve Pitchers
la source
Oui, comme l'a dit la réponse de Konrad Rudolph<- <<- au-dessus du = tableau de priorité, ce qui signifie <-qu'il sera exécuté en premier. Donc, x <- y = 5devrait être exécuté comme (x <- y) = 5.
Nick Dong
1
@ Nick Dong Oui en effet. La table de priorité des opérateurs est documentée sans ambiguïté dans la ? Syntaxe {base} .
Steve Pitchers
33

Selon John Chambers, l'opérateur =n'est autorisé qu'au "niveau supérieur", ce qui signifie qu'il n'est pas autorisé dans les structures de contrôle if, ce qui rend l'erreur de programmation suivante illégale.

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

Comme il l'écrit, «Interdire le nouveau formulaire d'affectation [=] dans les expressions de contrôle évite les erreurs de programmation (comme l'exemple ci-dessus) qui sont plus probables avec l'opérateur égal qu'avec les autres affectations S.»

Vous pouvez réussir à le faire s'il est "isolé de la structure logique environnante, par des accolades ou une paire de parenthèses supplémentaires", alors if ((x = 0)) 1 else xcela fonctionnerait.

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

Aaron a laissé Stack Overflow
la source
11
C'est un bug courant, x==0c'est presque toujours à la place.
Aaron a quitté Stack Overflow le
14
Ah, oui, j'ai oublié que vous avez dit "erreur de programmation". C'est en fait une bonne nouvelle que cela provoque une erreur. Et une bonne raison de préférer x=0comme mission plus x<-0!
Steve Pitchers,
7
Oui, c'est bien que cela provoque une erreur, bien que je tire une leçon différente sur ce qu'il faut préférer; Je choisis d'en utiliser =le moins possible car =et me ==ressemble tellement.
Aaron a quitté Stack Overflow le
2
La façon dont cet exemple est présenté m'est si étrange. if(x = 0) 1 else xrenvoie une erreur, m'aidant à trouver et à corriger un bogue. if(x <- 1) 1 else xne génère pas d'erreur et est très déroutant.
Gregor Thomas
3
Je veux dire, un vérificateur d'erreurs vraiment utile lancerait une erreur là-bas et dirait "vous avez du code inutile qui retournera toujours la elsevaleur, vouliez-vous l'écrire de cette façon?", Mais, cela peut être un rêve de pipe ...
TylerH
26

Les opérateurs <-et =affectent dans l'environnement dans lequel ils sont évalués. L'opérateur <-peut être utilisé n'importe où, alors qu'il =n'est autorisé qu'au niveau supérieur (par exemple, dans l'expression complète saisie à l'invite de commande) ou comme l'une des sous-expressions dans une liste d'expressions contreventée.

Haim Evgi
la source
8
Je pense que "niveau supérieur" signifie au niveau de l'instruction, plutôt qu'au niveau de l'expression. Donc, x <- 42en soi, c'est une déclaration; en if (x <- 42) {}elle serait une expression, et n'est pas valide. Pour être clair, cela n'a rien à voir avec le fait que vous soyez ou non dans l'environnement mondial.
Steve Pitchers
1
Ceci: «l'opérateur = n'est autorisé qu'au niveau supérieur» est un malentendu largement répandu et complètement faux.
Konrad Rudolph
Ce n'est pas vrai - par exemple, cela fonctionne, même si l'affectation n'est pas une expression complète:1 + (x = 2)
Pavel Minaev
1
Pour clarifier les commentaires de KonradRudolph et PavelMinaev, je pense qu'il est trop fort pour dire que c'est complètement faux, mais il y a une exception, qui est quand il est "isolé de la structure logique environnante, par des accolades ou une paire de parenthèses supplémentaire."
Aaron a quitté Stack Overflow
Ou function() x = 1, repeat x = 1, if (TRUE) x = 1....
Moody_Mudskipper
6

Cela peut également contribuer à la compréhension de la différence entre ces deux opérateurs:

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

Pour le premier élément, R a attribué des valeurs et un nom propre, tandis que le nom du deuxième élément semble un peu étrange.

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 ...

R version 3.3.2 (2016-10-31); macOS Sierra 10.12.1

Denis Rasulev
la source
6
pouvez-vous expliquer plus en détail pourquoi cela se produit / que se passe-t-il ici? (indice: data.frameessaie d'utiliser le nom de la variable fournie comme nom de l'élément dans le
bloc de
Juste pensé, cela pourrait-il être un bug? Et si oui, comment et où dois-je le signaler?
Denis Rasulev
7
ce n'est pas un bug. J'ai essayé de faire allusion à la réponse dans mon commentaire ci-dessus. Lors de la définition du nom de l'élément, R utilisera l'équivalent de make.names("b <- rnorm(10)").
Ben Bolker