Rlang::Sym in Anonymous Functions

rlang::sym in anonymous functions

The issue is not anonymous functions, but the operator precedence of !!. The help page for !! states that

The !! operator unquotes its argument. It gets evaluated immediately in the surrounding context.

This implies that when you write a complex NSE expression, such as select inside mutate, the unquoting will take place in the environment of the expression as a whole. As pointed out by @lionel, the unquoting then takes precedence over other things, such as creation of anonymous function environments.

In your case the !! unquoting is done with respect to the outer mutate(), which then attempts to find column x1 inside d, not data. There are two possible solutions:

1) Pull the expression involving !! into a standalone function (as you've done in your question):

res1 <- d %>% mutate(tmp = map2(x, y, get_it))

2) Replace !! with eval to delay expression evaluation:

res2 <- d %>% mutate(tmp = map2(x, y, function(a, b){
data %>%
mutate(y1 = eval(rlang::sym(a))) %>%
mutate(y2 = eval(rlang::sym(b))) %>%
select(y1, y2, val)
}))

identical(res1, res2) #TRUE

Why do `rlang::sym` and `rlang::quo_name` in LHS of expression behave similarly?

Moving comment to answer.

As per mentioned in the documentation you are referring to:

The rules on the LHS are slightly different: the unquoted operand
should evaluate to a string or a symbol.

Here, it works because x_ is, in fact, a character.

Using rlang::sym inside list definition

We can also put replace_na inside mutate and unquote:

library(dplyr)
library(tidyr)

var <- 'user_name'

data %>%
mutate(!!var := replace_na(!!sym(var), 0))

Result:

  user_name
1 0
2 1
3 2
4 3
5 4
6 5

Data:

data <- data.frame(user_name = c(NA, 1:5))

Declaring a sym without backticks in rlang

You intend na.omit(iris) to be interpreted as a call, not a single symbol. You already capture the call when you use substitute, but for some reason you convert it into a string by using deparse, then convert that into a single symbol using rlang::sym. The backticks appear because you have told rlang to turn the string into a single symbol, rather than keeping it as a call.

Why not use substitute on its own? It's simple and doesn't require an additional package.

afun <- function(formula, data, level) {

call('somefunction',
formula = formula,
data = substitute(data),
vec = c(2, 3, 4),
level = level)
}

afun(Sepal.Width ~ Petal.Length, na.omit(iris), 4)
#> somefunction(formula = Sepal.Width ~ Petal.Length, data = na.omit(iris),
#> vec = c(2, 3, 4), level = 4)

Understanding when to use ensym, sym vs enquo in a function

Your understanding is correct. sym/ensym is preferred when referencing a column in an existing data frame. enquo() will, of course, work as well, but it captures any arbitrary expression, allowing the user to specify things like mpg * cyl or log10(mpg + cyl)/2. If your downstream code assumes that xvar and yvar are single columns, having arbitrary expressions can lead to problems or unexpected behavior. In that sense, ensym() acts an argument verification step when you expect a reference to a single column.

As for converting symbols to strings, one approach is to use deparse():

median(dat[[deparse(ensym(xvar))]])

To get rlang::as_string to work, you need to drop !!, because you want to convert the expression itself to a string, not what the expression is referring to (e.g., mpg, cyl, etc.):

median(dat[[rlang::as_string(ensym(xvar))]])

Passing in strings to functions using rlang::sym and exclamation marks

Try this:

makeMM <- function(data, col) model.matrix(~ . - 1, data[col])

# test
makeMM(iris, "Species")

`r`/`rlang`/`dplyr`: How to render `sym` resilient to `NULL`?

If we need to check for NULL case, use an if condition

df1 <- if(!is.null(c)) {
df %>%
dplyr::mutate(b = !!dplyr::sym(c))
} else df

With multiple columns, an option is map

library(purrr)
b_column <- c_column <- "a"
map2_dfc(list(b_column, c_column), c("b", "c"), ~
if(!is.null(.x)) df %>%
transmute(!! .y := !! sym(.x))) %>%
bind_cols(df, .)

-output

#  a b c
#1 1 1 1
#2 2 2 2
#3 3 3 3
#4 4 4 4
#5 5 5 5

If one of them is NULL

c_column <- NULL
map2_dfc(list(b_column, c_column), c("b", "c"), ~
if(!is.null(.x)) df %>%
transmute(!! .y := !! sym(.x))) %>%
bind_cols(df, .)
# a b
#1 1 1
#2 2 2
#3 3 3
#4 4 4
#5 5 5

Another option is mutate with across, but make sure that we need to rename only the columns that are not NULL

nm1 <- c("b", "c")
i1 <- !map_lgl(list(b_column, c_column), is.null)
nm2 <- nm1[i1]
df %>%
mutate(across(all_of(c(b_column, c_column)), ~ .)) %>%
rename_at(vars(everything()), ~ nm2) %>%
bind_cols(df, .)

how to use rlang::as_string() inside of a function?

We may use ensym to convert to symbol and then apply the as_string

find_mean <- function(varname){
v1 <- rlang::as_string(rlang::ensym(varname))
tibble(mean = mean(pull(mtcars, {{varname}})),
variable = v1)
}

-testing

find_mean(qsec)
# A tibble: 1 x 2
mean variable
<dbl> <chr>
1 17.8 qsec

This can be done in base R as well, i.e. with deparse/substitute

> find_mean <- function(varname) deparse(substitute(varname))

> find_mean(qsec)
[1] "qsec"


Related Topics



Leave a reply



Submit