如何在 ggplot geoms 中编写具有整洁评估的函数?
How to write functions with tidy evaluation inside ggplot geoms?
我想编写一个在 ggplot 中执行美学映射的函数。该函数应该有两个参数:var
应该映射到 aesthetic
。下面的第一个代码块确实有效。
但是,我不想在初始 ggplot 函数中而是在 geom_point 函数中进行映射。在这里我收到以下错误消息:
Error: :=
can only be used within a quasiquoted argument
1.块:工作正常
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, !! (aesthetic) := !!var)) +
geom_point()
}
myfct(size, Petal.Width)
2。阻止:抛出错误
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(!! (aesthetic) := !!var))
}
myfct(size, Petal.Width)
如果将参数 aesthetic 作为带有 sym
的字符串传递,也会发生相同的行为。
# 1. block
myfct <- function(aesthetic, var){
aesthetic <- sym(aesthetic)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, !! aesthetic := {{var}})) +
geom_point()
}
myfct("size", Petal.Width)
# works
# 2. block
myfct <- function(aesthetic, var){
aesthetic <- sym(aesthetic)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(!! aesthetic := {{var}}))
}
myfct("size", Petal.Width)
# doesn't work
原因
如果我们看一下aes源代码,我们会发现第一和第二位是x和y保留的
> ggplot2::aes
function (x, y, ...)
{
exprs <- enquos(x = x, y = y, ..., .ignore_empty = "all")
aes <- new_aes(exprs, env = parent.frame())
rename_aes(aes)
}
让我们定义一个函数,看看它将如何影响 aes()
:
testaes <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
print("with x and y:")
print(aes(Sepal.Length,Sepal.Width,!!(aesthetic) := !!var))
print("without x and y:")
print(aes(!!(aesthetic) := !!var))
}
> testaes(size, Petal.Width)
[1] "with x and y:"
Aesthetic mapping:
* `x` -> `Sepal.Length`
* `y` -> `Sepal.Width`
* `size` -> `Petal.Width`
[1] "without x and y:"
Aesthetic mapping:
* `x` -> ``:=`(size, Petal.Width)`
如您所见,当使用 :=
时没有 x 和 y,aesthetic
和 var
被分配给 x。
要系统地解决这个问题,需要更多关于 NSE 和 ggplot2 源代码的知识。
解决方法
总是给第一名和第二名赋值
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width,!! (aesthetic) := !!var))
}
myfct(size, Petal.Width)
或者在 aes 上写一个包装器
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
# wrapper on aes
myaes <- function(aesthetic, var){
aes(x = Sepal.Length, y = Sepal.Width,!! (aesthetic) := !!var)
}
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(mapping = myaes(aesthetic,var))
}
myfct(size, Petal.Width)
或修改源代码
因为 x 和 y 是原因,我们可以通过删除 x 和 y 来修改 aes()
源代码。由于 geom_*()
通过默认值 inherit.aes = TRUE
继承 aes,因此您应该能够 运行 它。
aes_custom <- function(...){
exprs <- enquos(..., .ignore_empty = "all")
aes <- ggplot2:::new_aes(exprs, env = parent.frame())
ggplot2:::rename_aes(aes)
}
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes_custom(!!(aesthetic) := !!var))
}
myfct(size, Petal.Width)
更新
简而言之,参数顺序很重要。使用 NSE 时,我们应该始终将 !!x := !!y
放在未命名参数的位置(例如 ...
),而不要放在命名参数的位置。
我能够在 ggplot 之外重现问题,所以根本原因来自 NSE。似乎 :=
只有在未命名参数(...
)的位置才有效。在命名参数的位置(下例中的 x
)时,它无法正确求值。
library(rlang)
# test funciton
testNSE <- function(x,...){
exprs <- enquos(x = x, ...)
print(exprs)
}
# test data
a = quo(size)
b = quo(Sepal.Width)
:=
用于代替未命名的参数 (...
)
它工作正常
> testNSE(x,!!a := !!b)
<list_of<quosure>>
$x
<quosure>
expr: ^x
env: global
$size
<quosure>
expr: ^Sepal.Width
env: global
:=
用于代替命名参数
它不起作用,因为 !!a := !!b
用于 testNSE()
的第一个位置,并且第一个位置已经具有名称 x
。因此它尝试将 size := Sepal.Width
分配给 x
而不是将 Sepal.Width
分配给 size
.
> testNSE(!!a := !!b)
<list_of<quosure>>
$x
<quosure>
expr: ^^size := ^Sepal.Width
env: global
我想编写一个在 ggplot 中执行美学映射的函数。该函数应该有两个参数:var
应该映射到 aesthetic
。下面的第一个代码块确实有效。
但是,我不想在初始 ggplot 函数中而是在 geom_point 函数中进行映射。在这里我收到以下错误消息:
Error:
:=
can only be used within a quasiquoted argument
1.块:工作正常
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, !! (aesthetic) := !!var)) +
geom_point()
}
myfct(size, Petal.Width)
2。阻止:抛出错误
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(!! (aesthetic) := !!var))
}
myfct(size, Petal.Width)
如果将参数 aesthetic 作为带有 sym
的字符串传递,也会发生相同的行为。
# 1. block
myfct <- function(aesthetic, var){
aesthetic <- sym(aesthetic)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, !! aesthetic := {{var}})) +
geom_point()
}
myfct("size", Petal.Width)
# works
# 2. block
myfct <- function(aesthetic, var){
aesthetic <- sym(aesthetic)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(!! aesthetic := {{var}}))
}
myfct("size", Petal.Width)
# doesn't work
原因
如果我们看一下aes源代码,我们会发现第一和第二位是x和y保留的
> ggplot2::aes
function (x, y, ...)
{
exprs <- enquos(x = x, y = y, ..., .ignore_empty = "all")
aes <- new_aes(exprs, env = parent.frame())
rename_aes(aes)
}
让我们定义一个函数,看看它将如何影响 aes()
:
testaes <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
print("with x and y:")
print(aes(Sepal.Length,Sepal.Width,!!(aesthetic) := !!var))
print("without x and y:")
print(aes(!!(aesthetic) := !!var))
}
> testaes(size, Petal.Width)
[1] "with x and y:"
Aesthetic mapping:
* `x` -> `Sepal.Length`
* `y` -> `Sepal.Width`
* `size` -> `Petal.Width`
[1] "without x and y:"
Aesthetic mapping:
* `x` -> ``:=`(size, Petal.Width)`
如您所见,当使用 :=
时没有 x 和 y,aesthetic
和 var
被分配给 x。
要系统地解决这个问题,需要更多关于 NSE 和 ggplot2 源代码的知识。
解决方法
总是给第一名和第二名赋值
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(x = Sepal.Length, y = Sepal.Width,!! (aesthetic) := !!var))
}
myfct(size, Petal.Width)
或者在 aes 上写一个包装器
library(ggplot2)
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
# wrapper on aes
myaes <- function(aesthetic, var){
aes(x = Sepal.Length, y = Sepal.Width,!! (aesthetic) := !!var)
}
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(mapping = myaes(aesthetic,var))
}
myfct(size, Petal.Width)
或修改源代码
因为 x 和 y 是原因,我们可以通过删除 x 和 y 来修改 aes()
源代码。由于 geom_*()
通过默认值 inherit.aes = TRUE
继承 aes,因此您应该能够 运行 它。
aes_custom <- function(...){
exprs <- enquos(..., .ignore_empty = "all")
aes <- ggplot2:::new_aes(exprs, env = parent.frame())
ggplot2:::rename_aes(aes)
}
myfct <- function(aesthetic, var){
aesthetic <- enquo(aesthetic)
var <- enquo(var)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes_custom(!!(aesthetic) := !!var))
}
myfct(size, Petal.Width)
更新
简而言之,参数顺序很重要。使用 NSE 时,我们应该始终将 !!x := !!y
放在未命名参数的位置(例如 ...
),而不要放在命名参数的位置。
我能够在 ggplot 之外重现问题,所以根本原因来自 NSE。似乎 :=
只有在未命名参数(...
)的位置才有效。在命名参数的位置(下例中的 x
)时,它无法正确求值。
library(rlang)
# test funciton
testNSE <- function(x,...){
exprs <- enquos(x = x, ...)
print(exprs)
}
# test data
a = quo(size)
b = quo(Sepal.Width)
:=
用于代替未命名的参数 (...
)
它工作正常
> testNSE(x,!!a := !!b)
<list_of<quosure>>
$x
<quosure>
expr: ^x
env: global
$size
<quosure>
expr: ^Sepal.Width
env: global
:=
用于代替命名参数
它不起作用,因为 !!a := !!b
用于 testNSE()
的第一个位置,并且第一个位置已经具有名称 x
。因此它尝试将 size := Sepal.Width
分配给 x
而不是将 Sepal.Width
分配给 size
.
> testNSE(!!a := !!b)
<list_of<quosure>>
$x
<quosure>
expr: ^^size := ^Sepal.Width
env: global