Assume I have the following data structure:
library(dplyr)
d <- tibble(x = paste0("x", 1:3),
op = c("f", "g", "h"),
y = paste0("y", 1:3),
res = paste0("z", 1:3))
I want to create a new column cmd
which contains an unevaluated call which should eventually look like this:
(D <- d %>%
mutate(cmd = list(quote(z1 <- f(x1, y1)),
quote(z2 <- g(x2, y2)),
quote(z3 <- h(x3, y3)))))
# # A tibble: 3 × 5
# x op y res cmd
# <chr> <chr> <chr> <chr> <list>
# 1 x1 f y1 z1 <language>
# 2 x2 g y2 z2 <language>
# 3 x3 h y3 z3 <language>
but of course I do not want to hardcode these lines, but wanted to pull these values form the corresponding rows from d
, but I was not successful:
d %>%
rowwise() %>%
mutate(cmd = list(expr(!!sym(res) <- !!sym(op)(!!sym(x), !!sym(y)))))
results in:
Error: object 'res' not found
How would I achieve the goal?
N.B. cmd
will finally be evaluated in an environment where the arguments as well as the functions are defined, so conceptually something like:
e <- list2env(list(x1 = 1, x2 = 2, x3 = 3, y1 = 2, y2 = 2, y3 = 3,
f = \(x, y) x + y, g = \(x, y) x - y, h = \(x, y) x * y))
eval(D$cmd[[1]], e)
e$z1
# [1] 3
You've got good base R answers so I'll focus on tidyverse
, which seems to change a lot. This question from 2017 is related to part of your question (constructing an assignment call) but the accepted answer suggests using rlang::lang()
, which is now deprecated in favour of rlang::call2()
. So I think the recommended tidyverse
approach would now be this:
library(dplyr)
library(rlang)
D <- d %>%
rowwise() %>%
mutate(
cmd = list(
call2(
`<-`, sym(res), call2(op, sym(x), sym(y))
)
)
) %>%
ungroup()
Which gives you a cmd
column that looks like this:
D$cmd
# .Primitive("<-")(z1, f(x1, y1))
# [[2]]
# .Primitive("<-")(z2, g(x2, y2))
# [[3]]
# .Primitive("<-")(z3, h(x3, y3))
Which evaluates as desired in the environment in your question:
sapply(D$cmd, \(x) eval(x, e))
# [1] 3 0 9
And we can also see that this assigns to the desired variables in e
:
mget(d$res, e)
# $z1
# [1] 3
# $z2
# [1] 0
# $z3
# [1] 9