purrr::pmap 与 rlang 的混淆行为; "to quote" or not to quote argument 即Q
confusing behavior of purrr::pmap with rlang; "to quote" or not to quote argument that is the Q
我有一个自定义函数,我使用 rlang
从数据框中读取输入的变量。这个函数工作得很好不管输入的参数是带引号还是不带引号。但是,奇怪的是,当此函数与 purrr::pmap
一起使用时,它仅在参数被引用时才有效。
所以我有两个问题:
为什么函数会这样?
如何使用 rlang
创建函数,即使在 purrr::pmap
中使用也不必引用参数?
这是一个最小的 reprex,它使用一个简单的函数来强调这个问题:
# loading the needed libraries
library(rlang)
library(dplyr)
library(purrr)
# defining the function
tryfn <- function(data, x, y) {
data <-
dplyr::select(
.data = data,
x = !!rlang::enquo(x),
y = !!rlang::enquo(y)
)
# creating a dataframe of means
result_df <- data.frame(mean.x = mean(data$x), mean.y = mean(data$y))
# return the dataframe
return(result_df)
}
# without quotes (works!)
tryfn(iris, Sepal.Length, Sepal.Width)
#> mean.x mean.y
#> 1 5.843333 3.057333
# with quotes (works!)
tryfn(iris, "Sepal.Length", "Sepal.Width")
#> mean.x mean.y
#> 1 5.843333 3.057333
# pmap without quotes (doesn't work)
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = list(Sepal.Length, wt, len),
y = list(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> Error in is.data.frame(.l): object 'Sepal.Length' not found
# pmap with quotes (works!)
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = list("Sepal.Length", "wt", "len"),
y = list("Sepal.Width", "mpg", "dose")
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
由 reprex package (v0.2.0) 创建于 2018-05-21。
问题不在于 purrr
,真的。可以观察到相同的行为:
list(Sepal.Length) # Error: object 'Sepal.Length' not found
据我所知,当您将参数传递给您创建的函数时,!!
、enquo
等的所有魔法都可用。这就是将未加引号的字段名称直接传递给 tryfn()
的原因。
但是对于 pmap()
,您将字段名称(Sepal.Width
、wt
等)放在 list
定义中,并且 list
不喜欢那样 - 所以 pmap
从来没有机会将东西传递给 tryfn
因为你的 list
定义上的 barfs。
将您的字段名称作为字符串传递就可以了,因为 list
可以容纳该数据类型,然后 pmap
有机会将它们映射到 tryfn()
。
Hadley 对 quasiquotation with dplyr
的评论可能对您有用。
回答你的第二个问题:
How can I make a function using rlang such that I won't have to quote the arguments even if used in purrr::pmap?
您可以用 quo()
包裹您的字段名称以避免将它们按字面意思作为字符串引用,尽管我不确定这是否有很大改进:
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = list(quo(Sepal.Length), quo(wt), quo(len)),
y = list(quo(Sepal.Width), quo(mpg), quo(dose))
),
.f = tryfn) %>%
bind_rows(., .id="dataset")
dataset mean.x mean.y
1 1 5.843333 3.057333
2 2 3.217250 20.090625
3 3 18.813333 1.166667
问题是:R 看到了 Sepal.Length, wt, len
个符号,所以它试图在当前环境中查找并评估它们。当然,这会导致错误,因为它们是数据框的列。当您引用它们时,R 不会尝试评估和返回值,因为它会将这些值视为字符串。
如果将 list
替换为 base::alist
或 dplyr::vars
或 rlang::exprs
,它应该可以工作
注意:因为我们已经引用了输入,所以我们不再需要在 tryfn
中使用 rlang::enquo
。
# loading the needed libraries
library(rlang)
library(tidyverse)
# defining the function
tryfn <- function(data, x, y) {
data <-
dplyr::select(
.data = data,
x = !! x,
y = !! y
)
# creating a data frame of means
result_df <- data.frame(mean.x = mean(data$x), mean.y = mean(data$y))
# return the data frame
return(result_df)
}
# alist handles its arguments as if they described function arguments.
# So the values are not evaluated, and tagged arguments with no value are
# allowed whereas list simply ignores them.
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = alist(Sepal.Length, wt, len),
y = alist(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = dplyr::vars(Sepal.Length, wt, len),
y = dplyr::vars(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = rlang::exprs(Sepal.Length, wt, len),
y = rlang::exprs(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
由 reprex package (v0.2.0) 创建于 2018-05-21。
我有一个自定义函数,我使用 rlang
从数据框中读取输入的变量。这个函数工作得很好不管输入的参数是带引号还是不带引号。但是,奇怪的是,当此函数与 purrr::pmap
一起使用时,它仅在参数被引用时才有效。
所以我有两个问题:
为什么函数会这样?
如何使用
rlang
创建函数,即使在purrr::pmap
中使用也不必引用参数?
这是一个最小的 reprex,它使用一个简单的函数来强调这个问题:
# loading the needed libraries
library(rlang)
library(dplyr)
library(purrr)
# defining the function
tryfn <- function(data, x, y) {
data <-
dplyr::select(
.data = data,
x = !!rlang::enquo(x),
y = !!rlang::enquo(y)
)
# creating a dataframe of means
result_df <- data.frame(mean.x = mean(data$x), mean.y = mean(data$y))
# return the dataframe
return(result_df)
}
# without quotes (works!)
tryfn(iris, Sepal.Length, Sepal.Width)
#> mean.x mean.y
#> 1 5.843333 3.057333
# with quotes (works!)
tryfn(iris, "Sepal.Length", "Sepal.Width")
#> mean.x mean.y
#> 1 5.843333 3.057333
# pmap without quotes (doesn't work)
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = list(Sepal.Length, wt, len),
y = list(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> Error in is.data.frame(.l): object 'Sepal.Length' not found
# pmap with quotes (works!)
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = list("Sepal.Length", "wt", "len"),
y = list("Sepal.Width", "mpg", "dose")
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
由 reprex package (v0.2.0) 创建于 2018-05-21。
问题不在于 purrr
,真的。可以观察到相同的行为:
list(Sepal.Length) # Error: object 'Sepal.Length' not found
据我所知,当您将参数传递给您创建的函数时,!!
、enquo
等的所有魔法都可用。这就是将未加引号的字段名称直接传递给 tryfn()
的原因。
但是对于 pmap()
,您将字段名称(Sepal.Width
、wt
等)放在 list
定义中,并且 list
不喜欢那样 - 所以 pmap
从来没有机会将东西传递给 tryfn
因为你的 list
定义上的 barfs。
将您的字段名称作为字符串传递就可以了,因为 list
可以容纳该数据类型,然后 pmap
有机会将它们映射到 tryfn()
。
Hadley 对 quasiquotation with dplyr
的评论可能对您有用。
回答你的第二个问题:
How can I make a function using rlang such that I won't have to quote the arguments even if used in purrr::pmap?
您可以用 quo()
包裹您的字段名称以避免将它们按字面意思作为字符串引用,尽管我不确定这是否有很大改进:
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = list(quo(Sepal.Length), quo(wt), quo(len)),
y = list(quo(Sepal.Width), quo(mpg), quo(dose))
),
.f = tryfn) %>%
bind_rows(., .id="dataset")
dataset mean.x mean.y
1 1 5.843333 3.057333
2 2 3.217250 20.090625
3 3 18.813333 1.166667
问题是:R 看到了 Sepal.Length, wt, len
个符号,所以它试图在当前环境中查找并评估它们。当然,这会导致错误,因为它们是数据框的列。当您引用它们时,R 不会尝试评估和返回值,因为它会将这些值视为字符串。
如果将 list
替换为 base::alist
或 dplyr::vars
或 rlang::exprs
,它应该可以工作
注意:因为我们已经引用了输入,所以我们不再需要在 tryfn
中使用 rlang::enquo
。
# loading the needed libraries
library(rlang)
library(tidyverse)
# defining the function
tryfn <- function(data, x, y) {
data <-
dplyr::select(
.data = data,
x = !! x,
y = !! y
)
# creating a data frame of means
result_df <- data.frame(mean.x = mean(data$x), mean.y = mean(data$y))
# return the data frame
return(result_df)
}
# alist handles its arguments as if they described function arguments.
# So the values are not evaluated, and tagged arguments with no value are
# allowed whereas list simply ignores them.
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = alist(Sepal.Length, wt, len),
y = alist(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = dplyr::vars(Sepal.Length, wt, len),
y = dplyr::vars(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
purrr::pmap(.l = list(
data = list(iris, mtcars, ToothGrowth),
x = rlang::exprs(Sepal.Length, wt, len),
y = rlang::exprs(Sepal.Width, mpg, dose)
),
.f = tryfn)
#> [[1]]
#> mean.x mean.y
#> 1 5.843333 3.057333
#>
#> [[2]]
#> mean.x mean.y
#> 1 3.21725 20.09062
#>
#> [[3]]
#> mean.x mean.y
#> 1 18.81333 1.166667
由 reprex package (v0.2.0) 创建于 2018-05-21。