R: Using a string as an argument to mutate verb in dplyr
Asked Answered
Y

2

11

I am building a shiny app which needs to allow users to define new variables for plotting. Specifically I want to allow users to define an expression to be used in mutate verb. The server receives the expression as text and I am wondering how to make mutate execute it in dplyr 0.7. I can make it work (partially) using mutate_ but it is deprecated now. It also defines the new column name as the entire expression rather than the new variable

Here is a reproducible example:

input_from_shiny <- "Petal.ratio = Petal.Length/Petal.Width"
iris_mutated <- iris %>% mutate_(input_from_shiny)

This gives the following

> head(iris_mutated)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Petal.ratio = Petal.Length/Petal.Width
1          5.1         3.5          1.4         0.2  setosa                                   7.00
2          4.9         3.0          1.4         0.2  setosa                                   7.00
3          4.7         3.2          1.3         0.2  setosa                                   6.50
4          4.6         3.1          1.5         0.2  setosa                                   7.50
5          5.0         3.6          1.4         0.2  setosa                                   7.00
6          5.4         3.9          1.7         0.4  setosa                                   4.25

Technically, I can use regular expression to extract new variable name from the string and rename the new column accordingly, but I am wondering what is the correct way to implement it using latest dplyr version (was reading https://cran.r-project.org/web/packages/dplyr/vignettes/programming.html, but could not figure it out)

Yestreen answered 24/3, 2018 at 21:9 Comment(0)
I
13

We can use rlang::parse_quosure() together with !! (bang bang) to produce the same result:

  • parse_quosure: parses the supplied string and converts it into a quosure

  • !!: unquotes a quosure so it can be evaluated by tidyeval verbs

Note that parse_quosure() was soft-deprecated and renamed to parse_quo() in rlang 0.2.0 per its documentation. If we use parse_quo(), we need to specify the environment for the quosures e.g. parse_quo(input_from_shiny, env = caller_env())

library(rlang)
library(tidyverse)

input_from_shiny <- "Petal.ratio = Petal.Length/Petal.Width"
iris_mutated <- iris %>% mutate_(input_from_shiny)

iris_mutated2 <- iris %>% 
  mutate(!!parse_quosure(input_from_shiny))
head(iris_mutated2)

#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa
#>   Petal.ratio = Petal.Length/Petal.Width
#> 1                                   7.00
#> 2                                   7.00
#> 3                                   6.50
#> 4                                   7.50
#> 5                                   7.00
#> 6                                   4.25


identical(iris_mutated, iris_mutated2)
#> [1] TRUE

Edit: to separate LHS & RHS

lhs <- "Petal.ratio"
rhs <- "Petal.Length/Petal.Width"

iris_mutated3 <- iris %>% 
  mutate(!!lhs := !!parse_quosure(rhs))
head(iris_mutated3)

> head(iris_mutated3)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
  Petal.ratio
1        7.00
2        7.00
3        6.50
4        7.50
5        7.00
6        4.25

Created on 2018-03-24 by the reprex package (v0.2.0).

Igniter answered 24/3, 2018 at 21:58 Comment(4)
is there a way to automatically make the column name right or the only way is to rename it afterwards?Yestreen
You'd have to have the column name as a separate variable or parse it out as a separate variable and use mutate(!!new_col := !!parse_quosure(rest_of_expression)Gagman
@Jake: !! is not syntactic sugar because UQ is not a function (and will go away in a future version of rlang)Duce
Thanks for the clarification and heads up! Deleted my previous inaccurate comment.Gagman
C
4

Package friendlyeval is a simplified interface to tidy eval that tries to make things a bit more straight forward in cases like these.

Splitting your string in two, you obtain part of the string you wish to use as a column name and part of a string you wish to use as an expression. So you could write:

library(friendlyeval)
library(dplyr)
lhs <- "Petal.ratio"
rhs <- "Petal.Length/Petal.Width"

iris_mutated3 <- 
  iris %>% 
  mutate(!!treat_string_as_col(lhs) := !!treat_string_as_expr(rhs))
head(iris_mutated3)

By using the function on the lhs, you gain checking that lhs can be parsed as plain column name.

friendlyeval code can be converted to plain tidy eval code at any time using an RStudio addin.

Criswell answered 24/6, 2018 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.