Comment utilisez-vous «<< -» (affectation de portée) dans R?

140

Je viens de finir de lire sur la portée de l'intro R , et je suis très curieux de connaître la <<-mission.

Le manuel montrait un exemple (très intéressant) pour <<-lequel j'ai l'impression d'avoir compris. Ce qui me manque encore, c'est le contexte dans lequel cela peut être utile.

Donc, ce que j'aimerais lire de vous, ce sont des exemples (ou des liens vers des exemples) sur le moment où l'utilisation de <<-peut être intéressante / utile. Quels pourraient être les dangers de son utilisation (il semble facile de perdre la trace) et tous les conseils que vous pourriez avoir envie de partager.

Tal Galili
la source

Réponses:

196

<<-est le plus utile en conjonction avec des fermetures pour maintenir l'état. Voici une section d'un de mes articles récents:

Une fermeture est une fonction écrite par une autre fonction. Les fermetures sont ainsi appelées car elles englobent l'environnement de la fonction parent et peuvent accéder à toutes les variables et paramètres de cette fonction. Ceci est utile car cela nous permet d'avoir deux niveaux de paramètres. Un niveau de paramètres (le parent) contrôle le fonctionnement de la fonction. L'autre niveau (l'enfant) fait le travail. L'exemple suivant montre comment utiliser cette idée pour générer une famille de fonctions de puissance. La fonction parent ( power) crée des fonctions enfants ( squareet cube) qui font réellement le travail difficile.

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é de gérer des variables à deux niveaux permet également de maintenir l'état à travers les appels de fonction en permettant à une fonction de modifier des variables dans l'environnement de son parent. La clé de la gestion des variables à différents niveaux est l'opérateur d'affectation à double flèche <<-. Contrairement à l'affectation de flèche unique habituelle ( <-) qui fonctionne toujours au niveau actuel, l'opérateur de double flèche peut modifier les variables dans les niveaux parents.

Cela permet de gérer un compteur qui enregistre le nombre de fois qu'une fonction a été appelée, comme le montre l'exemple suivant. À chaque new_counterexécution, il crée un environnement, initialise le compteur idans cet environnement, puis crée une nouvelle fonction.

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

La nouvelle fonction est une fermeture et son environnement est l'environnement englobant. Lorsque les fermetures counter_oneet counter_twosont exécutées, chacun modifie le compteur dans son environnement englobant puis renvoie le décompte actuel.

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

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
hadley
la source
4
Hé c'est une tâche R non résolue sur Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Eh bien, c'était ...
Karsten W.
1
Serait-il nécessaire d'inclure plus d'une fermeture dans une fonction parentale? Je viens d'essayer un extrait, il semble que seule la dernière fermeture ait été exécutée ...
mckf111
Existe-t-il une alternative au signe égal au signe "<< -"?
Genom
38

Cela aide à penser <<-comme équivalent à assign(si vous définissez le inheritsparamètre de cette fonction sur TRUE). L'avantage assignest qu'il vous permet de spécifier plus de paramètres (par exemple l'environnement), donc je préfère utiliser assignover <<-dans la plupart des cas.

Utiliser <<-et assign(x, value, inherits=TRUE)signifie que «les environnements englobants de l'environnement fourni sont recherchés jusqu'à ce que la variable« x »soit rencontrée». En d'autres termes, il continuera à parcourir les environnements dans l'ordre jusqu'à ce qu'il trouve une variable avec ce nom, et il l'attribuera à cela. Cela peut être dans le cadre d'une fonction ou dans l'environnement global.

Afin de comprendre ce que font ces fonctions, vous devez également comprendre les environnements R (par exemple en utilisant search).

J'utilise régulièrement ces fonctions lorsque j'exécute une grande simulation et que je souhaite enregistrer des résultats intermédiaires. Cela vous permet de créer l'objet en dehors de la portée de la fonction ou de la applyboucle donnée . C'est très utile, surtout si vous avez des inquiétudes au sujet d'une grande boucle se terminant de manière inattendue (par exemple, une déconnexion de base de données), auquel cas vous pourriez tout perdre dans le processus. Cela équivaudrait à écrire vos résultats dans une base de données ou un fichier pendant un long processus, sauf qu'il stocke les résultats dans l'environnement R.

Mon principal avertissement avec ceci: soyez prudent car vous travaillez maintenant avec des variables globales, en particulier lors de l'utilisation <<-. Cela signifie que vous pouvez vous retrouver avec des situations dans lesquelles une fonction utilise une valeur d'objet de l'environnement, alors que vous vous attendiez à ce qu'elle en utilise une qui a été fournie en tant que paramètre. C'est l'une des principales choses que la programmation fonctionnelle essaie d'éviter (voir effets secondaires ). J'évite ce problème en attribuant mes valeurs à des noms de variables uniques (en utilisant coller avec un ensemble ou des paramètres uniques) qui ne sont jamais utilisés dans la fonction, mais juste utilisés pour la mise en cache et au cas où je devrais récupérer plus tard (ou faire des méta -analyse sur les résultats intermédiaires).

Shane
la source
3
Merci Tal. J'ai un blog, même si je ne l'utilise pas vraiment. Je ne peux jamais terminer un article parce que je ne veux rien publier à moins que ce soit parfait, et je n'ai tout simplement pas le temps pour ça ...
Shane
2
Un homme sage m'a dit un jour qu'il n'est pas important d'être parfait - seulement debout - ce que vous êtes, et vos messages le seront aussi. Aussi - parfois les lecteurs aident à améliorer le texte avec les commentaires (c'est ce qui se passe avec mon blog). J'espère qu'un jour vous reconsidérerez :)
Tal Galili
9

Un endroit où j'ai utilisé <<-était dans les interfaces graphiques simples utilisant tcl / tk. Certains des premiers exemples l'ont - car vous devez faire une distinction entre les variables locales et globales pour l'état complet. Voir par exemple

 library(tcltk)
 demo(tkdensity)

qui utilise <<-. Sinon, je suis d'accord avec Marek :) - une recherche Google peut aider.

Dirk Eddelbuettel
la source
Intéressant, je ne peux pas tkdensityen quelque sorte trouver dans R 3.6.0.
NelsonGon
1
Le paquet tcltk est livré avec 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
la source
11
Ceci est un bon exemple où ne pas utiliser <<-. Une boucle for serait plus claire dans ce cas.
hadley
4

À ce sujet, j'aimerais souligner que l' <<-opérateur se comportera de manière étrange lorsqu'il est appliqué (de manière incorrecte) dans une boucle for (il peut y avoir d'autres cas également). Compte tenu du code suivant:

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

vous pourriez vous attendre à ce que la fonction retourne la somme attendue, 6, mais à la place, elle renvoie 0, avec une variable globale mySumcréée et affectée à la valeur 3. Je ne peux pas expliquer complètement ce qui se passe ici mais certainement le corps d'un for loop n'est pas un nouveau «niveau» de portée. Au lieu de cela, il semble que R regarde en dehors de la fortestfonction, ne trouve pas de mySumvariable à affecter, crée donc une et attribue la valeur 1, la première fois dans la boucle. Lors des itérations suivantes, le RHS dans l'affectation doit faire référence à la mySumvariable interne (inchangée) tandis que le LHS se réfère à la variable globale. Par conséquent, chaque itération remplace la valeur de la variable globale par la valeur de cette itération i, d'où la valeur 3 à la sortie de la fonction.

J'espère que cela aide quelqu'un - cela m'a déconcerté pendant quelques heures aujourd'hui! (BTW, remplacez simplement <<-par <-et la fonction fonctionne comme prévu).

Matthew Wise
la source
2
dans votre exemple, le local mySumn'est jamais incrémenté mais uniquement le global mySum. Par conséquent, à chaque itération de la boucle for, le global mySumobtient la valeur 0 + i. Vous pouvez suivre cela avec debug(fortest).
ClementWalter
Cela n'a rien à voir avec le fait d'être une boucle for; vous faites référence à deux étendues différentes. Utilisez simplement <-partout de manière cohérente dans la fonction si vous souhaitez uniquement mettre à jour la variable locale à l'intérieur de la fonction.
smci
Ou utilisez << - partout @smci. Bien qu'il soit préférable d'éviter les globaux.
Apprentissage des statistiques par exemple
3

L' <<-opérateur peut également être utile pour les classes de référence lors de l'écriture de méthodes de référence . Par exemple:

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
la source