Я использую в основном ggplot2
для визуализации. Как правило, я проектирую график в интерактивном режиме (т. Е. Необработанный код ggplot2
, который использует NSE), но в конце I часто завершают этот код в функцию, которая получает данные и переменные для построения. И это всегда немного кошмар .Оценка Ленивая для ggplot2 внутри функции
Таким образом, типичные ситуации выглядят так. У меня есть некоторые данные, и я создаю график для него (в данном случае очень простой пример, используя набор данных mpg, который поставляется с ggplot2
).
library(ggplot2)
data(mpg)
ggplot(data = mpg,
mapping = aes(x = class, y = hwy)) +
geom_boxplot() +
geom_jitter(alpha = 0.1, color = "blue")
И когда я закончу проектирование участка, я обычно хочу, чтобы использовать его для различных переменных или данных и т.д. Таким образом, создать функцию, которая принимает данные и переменные для участка в качестве аргументов , Но из-за NSE это не так просто, как написать заголовок функции, а затем скопировать/вставить и заменить переменные для аргументов функции. Это не сработает, как показано ниже.
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") #
Так что я должен вернуться и исправить код, например, с помощью aes_string
Intead из aes
, который использует NSE (в данном примере это довольно легко, но для более сложных участков, с большим количеством преобразований и слоев, это становится кошмаром).
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
И дело в том, что я считаю очень удобным NSE, а также lazyeval
. Итак, Мне нравится делать что-то подобное.
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
Это дает мой график функции много гибкости. Например, вы можете указать пропущенные или некотируемые имена переменных и даже данные непосредственно вместо имени переменной (вид злоупотребления ленивой оценкой).
Но это имеет огромную проблему. Невозможно использовать функцию .
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)
Поэтому я не могу использовать циклы (например, lapply) для получения того же участка для различных комбинаций переменных или данных.
Так я думал, что злоупотреблять даже более ленивую, стандартную и нестандартную оценки, и попытаться объединить их все, так что я оба, гибкость показано выше, и возможность использовать функцию программно. В основном, я должен использовать tryCatch
для первого lazy_eval
выражение для каждой переменной и если оно не выполнено, для оценки разобранного выражения .
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)
Итак, в дополнение к вышеупомянутой гибкости, теперь я могу использовать однострочный слой , чтобы создать много одного и того же участка с различными переменными (или данными) .
lapply(c("class", "fl", "drv"), FUN = plotfn, yvar = hwy, data = mpg)
## [[1]]
##
## [[2]]
##
## [[3]]
Несмотря на то, что это очень практично, я подозреваю, что это не является хорошей практикой. Но насколько плохая практика? Это мой ключевой вопрос. Какие еще альтернативы я могу использовать, чтобы иметь лучшее из обоих миров?
Конечно, я вижу, что этот шаблон может создавать проблемы. Например.
# 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
А некоторые (многие?) Другие проблемы. Но мне кажется, что преимущества в терминах синтаксической гибкости перевешивают эти другие проблемы. Любые мысли по этому поводу?
Лучшей практикой является создание пары функций. Один из них - NSE, другой SE. Это описано в 'vignette ('nse')'. Это означает использование 'aes_' вместо' aes'. – Axeman
Спасибо, ..., да, я боялся, что это будет ответ. Хотя я вижу преимущества dplyr & co. «последовательная схема именования: SE - это имя NSE с _ в конце», всегда мне приходится использовать другую функцию для программирования и работы в интерактивном режиме. – elikesprogramming