Причины
Если мы посмотрим на исходный код 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 являются причиной, мы можем изменить исходное кодирование aes()
, удалив x иу. поскольку geom_*()
наследует aes, имея значение по умолчанию inherit.aes = TRUE
, поэтому вы должны иметь возможность его запустить.
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)
UPDATE
Короче говоря, порядок аргументов имеет значение. При использовании 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