简体   繁体   中英

r rlang: using is_quosure on tidyselect helper

Suppose that you have an argument in a R function that could be either:

  1. a raw tidyselect helper such as contains("a") , starts_with("a") etc,
  2. a list of quosures with the helpers, with vars(contains("a")) .

How do you check within the function if you are in case (1) or (2)?

The problem is that is_quosures(vars(contains("a"))) works, but is_quosures(contains("a")) will not work, as it tries first to evaluate the function contains("a") , which returns an error when evaluated alone!?

library(rlang)
library(dplyr)
is_quosures(vars(contains("a")))
#> [1] TRUE
is_quosures(contains("a"))
#> Error: No tidyselect variables were registered

fo <- function(var) {
  is_quosures(var)  
}

fo(vars(contains("a")))
#> [1] TRUE
fo(contains("a"))
#> Error: No tidyselect variables were registered

Created on 2019-12-03 by the reprex package (v0.3.0)

Use case

You want to use a function such as summarise_at(data, var) , and want to make it robust to the user specifying var as a direct tidyselect helper, or wrapped within a vars() call. Only way I figured out to handle this is to do a case-by-case if/then checking if it is a quosure or not (then wrap into vars if not), but this will precisely fail in the case above.

library(rlang)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

fo <- function(var) {
  is_var_closure <- rlang::is_quosures(var)
  if(is_var_closure) {
    dplyr::summarise_at(iris, var, mean)  
  } else {
    dplyr::summarise_at(iris, quos(var), mean)  
  }
}


fo(vars(starts_with("Sepal")))
#>   Sepal.Length Sepal.Width
#> 1     5.843333    3.057333
fo(starts_with("Sepal"))
#> Error: No tidyselect variables were registered

Created on 2019-12-03 by the reprex package (v0.3.0)

The way I've done this before is by capturing the expression and checking with is_call :

f <- function(var) {
  if (rlang::is_call(rlang::enexpr(var), names(tidyselect::vars_select_helpers))) {
    rlang::enquos(var)
  }
  else {
    stopifnot(rlang::is_quosures(var)) # or something more specific with a useful message
    var
  }
}

# both work
f(vars(starts_with("Sepal")))
f(starts_with("Sepal"))

Just make sure to use enexpr for is_call , see this GitHub issue .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM