使用parse_expr()、quo_name()和enquo()定义字符对象,用于在ggplot中绘制国家图表

bzzcjhmw  于 2023-06-03  发布在  其他
关注(0)|答案(3)|浏览(131)

bounty还有4天到期。此问题的答案有资格获得+50声望奖励。rez希望引起更多关注这个问题。

我有一个来自源代码的function,它使用了几个输入,包括国家名称,并返回该国家的图表。函数的第一行定义了一个我无法理解的Country_name对象。当我试图从函数中取出该部分并单独运行它时,它返回一个错误,而它在函数中工作正常。有人知道为什么会发生这种情况吗?Country_name的那行代码的目的是什么?

function(df, dfline, Country_name){
  Country_name <- rlang::parse_expr(quo_name(enquo(Country_name)))
  df %>%
    filter(Country == Country_name ...
}

拉出第一行并单独运行它会返回一个错误:

parse_expr(quo_name(enquo('United States')))

### Error in `enquo()`:
### ! `arg` must be a symbol
dsf9zpds

dsf9zpds1#

假设这是你的数据集:

df1 <- tribble(~ Country, ~ Value,
               'Brazil', 1,
               'Brazil', 2,
               'Canada', 3,
               'Canada', 4)

> df1 
# A tibble: 4 × 2
  Country Value
  <chr>   <dbl>
1 Brazil      1
2 Brazil      2
3 Canada      3
4 Canada      4

你可以把你的自定义过滤器函数简单地写为:

fun1 <- function(df, Country_name){
  df %>%
    filter(Country == Country_name)
}

> fun1(df1, 'Brazil')
# A tibble: 2 × 2
  Country Value
  <chr>   <dbl>
1 Brazil      1
2 Brazil      2

但是,假设您希望能够省略'Brazil'周围的引号,并且仍然获得相同的输出。如果你没有修改,你会得到一个错误:

> fun1(df1, Brazil)
# ...
#! object 'Brazil' not found
# ...

R将Brazil理解为变量,并在全局环境中查找它。它找不到它,然后返回一个错误。如果Brazil是一个变量,你可能会得到奇怪的结果:

Brazil <- 'Cadada'

> fun2(df1, Brazil)
# A tibble: 2 × 2
  Country Value
  <chr>   <dbl>
1 Canada      3
2 Canada      4

R看到Brazil的值为'Canada',将该值绑定到Country_name,并在过滤器上使用该值。
这不是你想要的您希望获得实际的单词Brazil,而不是它表示的值。这就是你提到的那条线的作用。我将在下面解释它是如何工作的。
第一步是对R说“我不想让你评估你收到的参数,我只想让你保存它的文本”。也就是说,我们希望 * 延迟传递到Country_name的 * 表达式 * 的求值 *。这可以通过以下几种方式实现:

  • substitute(Country_name)在碱基R中,正如Nir Graham所指出的;

substitute返回**(未求值)表达式**的解析树... - 替代的帮助页。

  • enquo(Country_name)和rlang,就像你的函数一样。

enquo()和enquos()化解函数参数。可以检查、修改已消除的表达式并将其注入到其他表达式中。-enquo的帮助页面。

  • enexpr(Country_name)与rlang,也如Nir Graham所述;

enexpr()和enexprs()类似于enquo()和enquos(),但返回裸表达式而不是quosures。-enexpr的帮助页面
所以它们都有非常相似的效果。最大的区别是enquo“返回quosures而不是裸表达式”。简单地说,quosures 是一个表达式,它也指向应该找到相关变量值的环境 *。我们不需要这样做(但这也不是问题),因为所讨论的表达式不会被求值,我们只需要它的文本。
之后,我们只想得到那个被消隐的表达式的文本,可以用以下命令来实现:

  • as.character() ;
  • deparse1() ;
  • rlang::quo_name() ;
  • rlang::expr_name() .

因此,这些选项与Nir Graham所做的类似:

fun2_base <- function(df, Country_name){
  Country_name <- deparse1(substitute(Country_name))
  df %>%
    filter(Country == Country_name)
}

fun2_rlang <- function(df, Country_name){
  Country_name <- as.character(enexpr(Country_name))
  df %>%
    filter(Country == Country_name)
}

fun2_base(df1, Brazil)
fun2_rlang(df1, Brazil)

所有产量:

# A tibble: 2 × 2
  Country Value
  <chr>   <dbl>
1 Brazil      1
2 Brazil      2

注意,我们不需要删除Brazil变量,因为它没有被计算。

c86crjj0

c86crjj02#

它使用3个函数调用来完成2中可实现的功能,无论是在base中还是使用rlang。

library(dplyr)
library(rlang)

myfilt_base <- function(x){
  mysym <- deparse1(substitute(x))
  filter(iris, Species == mysym)
}

myfilt_base(versicolor)

myfilt_rlang <- function(x){
  mysym <- as.character(enexpr(x))
  filter(iris, Species == mysym)
}

myfilt_rlang(virginica)
oogrdqng

oogrdqng3#

首先让我们构建一个最小的reprex

library(dplyr, warn.conflicts = FALSE)
fun <- function(df, Country_name){
  Country_name <- rlang::parse_expr(quo_name(enquo(Country_name)))
  df %>%
    filter(Country == Country_name)
}
df <- data.frame(x = 1:2, Country = c("Belgium", "Ukraine"))
df
#>   x Country
#> 1 1 Belgium
#> 2 2 Ukraine

fun(df, Ukraine)
#>   x Country
#> 1 2 Ukraine

然后让我们使用boomer来打印中间输出:

fun1 <- boomer::rig(fun)
fun1(df, Ukraine)
#> 👇 fun
#> 💣 rlang::parse_expr(quo_name(enquo(Country_name))) 
#> · 💣 quo_name(enquo(Country_name)) 
#> · · 💣 💥 enquo(Country_name) 
#> · · <quosure>
#> · · expr: ^Ukraine
#> · · env:  global
#> · · 
#> · 💥 quo_name(enquo(Country_name)) 
#> · [1] "Ukraine"
#> · 
#> 💥 rlang::parse_expr(quo_name(enquo(Country_name))) 
#> Ukraine
#> 
#> 💣 df %>% filter(Country == Country_name) 
#> · 💣 filter(., Country == Country_name) 
#> · · df :
#> · ·   x Country
#> · · 1 1 Belgium
#> · · 2 2 Ukraine
#> · · Country_name :
#> · · Ukraine
#> · · 💣 💥 Country == Country_name 
#> · · [1] FALSE  TRUE
#> · · 
#> · 💥 filter(., Country == Country_name) 
#> ·   x Country
#> · 1 2 Ukraine
#> · 
#> 💥 df %>% filter(Country == Country_name) 
#>   x Country
#> 1 2 Ukraine
#> 
#> 👆 fun
#>   x Country
#> 1 2 Ukraine

我们看到:

  • enquo()将输入捕获到quosure中
  • quo_name()将表达式提取为字符串
  • parse_expr()从字符串构建符号
  • 这个符号用在等式中(这里它被强制转换为字符,尝试quote(a) == "a"来检查它是如何工作的)。

如果我们想更好地理解对象,我们可以在print参数中使用{constructive}。它将打印代码来重建对象,而不是打印对象。

# remotes::install_github("cynkra/constructive")
fun2 <- boomer::rig(fun, print = constructive::construct)
fun2(df, Ukraine)
#> 👇 fun
#> 💣 rlang::parse_expr(quo_name(enquo(Country_name))) 
#> · 💣 quo_name(enquo(Country_name)) 
#> · · 💣 💥 enquo(Country_name) 
#> · · rlang::new_quosure(quote(Ukraine), .GlobalEnv)
#> · · 
#> · 💥 quo_name(enquo(Country_name)) 
#> · "Ukraine"
#> · 
#> 💥 rlang::parse_expr(quo_name(enquo(Country_name))) 
#> quote(Ukraine)
#> 
#> 💣 df %>% filter(Country == Country_name) 
#> · 💣 filter(., Country == Country_name) 
#> · · df :
#> · · data.frame(x = 1:2, Country = c("Belgium", "Ukraine"))
#> · · Country_name :
#> · · quote(Ukraine)
#> · · 💣 💥 Country == Country_name 
#> · · c(FALSE, TRUE)
#> · · 
#> · 💥 filter(., Country == Country_name) 
#> · data.frame(x = 2L, Country = "Ukraine")
#> · 
#> 💥 df %>% filter(Country == Country_name) 
#> data.frame(x = 2L, Country = "Ukraine")
#> 
#> 👆 fun
#>   x Country
#> 1 2 Ukraine

boomer::boom(fun(df, Ukraine), print = function(x) print(constructive::construct(x)))
#> 💣 💥 fun(df, Ukraine) 
#> data.frame(x = 2L, Country = "Ukraine")
#>   x Country
#> 1 2 Ukraine

创建于2023-06-02使用reprex v2.0.2
底线是,代码是臃肿的,也怪异和不安全,你不应该提供字符串作为变量只是为了备用双引号,你将如何提供“英国”?
正确的方法是将Country_name作为字符串提供,并具有:

fun <- function(df, Country_name){
  df %>%
    filter(Country == Country_name)
}

或者为了更加安全,如果df可能包含一个Country_name列,该列将与参数冲突:

fun <- function(df, Country_name){
  df %>%
    filter(Country == .env$Country_name)
}

fun <- function(df, Country_name){
  df %>%
    filter(Country == !!Country_name)
}

相关问题