Évaluation différée pour ggplot2 l'intérieur d'une fonction
J'utilise principalement des ggplot2
pour des visualisations. Généralement, je la conception de l'intrigue
de manière interactive (c'est à dire brut ggplot2
code qui utilise NSE), mais à la fin, je
souvent l'emballage jusqu'à ce code dans une fonction qui reçoit
les données et les variables de la parcelle. Et c'est toujours un peu un
cauchemar.
Ainsi, les situations typiques ressemble à ceci. J'ai quelques données, et j'ai
la création d'une intrigue (dans ce cas, une très très simple exemple, à l'aide de
le mpg dataset qui vient avec ggplot2
).
library(ggplot2)
data(mpg)
ggplot(data = mpg,
mapping = aes(x = class, y = hwy)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
Et quand j'ai fini la conception de l'intrigue, en général, je veux l'utiliser pour
différentes variables ou de données, etc. J'ai donc créer une fonction qui reçoit
les données et les variables pour l'intrigue, en tant qu'arguments. Mais en raison de la NSE, il est
pas aussi facile que d'écrire la fonction en-tête, puis copier/coller et remplacer
variables pour les arguments des fonctions. Cela ne fonctionnerait pas, comme illustré ci-dessous.
mpg <- mpg
plotfn <- function(data, xvar, yvar){
ggplot(data = data,
mapping = aes(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, class, hwy) # Can't find object
## Don't know how to automatically pick scale for object of type function. Defaulting to continuous.
## Warning: restarting interrupted promise evaluation
## Error in eval(expr, envir, enclos): object 'hwy' not found
plotfn(mpg, "class", "hwy") #
Donc, je dois revenir en arrière et corriger le code, par exemple, à l'aide de aes_string
intead de la aes
qui utilise NSE (dans cet exemple, c'est plutôt facile, mais
pour plus compliquée parcelles, avec beaucoup de transformations et de couches,
cela devient un cauchemar).
plotfn <- function(data, xvar, yvar){
ggplot(data = data,
mapping = aes_string(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, "class", "hwy") # Now this works
Et le truc, c'est que je trouve très pratique et en génie et aussi lazyeval
. Donc
Je voudrais faire quelque chose comme ça.
mpg <- mpg
plotfn <- function(data, xvar, yvar){
data_gd <- data.frame(
xvar = lazyeval::lazy_eval(substitute(xvar), data = data),
yvar = lazyeval::lazy_eval(substitute(yvar), data = data))
ggplot(data = data_gd,
mapping = aes(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, class, hwy) # Now this works
plotfn(mpg, "class", "hwy") # This still works
plotfn(NULL, rep(letters[1:4], 250), 1:100) # And even this crazyness works
Cela donne ma fonction plot de beaucoup de flexibilité. Par exemple, vous pouvez
passer cotées ou non cotées, les noms de variables et même les données directement
au lieu d'un nom de variable (type d'abuser de l'évaluation différée).
Mais cela a un énorme problème. La fonction ne peut pas être utilisé
par programmation.
dynamically_changing_xvar <- "class"
plotfn(mpg, dynamically_changing_xvar, hwy)
## Error in eval(expr, envir, enclos): object 'dynamically_changing_xvar' not found
# This does not work, because it never finds the object
# dynamically_changing_xvar in the data, and it does not get evaluated to
# obtain the variable name (class)
Donc je ne peux pas utiliser de boucles (par exemple lapply) pour produire la même parcelle pour
différentes combinaisons de variables, ou des données.
J'ai donc pensé à des abus encore plus de paresseux, standard et non-standard
d'évaluation, et d'essayer de les combiner entre eux tous si j'ai les deux, la flexibilité
indiqué ci-dessus et la capacité d'utiliser la fonction de programmation.
Fondamentalement, ce que je fais est d'utiliser tryCatch
à première lazy_eval
l'
expression pour chaque variable, et si elle échoue, pour évaluer l'analyse
expression.
plotfn <- function(data, xvar, yvar){
data_gd <- NULL
data_gd$xvar <- tryCatch(
expr = lazyeval::lazy_eval(substitute(xvar), data = data),
error = function(e) eval(envir = data, expr = parse(text=xvar))
)
data_gd$yvar <- tryCatch(
expr = lazyeval::lazy_eval(substitute(yvar), data = data),
error = function(e) eval(envir = data, expr = parse(text=yvar))
)
ggplot(data = as.data.frame(data_gd),
mapping = aes(x = xvar, y = yvar)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
}
plotfn(mpg, class, hwy) # Now this works, again
plotfn(mpg, "class", "hwy") # This still works, again
plotfn(NULL, rep(letters[1:4], 250), 1:100) # And this crazyness still works
# And now, I can also pass a local variable to the function, that contains
# the name of the variable that I want to plot
dynamically_changing_xvar <- "class"
plotfn(mpg, dynamically_changing_xvar, hwy)
Ainsi, dans le cadre de ladite flexibilité, maintenant je peux utiliser
one-liner ou alors, pour produire un grand nombre de la même parcelle, avec différents
des variables (ou des données).
lapply(c("class", "fl", "drv"), FUN = plotfn, yvar = hwy, data = mpg)
## [[1]]
##
## [[2]]
##
## [[3]]
Même si c'est très pratique, j'imagine que ce n'est pas une bonne pratique. Mais
comment une mauvaise habitude s'agit-il? C'est la clé de ma question. Quelles sont les autres alternatives
puis-je utiliser pour avoir le meilleur des deux mondes?
Bien sûr, je peux voir ce modèle peut créer des problèmes. Par exemple.
# If I have a variable in the global environment that contains the variable
# I want to plot, but whose name is in the data passed to the function,
# then it will use the name of the variable and not its content
drv <- "class"
plotfn(mpg, drv, hwy) # Here xvar on the plot is drv and not class
Et certains (beaucoup?) d'autres problèmes. Mais il me semble que les avantages en termes
de la syntaxe-la flexibilité supérieurs à ceux d'autres questions. Des idées sur ce point?
vignette('nse')
. Cela signifie en utilisant aes_
au lieu de aes
.Merci, ..., ouais, j'avais peur qu'allait être la réponse. Bien que je vois les avantages de dplyr & co. "schéma cohérent de nommage: le SE est la NSE nom _ sur la fin", il a toujours des bugs moi d'avoir à utiliser une autre fonction de la programmation et de travailler de manière interactive.
OriginalL'auteur elikesprogramming | 2016-04-17
Vous devez vous connecter pour publier un commentaire.
L'extraction de votre fonction proposée pour plus de clarté:
Une telle fonction est généralement très utile, car vous pouvez combiner des chaînes, et mettre à nu les noms de variables. Mais comme vous le dites, il peut ne pas toujours être en sécurité. Considérez les points suivants artificiel exemple:
Quel sera votre fonction générer? Seront-elles les mêmes (ils ne le sont pas)? Ce n'est pas vraiment clair pour moi ce sera le résultat. Programmation avec une telle fonction peut donner des résultats inattendus, selon les variables qui existent dans
data
et qui existent dans l'environnement. Depuis beaucoup de gens utilisent des noms de variables commex
,xvar
oucount
(même si ils peut-être ne devrait pas), les choses peuvent déraper.Aussi, si je voulais de la force de l'une ou l'autre interprétation de
class
, je ne peux pas.Je dirais que c'est une sorte de semblable à l'utilisation de
attach
: pratique, mais à un certain point, il pourrait vous mordre dans le derrière.Donc, j'utilise un NSE et SE paire:
La création de ceux-ci est effectivement plus facile que de votre fonction, je pense. Vous pouvez opter pour capturer tous les arguments paresseusement avec
lazy_dots
trop.Maintenant, nous obtenons plus facile de prédire les résultats lors de l'utilisation du coffre-fort SE version:
La NSE version est toujours affectée:
(Je trouve ça légèrement ennuyeux que
ggplot2::aes_
n'est pas aussi prendre des chaînes de caractères.)Les deux dernières lignes de code n'a pas fonctionné quand j'ai essayé de les exécuter.
NSE dans le tidyverse a changé, donc c'est très possible.
OriginalL'auteur Axeman