É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")

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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") # 

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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

Évaluation différée pour ggplot2 l'intérieur d'une fonction

plotfn(mpg, "class", "hwy") # This still works

Évaluation différée pour ggplot2 l'intérieur d'une fonction

plotfn(NULL, rep(letters[1:4], 250), 1:100) # And even this crazyness works

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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

Évaluation différée pour ggplot2 l'intérieur d'une fonction

plotfn(mpg, "class", "hwy") # This still works, again

Évaluation différée pour ggplot2 l'intérieur d'une fonction

plotfn(NULL, rep(letters[1:4], 250), 1:100) # And this crazyness still works

Évaluation différée pour ggplot2 l'intérieur d'une fonction

# 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) 

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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]]

Évaluation différée pour ggplot2 l'intérieur d'une fonction

## 
## [[2]]

Évaluation différée pour ggplot2 l'intérieur d'une fonction

## 
## [[3]]

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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

Évaluation différée pour ggplot2 l'intérieur d'une fonction
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?

La meilleure pratique consiste à produire une paire de fonctions. On est NSE, l'autre SE. Ce qui est présenté dans 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