对函数

时间:2016-04-17 23:01:31

标签: r ggplot2 lazy-evaluation

我主要使用ggplot2进行可视化。通常,我设计情节 交互式(即使用NSE的原始ggplot2代码),但最后,我 经常最终将该代码包装到一个接收的函数中 要绘制的数据和变量。这总是一点点 梦魇。

因此,典型的情况看起来像这样。我有一些数据和我 为它创建一个图(在这种情况下,一个非常简单的例子,使用 ggplot2)附带的mpg数据集。

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 使用NSE的aes的intead(在这个例子中它很容易,但是 对于更复杂的图,有很多变换和图层, 这成了一场噩梦。)

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)来生成相同的图 变量或数据的不同组合。

所以我想滥用更多的懒惰,标准和非标准 评估,并尝试将它们全部结合起来,这样我就有了灵活性 如上所示,以及以编程方式使用该功能的能力。 基本上,我所做的就是使用tryCatchlazy_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 个答案:

答案 0 :(得分: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中存在哪些变量以及环境中存在哪些变量。由于很多人使用变量名称,例如xxvarcount(即使他们可能不应该这样),事情也会变得混乱。

另外,如果我想强制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')

NSE版本仍然受到影响:

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

(我觉得ggplot2::aes_也不会接受字符串,这有点令人讨厌。)