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 sym
bol 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
How to Properly Document S4 Methods Using Roxygen2
Converting Data Frame Column from Character to Numeric
How to Create Vectors with Specific Intervals in R
Aggregate Methods Treat Missing Values (Na) Differently
Two-Way Density Plot Combined with One Way Density Plot with Selected Regions in R
Specify Widths and Heights of Plots with Grid.Arrange
Sp::Over() for Point in Polygon Analysis
Creating (And Accessing) a Sparse Matrix with Na Default Entries
Documenttermmatrix Error on Corpus Argument
Removing Whitespace from a Whole Data Frame in R
Horizontal Dendrogram in R with Labels
Convert Factor to Integer in a Data Frame
R Markdown - Variable Output Name
Adjust Plot Title (Main) Position
Fast Large Matrix Multiplication in R
Obtaining Threshold Values from a Roc Curve
Add Color to Boxplot - "Continuous Value Supplied to Discrete Scale" Error