2016-04-17 2 views
6

Я использую в основном 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 


А некоторые (многие?) Другие проблемы. Но мне кажется, что преимущества в терминах синтаксической гибкости перевешивают эти другие проблемы. Любые мысли по этому поводу?

+1

Лучшей практикой является создание пары функций. Один из них - NSE, другой SE. Это описано в 'vignette ('nse')'. Это означает использование 'aes_' вместо' aes'. – Axeman

+0

Спасибо, ..., да, я боялся, что это будет ответ. Хотя я вижу преимущества dplyr & co. «последовательная схема именования: SE - это имя NSE с _ в конце», всегда мне приходится использовать другую функцию для программирования и работы в интерактивном режиме. – elikesprogramming

ответ

2

Распаковка вашей предложенной функции для ясности:

library(ggplot2) 
data(mpg) 

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

Такой функция, как правило, весьма полезно, так как вы можете свободно смешивать строки и голые имена переменных. Но, как вы говорите, это может быть не всегда безопасно. Рассмотрим следующий надуманный пример:

class <- "drv" 
Class <- "drv" 
plotfn(mpg, class, hwy) 
plotfn(mpg, Class, hwy) 

Что будет генерировать ваша функция? Будут ли они такими же (это не так)? Мне непонятно, какой результат. Программирование с такой функцией может дать неожиданные результаты, зависящие от того, какие переменные существуют в data и которые существуют в среде. Поскольку многие люди используют имена переменных, такие как x, xvar или count (даже если они, возможно, не должны), все может стать беспорядочным.

Кроме того, если я хотел заставить одну или другую интерпретацию class, я не могу.

Я бы сказал, что это похоже на использование attach: удобно, но в какой-то момент он может укусить вас позади.

Поэтому я бы использовать NSE и SE пары:

plotfn <- function(data, xvar, yvar) { 
    plotfn_(data, 
      lazyeval::lazy_eval(xvar, data = data), 
      lazyeval::lazy_eval(yvar, data = data)) 
) 
} 

plotfn_ <- function(data, xvar, yvar){ 
    ggplot(data = data, 
     mapping = aes_(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 

Создание этих на самом деле проще, чем ваши функции, я думаю. Вы могли бы сделать все аргументы лениво с помощью lazy_dots.

Теперь мы получаем более легко предсказать результаты при использовании безопасной версии SE:

class <- "drv" 
Class <- "drv" 
plotfn_(mpg, class, 'hwy') 
plotfn_(mpg, Class, 'hwy') 

НУШ версия все еще затронуты, хотя:

plotfn(mpg, class, hwy) 
plotfn(mpg, Class, hwy) 

(я считаю, мягко говоря раздражает, что ggplot2::aes_ Безразлично 't также взять строки.)

+1

Да, я согласен на 100% с «Программирование с такой функцией может дать неожиданные результаты, зависящие от того, какие переменные существуют в данных и которые существуют в среде». ..., просто, что иногда я чувствую, что удобство этого перевешивает риск того, что меня укусят. – elikesprogramming

+0

Последние две строки кода не работали, когда я пытался их запустить. – student

+0

NSE в tidyverse изменилось, так что это очень возможно. – Axeman

 Смежные вопросы

  • Нет связанных вопросов^_^