dplyr/rlang: parse_expr with multiple expressions
We can use the triple-bang operator with the plural form parse_exprs
and a modified e2
expression to parse multiple expressions (see ?parse_quosures
):
Explanation:
- Multiple expressions in
e2
need to be separated either by;
or by new lines. - From
?quasiquotation
: The!!!
operator unquotes and splices its argument. The argument should represents a list or a vector.
e2 = "vs + am ; am +vs";
mtcars %>% mutate(!!!parse_exprs(e2))
# mpg cyl disp hp drat wt qsec vs am gear carb vs + am am + vs
#1 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4 1 1
#2 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4 1 1
#3 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1 2 2
#4 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1 1 1
#5 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2 0 0
#6 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1 1 1
#7 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4 0 0
#8 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2 1 1
#9 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2 1 1
#10 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4 1 1
#11 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4 1 1
#12 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3 0 0
#13 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3 0 0
#14 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3 0 0
#15 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4 0 0
#16 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4 0 0
#17 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4 0 0
#18 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1 2 2
#19 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2 2 2
#20 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1 2 2
#21 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1 1 1
#22 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2 0 0
#23 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2 0 0
#24 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4 0 0
#25 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2 0 0
#26 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1 2 2
#27 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2 1 1
#28 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2 2 2
#29 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4 1 1
#30 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6 1 1
#31 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8 1 1
#32 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2 2 2
Using rlang Package to Parse Quoted Argument
Use rlang::parse_expr
to convert strings to expressions and eval
to evaluate them. eval()
allows you to provide context for the expression in its second argument, where we supply the person
data frame to it. In case of filter
, the context is already understood to be the dataframe on the left-hand side of the %>%
pipe.
Another difference in how we handle the two expressions is that filter()
has an additional internal layer of quasiquoation. Since you already have an expression, you don't need it to be quoted again, so you would use !!
to unquote it.
different_age_friends <- function(condition, p = person, f = friends)
{
stmts <- str_split(condition, " ~ ")[[1]] %>% map( rlang::parse_expr )
if( eval(stmts[[1]], p) ) # Effectively: eval(age == 20, person)
f %>% filter(!!stmts[[2]]) # Effectively: friends %>% filter(age == 48)
else
f
}
different_age_friends(condition = "age == 20 ~ age == 48")
# # A tibble: 2 x 2
# id age
# <dbl> <dbl>
# 1 2 48
# 2 5 48
Minor note:
- You didn't provide a value for
different_age_friends
when the condition is false. I made an assumption that in this case, the entire friends list is to be returned.
How to use an expression in dplyr::mutate in R
Any of these work. The second is similar to the first but does not require that rlang
be on the search path. The third and fourth also work if the d=
part is not present in expr
in which case default names are used. The last one uses only base R and is also the shortest.
data %>% mutate(within(., !!parse_expr(expr)))
data %>% mutate(within(., !!parse(text = expr)))
data %>% mutate(data, !!parse_expr(sprintf("tibble(%s)", expr)))
data %>% { eval_tidy(parse_expr(sprintf("mutate(., %s)", expr))) }
within(data, eval(parse(text = expr))) # base R
Note
Assume this premable:
library(dplyr)
library(rlang)
# input
data <- tibble(a = c(1, 2), b = c(3, 4))
expr <- "d = a + b"
How to pass an expression to the filter() verb the tidy way?
We can select
the columns of interest and extract the components with ..1
, ..2
(or .x
, .y
- if there are only 2 columns)
library(dplyr)
library(tibble)
library(purrr)
tibble(pick_these = get_these %>%
map(rlang::parse_expr)) %>%
mutate(wat = names(get_these),
goods = list(diamonds)) %>%
mutate(goods = pmap(select(., goods, pick_these), ~ {
..1 %>%
filter(rlang::eval_tidy( ..2))
})) %>%
dplyr::select(goods, wat)
# A tibble: 2 x 2
# goods wat
# <list> <chr>
#1 <tibble [3,903 × 10]> Ideal E
#2 <tibble [307 × 10]> Good J
Functions dplyr with rlang::last_error() in purrr::map loop in r
The specific error is caused because intersect_list
has empty items in the list, which cannot be joined because they are empty, and hence have no columns to join by. If you modified the map function to only use non-empty items of intersect_list
you would not get that error.
As you noted in the comments, removing the empty list entries with keep(intersect_list, ~ !is.null(.))
before mapping left_join
onto the list items will fix the error.
However, I don't think this is the most elegant way to solve this problem. I might misunderstand what the goal is, but if it's to produce a raster from the total length of lines within each grid cell, I think a simpler approach without using purrr
might work.
This is not the exact same as your product, but I'm keeping it simpler rn to illustrate an alternate approach. Here is a sum of the lengths in each cell as a stars
object (similar to raster
but plays better with the tidyverse and sf
).
I'm starting off from your objects one_df
and grid
:
# Turn multiple lines into single MULTILINESTRING:
one_df %>%
st_union() ->
union_df
# Intersection of each grid cell with the MULTILINESTRING geometry:
grid %>%
st_intersection(union_df) ->
grid_lines
# Get lengths:
grid_lines %>%
mutate(length = st_length(x)) %>%
st_drop_geometry() ->
grid_lengths
# Join the calculated lengths back with the spatial grid,
# most of which will have NA for length
grid %>%
left_join(grid_lengths, by = "cell") ->
grid_with_lengths
# Rasterize the length field of the grid
grid_with_lengths %>%
dplyr::select(length) %>%
stars::st_rasterize() ->
length_stars
length_stars %>% mapview::mapview()
rlang Expressions in List as Args to Function
After parse_exprs()
, you end up with assignment expressions stored in an unnamed list:
# [[1]]
# base = 10
#
# [[2]]
# offset = 0
while, to get the effect you want with !!!
, they need to be values in a named list:
# $base
# [1] 10
#
# $offset
# [1] 0
With that said, I suggest "forwarding the dots" instead, because it leads to a simpler and more flexible implementation:
add_step <- function(x, step, selector, ...){
f <- parse_expr(paste0("step_", step))
vars <- sym(selector)
step_expr <- call2(f, vars, ...) # <---- forwarding the dots here
expr(!!x %>% !!step_expr)
}
The user can now simply provide the desired arguments directly to add_step()
:
rcp %>% add_step(user_step, user_selector, base=10, offset=0)
# recipe(mpg ~ cyl + hp + wt + disp, data = mtcars) %>%
# step_log(disp, base = 10, offset = 0)
OR store them in a list and use !!!
on your function:
user_args <- list(base = 10, offset=0)
rcp %>% add_step(user_step, user_selector, !!!user_args)
R dplyr pass expression as argument to function
Any of these seems to work with parsing expression: parse_expr()
and parse_quo()
library(rlang)
library(dplyr)
test_func1 <- function(data, myexpr){
data %>%
filter(!!parse_expr(myexpr))
}
test_func2 <- function(data, myexpr){
data %>%
filter(!!parse_quo(myexpr, env = global_env()))
}
mtcars %>%
test_func1(myexpr = "cyl > 6")
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
#> Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
#> Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3
#> Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3
#> Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3
#> Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4
#> Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4
#> Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4
#> Dodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2
#> AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2
#> Camaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4
#> Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2
#> Ford Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4
#> Maserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8
mtcars %>%
test_func2(myexpr = "cyl > 6")
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
#> Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
#> Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3
#> Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3
#> Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3
#> Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4
#> Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4
#> Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4
#> Dodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2
#> AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2
#> Camaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4
#> Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2
#> Ford Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4
#> Maserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8
Created on 2020-06-16 by the reprex package (v0.3.0)
How to use dynamic tidy-select expressions in dplyr::select()?
You can't just pass in a string. A string is not the same as an expression. One way is to use purrr
and rlang
to build the expression and then inject that into the select
library(purrr)
library(rlang)
query <- map2(
map(x, ~expr(contains(!!.x))),
map(names(x), ~expr(contains(!!.x))),
~expr((!!.x & !!.y))) %>%
reduce(~expr(!!.x | !!.y))
dplyr::select(df, !!query)
Though if you really wanted to build the code as a string, then you would just need to parse that string into an expression first using rlang::parse_expr
. You just need to add some quotes to your string so it exactly matches the code you used before
s <- paste0("(", paste0("contains(\"", names(x),"\") & contains(\"", x, "\")"), ")", collapse = " | ")
dplyr::select(df, !!rlang::parse_expr(s))
Related Topics
Calling a User-Defined R Function from C++ Using Rcpp
Get Margin Line Locations in Log Space
Aggregating All Unique Values of Each Column of Data Frame
Format Ttest Output by R for Tex
How to Read Specific Rows of CSV File with Fread Function
Count the Number of Unique Characters in a String
Reshape Multi Id Repeated Variable Readings from Long to Wide
Error in File(File, "Rt"):Invalid 'Description' Argument in Complete.Cases Program
Any Way to Force Fread() of Data.Table Not to Stop on Empty Lines
Correctly Color Vertices in R Igraph
Rjava Linker Error Licuuc with Anaconda & Fopenmp Error Without Anaconda for MACos Sierra 10.12.4
R Xml - Combining Parent and Child Nodes into Data Frame
Calculating a Distance Matrix by Dtw
Substitute Dt1.X with Dt2.Y When Dt1.X and Dt2.X Match in R
Difference Between 'Names(Df[1]) <- ' and 'Names(Df)[1] <- '