├── .Rbuildignore ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── friendlyeval.R └── transform.R ├── README.md ├── appveyor.yml ├── inst └── rstudio │ └── addins.dcf ├── man ├── friendlyeval_to_rlang.Rd ├── treat_input_as_col.Rd ├── treat_input_as_expr.Rd ├── treat_inputs_as_cols.Rd ├── treat_inputs_as_exprs.Rd ├── treat_string_as_col.Rd ├── treat_string_as_expr.Rd ├── treat_strings_as_cols.Rd └── treat_strings_as_exprs.Rd ├── media └── friendlyeval.gif └── tests ├── testthat.R └── testthat └── test_friendlyeval.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.travis\.yml$ 2 | ^LICENSE\.md 3 | ^appveyor\.yml$ 4 | ^media 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | sudo: false 5 | cache: packages 6 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: friendlyeval 2 | Title: A Friendly Interface to Tidyeval 3 | Version: 0.1.1 4 | Authors@R: c( 5 | person("Miles", "McBain", email = "miles.mcbain@gmail.com", role = c("aut", "cre")), 6 | person("Patrick", "Kennedy", role = "aut"), 7 | person("Alicia", "Schep", role = "aut"), 8 | person("Lionel","Henry", role = "ctb"), 9 | person("Nicholas", "Tierney", role = "ctb")) 10 | Description: Provides an alternative auto-complete friendly interface to rlang 11 | that is more closely aligned with the task domain of a user 'programming with 12 | dplyr'. Facility is provided to replace friendlyeval functions with their rlang 13 | equivalents after protyping a solution. 14 | Depends: R (>= 3.4.0) 15 | License: MIT + file LICENSE 16 | Encoding: UTF-8 17 | LazyData: true 18 | Imports: rlang (>= 0.3.0.0), 19 | purrr, 20 | tibble, 21 | rstudioapi 22 | RoxygenNote: 6.1.0 23 | Suggests: testthat, 24 | dplyr 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2018 2 | COPYRIGHT HOLDER: Miles McBain 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2018 Miles McBain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(friendlyeval_to_rlang) 4 | export(treat_input_as_col) 5 | export(treat_input_as_expr) 6 | export(treat_inputs_as_cols) 7 | export(treat_inputs_as_exprs) 8 | export(treat_string_as_col) 9 | export(treat_string_as_expr) 10 | export(treat_strings_as_cols) 11 | export(treat_strings_as_exprs) 12 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # 0.1.1 2 | * Remove hack in treat_strings_as_exprs, now fully supported in `rlang` 0.3.0. 3 | * Add dependency on `rlang` version. 4 | 5 | # Friendlyeval 0.1.0 6 | * API Overhaul 7 | - increased from 5 to 8 functions 8 | - all functions have better names including `treat_` prefix 9 | - `_lhs` variant for column names is no longer required. 10 | * Documentation overhaul. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /R/friendlyeval.R: -------------------------------------------------------------------------------- 1 | ##' Take what was input and treat it as a column name argument to a dplyr function. 2 | ##' 3 | ##' This is used inside a function to pass the literal text of what the caller 4 | ##' typed as an argument to a `dplyr` function. When using `dplyr` the text will 5 | ##' typically be destined for a column name argument. See examples for usage 6 | ##' scenarios. 7 | ##' 8 | ##' @title treat_input_as_col 9 | ##' @usage treat_input_as_col(arg) 10 | ##' @param arg the argument for which the literal input text is to be used 11 | ##' as a column name. 12 | ##' @return Something that will resolve to a column named when prefixed with !!. 13 | ##' @examples 14 | ##' \dontrun{ 15 | ##' select_this <- function(a_col){ 16 | ##' select(mtcars, !!treat_input as_col(a_col)) 17 | ##' } 18 | ##' select_this(cyl) 19 | ##' 20 | ##' mean_this <- function(a_col){ 21 | ##' mutate(mtcars, result = mean(!!treat_input_as_col(a_col))) 22 | ##' } 23 | ##' mean_this(cyl) 24 | ##' 25 | ##' filter_same <- function(dat, x, y) { 26 | ##' filter(dat, !!treat_input_as_col(x) == !!treat_input_as_col(y)) 27 | ##' } 28 | ##' filter_same(mtcars, carb, gear) 29 | ##' } 30 | ##' @export 31 | treat_input_as_col <- rlang::ensym 32 | 33 | 34 | ##' Take what was input and treat it as an expression argument to dplyr function. 35 | ##' 36 | ##' This is used inside a function to pass the literal text of what the caller 37 | ##' typed as an expression to a `dplyr` function. These might be 38 | ##' logical expressions passed to filter: `filter(dat, col == "example")`, or 39 | ##' functions of columns passed to mutate or summarise: `summarise(dat, mean(col))`. 40 | ##' 41 | ##' @title treat_input_as_expr 42 | ##' @usage treat_input_as_expr(arg) 43 | ##' @param arg the argument for which the literal input text is to be used as an 44 | ##' expression. 45 | ##' @return something that will resolve to an expression when prefixed by `!!` 46 | ##' @export 47 | treat_input_as_expr <- rlang::enquo 48 | 49 | 50 | ##' Take the literal text input for a comma separated list of arguments and treat it as list of column names in a dplyr function. 51 | ##' 52 | ##' The most common usage of this is to pass `...`, from your function directly through to dplyr functions as column names, as in the `select_these` example. 53 | ##' 54 | ##' This function must be prefixed with `!!!` to treat the output as a list. 55 | ##' 56 | ##' @title treat_inputs_as_cols 57 | ##' @usage treat_inputs_as_cols(...) 58 | ##' @param ... a comma separated list of arguments to be treated as column names. 59 | ##' @return something that will resolve to a list of column names when prefixed 60 | ##' with `!!!` 61 | ##' @examples 62 | ##' \dontrun{ 63 | ##' select_these <- function(dat, ...){ 64 | ##' select(dat, !!!treat_inputs_as_cols(...)) 65 | ##' } 66 | ##' select_these(mtcars, cyl, wt) 67 | ##' } 68 | ##' @export 69 | treat_inputs_as_cols <- function(...){ 70 | eval.parent(rlang::ensyms(...)) 71 | } 72 | 73 | ##' Take the literal text input for a comma separated list of arguments and treat it as a list of expressions in a dplyr function. 74 | ##' 75 | ##' Common usage of this is to pass `...` from your function directly through to 76 | ##' dplyr functions as expressions. This could be a list of filtering 77 | ##' expressions for filter: `filter(dat, col1 == "example1", col2 == 78 | ##' "example2")`, or a list of functions of columns to `mutate` or `summarise`: 79 | ##' `summarise(dat, mean(col1), var(col1))` 80 | ##' 81 | ##' This function must be prefixed with `!!!` to treat the output as a list. 82 | ##' 83 | ##' @title treat_inputs_as_exprs 84 | ##' @param ... a comma separated list of arguments to be treated as expressions. 85 | ##' @return something that will resolve to a list of expressions when prefixed with `!!!` 86 | ##' @export 87 | treat_inputs_as_exprs <- function(...){ 88 | eval.parent(rlang::enquos(...)) 89 | } 90 | 91 | ##' Take the a string value and use it as a column name in a 92 | ##' dplyr function. 93 | ##' 94 | ##' This is used to take the string value of a variable and use it 95 | ##' in place of a literal column name when calling a dplyr function. This 96 | ##' ability is useful when the name of the column to operate on is determined at 97 | ##' run-time from data. 98 | ##' 99 | ##' @title treat_string_as_col 100 | ##' @usage treat_string_as_col(arg) 101 | ##' @param arg the argument that holds a value to be used as a column name. 102 | ##' @return something that will resolve to a column name when prefixed with `!!`. 103 | ##' @examples 104 | ##' \dontrun{ 105 | ##' ## drop this run-time determined column. 106 | ##' b <- "cyl" 107 | ##' select(mtcars, -!!treat_string_as_col(b)) 108 | ##' 109 | ##' ## function double a column 110 | ##' double_col <- function(dat, arg) { 111 | ##' dplyr::mutate(dat, result = !!rlang::sym(arg) * 2) 112 | ##' } 113 | ##' double_col(mtcars, arg = 'cyl') 114 | ##' 115 | ##' } 116 | ##' @export 117 | treat_string_as_col <- function(arg){ 118 | rlang::sym(arg) 119 | } 120 | 121 | ##' Take a list/vector of strings and use the values as column 122 | ##' names. 123 | ##' 124 | ##' This is used to take the character values of a list or vector and use them 125 | ##' as a comma separated list of column names in a dplyr function. The most 126 | ##' common usage would be applied yo the values of a single argument, as in the 127 | ##' `select_these2` example, however it can also be used with ... to transform 128 | ##' all ... values to column names - see `select_these3` example. 129 | ##' 130 | ##' @title treat_strings_as_cols 131 | ##' @usage treat_strings_as_cols(arg) 132 | ##' @param arg the argument that holds a list of values to be used as column names. 133 | ##' @return something that will resolve to a comma separated list of column 134 | ##' names when prefixed with `!!!`. 135 | ##' @examples 136 | ##' \dontrun{ 137 | ##' select_these2 <- function(dat, cols){ 138 | ##' select(dat, !!!treat_strings_as_cols(cols)) 139 | ##' } 140 | ##' select_these2(mtcars, cols = c("cyl", "wt")) 141 | ##' 142 | ##' select_these3 <- function(dat, ...){ 143 | ##' dots <- list(...) 144 | ##' select(dat, !!!treat_strings_as_cols(dots)) 145 | ##' } 146 | ##' select_these3(mtcars, "cyl", "wt") 147 | ##' 148 | ##' select_not_these <- function(dat, cols){ 149 | ##' select(dat, -c(!!!treat_strings_as_cols(cols))) 150 | ##' } 151 | ##' select_not_these(mtcars, cols = c("cyl", "wt")) 152 | ##' 153 | ##' } 154 | ##' @export 155 | treat_strings_as_cols <- function(arg){ 156 | rlang::syms(arg) 157 | } 158 | 159 | 160 | ##' Treat the string value of a variable as an expression in a dplyr function. 161 | ##' 162 | ##' This will parse a string and treat it as an expression to be evaluated 163 | ##' in the context of a dplyr function call. This may be convenient when 164 | ##' building expressions to evaluate at run-time. 165 | ##' 166 | ##' @title treat_string_as_expr(x) 167 | ##' @usage treat_string_as_expr(x) 168 | ##' @param x a string to be treated as an expression. 169 | ##' @return something that will resolve to an expression when prefixed with `!!` 170 | ##' @examples 171 | ##' \dontrun{ 172 | ##' 173 | ##' ## processing operations from other notation 174 | ##' calc_result <- function(dat, operation){ 175 | ##' operation <- gsub('x', '*', operation) 176 | ##' mutate(dat, result = !!treat_string_as_expr(operation)) 177 | ##' } 178 | ##' 179 | ##' calc_result(mtcars, "mpg x hp") 180 | ##' } 181 | ##' 182 | ##' @export 183 | treat_string_as_expr <- rlang::parse_expr 184 | 185 | ##' Treat the string values of a character vector as expressions in a dplyr function. 186 | ##' 187 | ##' This will parse a vector of strings and treat them as a list of 188 | ##' expressions to be evaluated in the context of a dplyr function. This may be 189 | ##' convenient when building expressions to evaluate at run time. 190 | ##' 191 | ##' 192 | ##' @title treat_strings_as_exprs(arg) 193 | ##' @param arg a vector of strings to be treated as expressions. 194 | ##' @return something that will resolve to a list of expressions when prefixed with `!!!` 195 | ##' @examples 196 | ##' \dontrun{ 197 | ##' summarise_uppr <- function(dat, ...){ 198 | ##' ## need to capture a character vector 199 | ##' dots <- as.character(list(...)) 200 | ##' functions <- tolower(unlist(dots)) 201 | ##' summarise(dat, !!!treat_strings_as_exprs(functions)) 202 | ##' } 203 | ##' 204 | ##' summarise_uppr(mtcars, 'MEAN(mpg)', 'VAR(mpg)') 205 | ##' } 206 | ##' @export 207 | treat_strings_as_exprs <- function(arg){ 208 | rlang::parse_exprs(arg) 209 | } 210 | 211 | -------------------------------------------------------------------------------- /R/transform.R: -------------------------------------------------------------------------------- 1 | .friendlyeval <- new.env(parent=emptyenv()) 2 | .friendlyeval$transforms <- 3 | tibble::tribble( 4 | ~friendly, ~rlang, 5 | "treat_input_as_col", "rlang::ensym", 6 | "treat_inputs_as_cols", "rlang::ensyms", 7 | "treat_input_as_expr", "rlang::enquo", 8 | "treat_inputs_as_exprs", "rlang::enquos", 9 | "treat_string_as_col", "rlang::sym", 10 | "treat_strings_as_cols", "rlang::syms", 11 | "treat_string_as_expr", "rlang::parse_expr", 12 | "treat_strings_as_exprs", "(function(x){rlang::parse_exprs(textConnection(x))})" 13 | ) 14 | 15 | #' Convert friendlyeval functions to rlang 16 | #' 17 | #' Works on a RStudio document selection if one exists, or the entire 18 | #' active source editor if no selection exists. 19 | #' 20 | #' @return nothing. 21 | #' @export 22 | #' 23 | friendlyeval_to_rlang <- function(){ 24 | 25 | if (rstudioapi::isAvailable()){ 26 | source_context <- rstudioapi::getSourceEditorContext() 27 | selection_content <- source_context$selection[[1]]$text 28 | 29 | if (nzchar(selection_content)){ 30 | ## replace all friendlyeval functions in selection with rlang 31 | rlang_content <- replace_friendly(selection_content) 32 | rstudioapi::modifyRange(location = source_context$selection[[1]]$range, 33 | text = rlang_content, 34 | id = source_context$id) 35 | 36 | } else { 37 | ## replace all friendlyeval functions in open document with rlang 38 | rlang_content <- replace_friendly(source_context$contents) 39 | rstudioapi::setDocumentContents(text = paste0(rlang_content, collapse = "\n"), 40 | id = source_context$id) 41 | } 42 | } 43 | } 44 | 45 | replace_friendly <- function(text){ 46 | 47 | ## replace functions using the map in .friendlyeval$transforms 48 | rlang_text <- purrr::reduce2( 49 | .x = .friendlyeval$transforms$friendly, 50 | .y = .friendlyeval$transforms$rlang, 51 | .f = function(text, friendly, rlang){ 52 | gsub(pattern = paste0("\\b",friendly,"\\b"), 53 | replacement = rlang, x = text) 54 | }, 55 | .init = text 56 | ) 57 | 58 | ## clean up any 'friendlyeval::' 59 | rlang_text <- gsub('friendlyeval::', '', rlang_text) 60 | rlang_text 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # friendlyeval 2 | 3 | [![Travis-CI Build Status](https://api.travis-ci.org/MilesMcBain/friendlyeval.svg?branch=master)](https://travis-ci.org/MilesMcBain/friendlyeval) 4 | [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/milesmcbain/friendlyeval?branch=master&svg=true)](https://ci.appveyor.com/project/milesmcbain/friendlyeval) 5 | [![lifecycle](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) 6 | 7 | ![](https://cdn.rawgit.com/MilesMcBain/friendlyeval/39e7c55e/media/friendlyeval.gif) 8 | 9 | --- 10 | 11 | A friendly interface to the **tidy eval** framework and the 12 | [`rlang`](http://rlang.r-lib.org/) package for casual 13 | [`dplyr`](https://dplyr.tidyverse.org/) users. 14 | 15 | This package provides an alternative, auto-complete friendly interface to 16 | `rlang` that is more closely aligned with the task domain of a user 'programming 17 | with dplyr'. It implements most of the cases in the ['programming with 18 | dplyr'](https://dplyr.tidyverse.org/articles/programming.html) vignette. 19 | 20 | The interface can also convert itself to standard `rlang` with the help of an 21 | [RStudio addin](https://rstudio.github.io/rstudioaddins/) that replaces 22 | `friendlyeval` functions with their `rlang` equivalents. This allows you to 23 | prototype in friendly, then subsequently automagically transform to `rlang`. 24 | Your friends won't know the difference. 25 | # Installation 26 | 27 | ```r 28 | devtools::install_github("milesmcbain/friendlyeval") 29 | ``` 30 | 31 | # Overview 32 | 33 | Arguments passed to `dplyr` can be *treated* as: 34 | 35 | * a single literal column name (e.g. `mpg` in `select(mtcars, mpg)`) 36 | * a single expression (e.g. `cyl <= 6` in `filter(mtcars, cyl <= 6)`) 37 | * a list of literal column names (e.g. `mpg, cyl` in `select(mtcars, mpg, cyl)`) 38 | * a list of expressions (e.g. `hp >= mean(hp), wt > 3` in `filter(mtcars, hp >= mean(hp), wt > 3)`) 39 | 40 | `dplyr` uses special argument handling to interpret and treat those arguments as 41 | one or more column names or expressions. User functions don't perform that same 42 | argument handling, so we need some way to tell `dplyr` how to *treat* these 43 | arguments we pass from our enclosing functions. `rlang` provides the functions 44 | we need to do just that, but knowing which `rlang` function maps to each use 45 | case requires a fairly nuanced understanding of metaprogramming concepts. 46 | 47 | `friendlyeval` helps bridge that gap by providing a descriptive (and 48 | auto-complete friendly) set of eight complimentary functions that instruct 49 | `dplyr` to resolve arguments we pass using either: 50 | 51 | * the literal input provided as the arguments to our function (e.g. the text `lat` and `lon` in `my_select(dat, lat, lon)`) 52 | * the string values of those arguments (e.g. `"lat"` and `"lon"` in `my_select(dat, arg1 = "lat", "lon")`): 53 | 54 | function | usage 55 | --- | --- 56 | `treat_input_as_col` | Treat the literal text input provided as a `dplyr` column name. 57 | `treat_input_as_expr` | Treat the literal text input provided as an expression involving a `dplyr` column name (e.g. in `filter(dat, col1 == 0)`, `col1 == 0` is an expression involving the value of col1). 58 | `treat_inputs_as_cols` | Treat a comma separated list of arguments as a list of `dplyr` column names. 59 | `treat_inputs_as_exprs` | Treat a comma separated list of arguments as a list of expressions. 60 | `treat_string_as_col` | Treat the character value of your function argument as a `dplyr` column name. 61 | `treat_string_as_expr` | Treat the character value of your function argument as an expression involving a `dplyr` column name. 62 | `treat_strings_as_cols` | Treat a list of character values as a list of `dplyr` column names. 63 | `treat_strings_as_exprs` | Treat a vector of strings as a list of expressions involving `dplyr` column names. 64 | 65 | These eight functions are used in conjunction with three tidy eval operators: 66 | 67 | * `!!` 68 | * `!!!` 69 | * `:=` 70 | 71 | `!!` and `!!!` are signposts that tell `dplyr`: 72 | 73 | > "Stop! This needs to be evaluated to resolve column names or expressions". 74 | 75 | `!!` tells `dplyr` to expect a single column name or expression, whereas `!!!` says to expect a list of column names or expressions. 76 | 77 | `:=` is used in place of `=` in the special case where we need `dplyr` to 78 | resolve a column name on the left hand side of an `=` like in 79 | `mutate(!!treat_input_as_col(colname) = rownumber)`. Evaluating on the left hand 80 | side in this example is not legal R syntax, so instead we must write: 81 | `mutate(!!treat_input_as_col(colname) := rownumber)`. 82 | 83 | # Writing functions that call `dplyr` 84 | 85 | `dplyr` functions try to be user-friendly by saving you typing. This allows you 86 | to write code like `mutate(data, col1 = abs(col2), col3 = col4*100)` instead of 87 | the more cumbersome base R style: `data$col <- abs(data$col2); data$col3 <- 88 | data$col4*100`. 89 | 90 | The cost of this convenience is more work when we want to write functions that call `dplyr`, because `dplyr` needs to be instructed how to *treat* the arguments we pass to it. For example, this function does not work as we might expect: 91 | 92 | ```r 93 | double_col <- function(dat, arg){ 94 | mutate(dat, result = arg*2) 95 | } 96 | 97 | double_col(mtcars, cyl) 98 | 99 | # Error in mutate_impl(.data, dots) : 100 | # Evaluation error: object 'cyl' not found. 101 | ``` 102 | This is because our `double_col` function doesn't perform the same special argument handling as `dplyr` functions. What if we pass our column name as a string value instead? 103 | ```r 104 | double_col(mtcars, arg = 'cyl') 105 | 106 | # Error in mutate_impl(.data, dots) : 107 | # Evaluation error: non-numeric argument to binary operator. 108 | ``` 109 | 110 | That doesn't work either, even though those were our only options under normal evaluation rules! Fortunately, there are two ways to make `double_col` work. We can either: 111 | 112 | 1. Instruct `dplyr` to treat the literal **input** provided for the `arg` argument as a **column name**. So `double_col(mtcars, cyl)` would work. 113 | 2. Instruct `dplyr` to treat the **string** value bound to `arg` - "cyl" - as a **column name**, rather than as a normal character vector. So `double_col(mtcars, arg = "cyl")` would work. 114 | 115 | ## Usage examples 116 | 117 | ### Making `double_col` work 118 | Using what was input, `dplyr` style: 119 | 120 | ```r 121 | double_col <- function(dat, arg){ 122 | mutate(dat, result = !!treat_input_as_col(arg) * 2) 123 | } 124 | 125 | ## working call form: 126 | double_col(mtcars, cyl) 127 | ``` 128 | 129 | Using the supplied value: 130 | ```r 131 | double_col <- function(dat, arg){ 132 | mutate(dat, result = !!treat_string_as_col(arg) * 2) 133 | } 134 | 135 | ## working call form: 136 | double_col(mtcars, arg = 'cyl') 137 | ``` 138 | 139 | ### Supplying column names to assign results to (lhs variant) 140 | A more useful version of `double_col` would be to allow the name of the resulting column to be set via the function. Again, this can be done using the literal input, `dplyr` style: 141 | 142 | ```r 143 | double_col <- function(dat, arg, result){ 144 | ## note usage of ':=' for lhs eval. 145 | mutate(dat, !!treat_input_as_col(result) := !!treat_input_as_col(arg) * 2) 146 | } 147 | 148 | ## working call form: 149 | double_col(mtcars, cyl, cylx2) 150 | ``` 151 | 152 | Or using supplied values: 153 | ```r 154 | double_col <- function(dat, arg, result){ 155 | ## note usage of ':=' for lhs eval. 156 | mutate(dat, !!treat_string_as_col(result) := !!treat_string_as_col(arg) * 2) 157 | } 158 | 159 | ## working call form: 160 | double_col(mtcars, arg = 'cyl', result = 'cylx2') 161 | ``` 162 | 163 | ### Working with argument lists containing column names 164 | When wrapping `group_by`, you will likely want to pass a list of column names. Here's how to do that using what was input, `dplyr` style: 165 | 166 | ```r 167 | reverse_group_by <- function(dat, ...){ 168 | ## this expression is split out for readability, but it can be nested into below. 169 | groups <- treat_inputs_as_cols(...) 170 | 171 | group_by(dat, !!!rev(groups)) 172 | } 173 | 174 | ## working call form 175 | reverse_group_by(mtcars, gear, am) 176 | ``` 177 | 178 | Here's how to do it using a list of values: 179 | ```r 180 | reverse_group_by <- function(dat, columns){ 181 | groups <- treat_strings_as_cols(columns) 182 | 183 | group_by(dat, !!!rev(groups)) 184 | } 185 | 186 | ## working call form: 187 | reverse_group_by(mtcars, c('gear', 'am')) 188 | ``` 189 | 190 | And here's how to do it using the values of `...`: 191 | ```r 192 | reverse_group_by <- function(dat, ...){ 193 | ## note the list() around ... to collect the values of the arguments into a list. 194 | groups <- treat_strings_as_cols(list(...)) 195 | 196 | group_by(dat, !!!rev(groups)) 197 | } 198 | 199 | ## working call form: 200 | reverse_group_by(mtcars, 'gear', 'am') 201 | ``` 202 | 203 | ### Passing expressions involving columns 204 | Using the `_expr` functions, you can pass expressions involving column names to 205 | `dplyr` functions like `filter`, `mutate` and `summarise`. An example of a 206 | function involving an expression is a more general version of the `double_col` 207 | function from above, called `double_anything`, that can take expressions 208 | involving columns, and double those: 209 | 210 | ```r 211 | double_anything <- function(dat, arg){ 212 | mutate(dat, result = (!!treat_input_as_expr(arg)) * 2) 213 | } 214 | 215 | ## working call form: 216 | double_anything(mtcars, cyl * am) 217 | 218 | ## mpg cyl disp hp drat wt qsec vs am gear carb result 219 | ## 1 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4 12 220 | ## 2 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4 12 221 | ## 3 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1 8 222 | ## 4 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1 0 223 | ## 5 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2 0 224 | ``` 225 | 226 | A common usage pattern is to take a list of expressions. Consider the `filter_loudly` function that reports the number of rows filtered: 227 | 228 | ```r 229 | filter_loudly(mtcars, cyl >= 6, am == 1) 230 | 231 | ## Filtered out 27 rows. 232 | ## mpg cyl disp hp drat wt qsec vs am gear carb 233 | ## 1 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 234 | ## 2 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 235 | ## 3 15.8 8 351 264 4.22 3.170 14.50 0 1 5 4 236 | ## 4 19.7 6 145 175 3.62 2.770 15.50 0 1 5 6 237 | ## 5 15.0 8 301 335 3.54 3.570 14.60 0 1 5 8 238 | ``` 239 | 240 | You can implement this function using filtering expressions exactly as input, `dplyr` style: 241 | ```r 242 | filter_loudly <- function(x, ...){ 243 | in_rows <- nrow(x) 244 | out <- filter(x, !!!treat_inputs_as_exprs(...)) 245 | out_rows <- nrow(out) 246 | message("Filtered out ",in_rows-out_rows," rows.") 247 | return(out) 248 | } 249 | 250 | ## working call form: 251 | ## filter_loudly(mtcars, cyl >= 6, am == 1) 252 | ``` 253 | 254 | Or using a list/vector of values, parsed as expressions: 255 | ```r 256 | filter_loudly <- function(x, filter_expressions){ 257 | ## if accepting list arguments, should check all are character 258 | stopifnot(purrr::every(filter_expressions, is.character)) 259 | 260 | in_rows <- nrow(x) 261 | out <- filter(x, !!!treat_strings_as_exprs(filter_expressions)) 262 | out_rows <- nrow(out) 263 | message("Filtered out ",in_rows-out_rows," rows.") 264 | return(out) 265 | } 266 | 267 | ## working call form: 268 | ## filter_loudly(mtcars, list('cyl >= 6', 1)) 269 | ``` 270 | 271 | Or capturing the values of `...`, parsed as expressions: 272 | ```r 273 | filter_loudly <- function(x, ...){ 274 | dots <- list(...) 275 | ## if accepting list arguments, should check all are character 276 | stopifnot(purrr::every(dots, is.character)) 277 | 278 | in_rows <- nrow(x) 279 | out <- filter(x, !!!treat_strings_as_exprs(dots)) 280 | out_rows <- nrow(out) 281 | message("Filtered out ",in_rows-out_rows," rows.") 282 | return(out) 283 | } 284 | 285 | ## working call form: 286 | ## filter_loudly(mtcars, 'cyl >= 6', 'am == 1') 287 | ``` 288 | 289 | ## A note on expressions vs columns 290 | It may have occurred to you that there are cases where a column name is a valid expression and vice versa. This is true, and it means that in some situations, you could switch the `_col` and the `_expr` versions of functions (e.g. use `treat_input_as_expr` in place of `treat_input_as_col`) and things would continue to work. However, using the `col` version where appropriate invokes checks that assert that what was passed to it can be interpreted as a simple column name. This is useful in situations where expressions are not permitted, like in `select` or on the left hand side of the internal assignment in `mutate`: e.g. `mutate(lhs_col = some_expr)`. 291 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | # Download script file from GitHub 4 | init: 5 | ps: | 6 | $ErrorActionPreference = "Stop" 7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 8 | Import-Module '..\appveyor-tool.ps1' 9 | 10 | install: 11 | ps: Bootstrap 12 | 13 | cache: 14 | - C:\RLibrary 15 | 16 | # Adapt as necessary starting from here 17 | 18 | build_script: 19 | - travis-tool.sh install_deps 20 | 21 | test_script: 22 | - travis-tool.sh run_tests 23 | 24 | on_failure: 25 | - 7z a failure.zip *.Rcheck\* 26 | - appveyor PushArtifact failure.zip 27 | 28 | artifacts: 29 | - path: '*.Rcheck\**\*.log' 30 | name: Logs 31 | 32 | - path: '*.Rcheck\**\*.out' 33 | name: Logs 34 | 35 | - path: '*.Rcheck\**\*.fail' 36 | name: Logs 37 | 38 | - path: '*.Rcheck\**\*.Rout' 39 | name: Logs 40 | 41 | - path: '\*_*.tar.gz' 42 | name: Bits 43 | 44 | - path: '\*_*.zip' 45 | name: Bits 46 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Tranform friendlyeval to rlang 2 | Description: Converts friendlyeval functions in the active selection or active source editor to rlang 3 | Binding: friendlyeval_to_rlang 4 | Interactive: yes 5 | 6 | -------------------------------------------------------------------------------- /man/friendlyeval_to_rlang.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transform.R 3 | \name{friendlyeval_to_rlang} 4 | \alias{friendlyeval_to_rlang} 5 | \title{Convert friendlyeval functions to rlang} 6 | \usage{ 7 | friendlyeval_to_rlang() 8 | } 9 | \value{ 10 | nothing. 11 | } 12 | \description{ 13 | Works on a RStudio document selection if one exists, or the entire 14 | active source editor if no selection exists. 15 | } 16 | -------------------------------------------------------------------------------- /man/treat_input_as_col.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_input_as_col} 4 | \alias{treat_input_as_col} 5 | \title{treat_input_as_col} 6 | \usage{ 7 | treat_input_as_col(arg) 8 | } 9 | \arguments{ 10 | \item{arg}{the argument for which the literal input text is to be used 11 | as a column name.} 12 | } 13 | \value{ 14 | Something that will resolve to a column named when prefixed with !!. 15 | } 16 | \description{ 17 | Take what was input and treat it as a column name argument to a dplyr function. 18 | } 19 | \details{ 20 | This is used inside a function to pass the literal text of what the caller 21 | typed as an argument to a `dplyr` function. When using `dplyr` the text will 22 | typically be destined for a column name argument. See examples for usage 23 | scenarios. 24 | } 25 | \examples{ 26 | \dontrun{ 27 | select_this <- function(a_col){ 28 | select(mtcars, !!treat_input as_col(a_col)) 29 | } 30 | select_this(cyl) 31 | 32 | mean_this <- function(a_col){ 33 | mutate(mtcars, result = mean(!!treat_input_as_col(a_col))) 34 | } 35 | mean_this(cyl) 36 | 37 | filter_same <- function(dat, x, y) { 38 | filter(dat, !!treat_input_as_col(x) == !!treat_input_as_col(y)) 39 | } 40 | filter_same(mtcars, carb, gear) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /man/treat_input_as_expr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_input_as_expr} 4 | \alias{treat_input_as_expr} 5 | \title{treat_input_as_expr} 6 | \usage{ 7 | treat_input_as_expr(arg) 8 | } 9 | \arguments{ 10 | \item{arg}{the argument for which the literal input text is to be used as an 11 | expression.} 12 | } 13 | \value{ 14 | something that will resolve to an expression when prefixed by `!!` 15 | } 16 | \description{ 17 | Take what was input and treat it as an expression argument to dplyr function. 18 | } 19 | \details{ 20 | This is used inside a function to pass the literal text of what the caller 21 | typed as an expression to a `dplyr` function. These might be 22 | logical expressions passed to filter: `filter(dat, col == "example")`, or 23 | functions of columns passed to mutate or summarise: `summarise(dat, mean(col))`. 24 | } 25 | -------------------------------------------------------------------------------- /man/treat_inputs_as_cols.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_inputs_as_cols} 4 | \alias{treat_inputs_as_cols} 5 | \title{treat_inputs_as_cols} 6 | \usage{ 7 | treat_inputs_as_cols(...) 8 | } 9 | \arguments{ 10 | \item{...}{a comma separated list of arguments to be treated as column names.} 11 | } 12 | \value{ 13 | something that will resolve to a list of column names when prefixed 14 | with `!!!` 15 | } 16 | \description{ 17 | Take the literal text input for a comma separated list of arguments and treat it as list of column names in a dplyr function. 18 | } 19 | \details{ 20 | The most common usage of this is to pass `...`, from your function directly through to dplyr functions as column names, as in the `select_these` example. 21 | 22 | This function must be prefixed with `!!!` to treat the output as a list. 23 | } 24 | \examples{ 25 | \dontrun{ 26 | select_these <- function(dat, ...){ 27 | select(dat, !!!treat_inputs_as_cols(...)) 28 | } 29 | select_these(mtcars, cyl, wt) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /man/treat_inputs_as_exprs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_inputs_as_exprs} 4 | \alias{treat_inputs_as_exprs} 5 | \title{treat_inputs_as_exprs} 6 | \usage{ 7 | treat_inputs_as_exprs(...) 8 | } 9 | \arguments{ 10 | \item{...}{a comma separated list of arguments to be treated as expressions.} 11 | } 12 | \value{ 13 | something that will resolve to a list of expressions when prefixed with `!!!` 14 | } 15 | \description{ 16 | Take the literal text input for a comma separated list of arguments and treat it as a list of expressions in a dplyr function. 17 | } 18 | \details{ 19 | Common usage of this is to pass `...` from your function directly through to 20 | dplyr functions as expressions. This could be a list of filtering 21 | expressions for filter: `filter(dat, col1 == "example1", col2 == 22 | "example2")`, or a list of functions of columns to `mutate` or `summarise`: 23 | `summarise(dat, mean(col1), var(col1))` 24 | 25 | This function must be prefixed with `!!!` to treat the output as a list. 26 | } 27 | -------------------------------------------------------------------------------- /man/treat_string_as_col.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_string_as_col} 4 | \alias{treat_string_as_col} 5 | \title{treat_string_as_col} 6 | \usage{ 7 | treat_string_as_col(arg) 8 | } 9 | \arguments{ 10 | \item{arg}{the argument that holds a value to be used as a column name.} 11 | } 12 | \value{ 13 | something that will resolve to a column name when prefixed with `!!`. 14 | } 15 | \description{ 16 | Take the a string value and use it as a column name in a 17 | dplyr function. 18 | } 19 | \details{ 20 | This is used to take the string value of a variable and use it 21 | in place of a literal column name when calling a dplyr function. This 22 | ability is useful when the name of the column to operate on is determined at 23 | run-time from data. 24 | } 25 | \examples{ 26 | \dontrun{ 27 | ## drop this run-time determined column. 28 | b <- "cyl" 29 | select(mtcars, -!!treat_string_as_col(b)) 30 | 31 | ## function double a column 32 | double_col <- function(dat, arg) { 33 | dplyr::mutate(dat, result = !!rlang::sym(arg) * 2) 34 | } 35 | double_col(mtcars, arg = 'cyl') 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /man/treat_string_as_expr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_string_as_expr} 4 | \alias{treat_string_as_expr} 5 | \title{treat_string_as_expr(x)} 6 | \usage{ 7 | treat_string_as_expr(x) 8 | } 9 | \arguments{ 10 | \item{x}{a string to be treated as an expression.} 11 | } 12 | \value{ 13 | something that will resolve to an expression when prefixed with `!!` 14 | } 15 | \description{ 16 | Treat the string value of a variable as an expression in a dplyr function. 17 | } 18 | \details{ 19 | This will parse a string and treat it as an expression to be evaluated 20 | in the context of a dplyr function call. This may be convenient when 21 | building expressions to evaluate at run-time. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | 26 | ## processing operations from other notation 27 | calc_result <- function(dat, operation){ 28 | operation <- gsub('x', '*', operation) 29 | mutate(dat, result = !!treat_string_as_expr(operation)) 30 | } 31 | 32 | calc_result(mtcars, "mpg x hp") 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /man/treat_strings_as_cols.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_strings_as_cols} 4 | \alias{treat_strings_as_cols} 5 | \title{treat_strings_as_cols} 6 | \usage{ 7 | treat_strings_as_cols(arg) 8 | } 9 | \arguments{ 10 | \item{arg}{the argument that holds a list of values to be used as column names.} 11 | } 12 | \value{ 13 | something that will resolve to a comma separated list of column 14 | names when prefixed with `!!!`. 15 | } 16 | \description{ 17 | Take a list/vector of strings and use the values as column 18 | names. 19 | } 20 | \details{ 21 | This is used to take the character values of a list or vector and use them 22 | as a comma separated list of column names in a dplyr function. The most 23 | common usage would be applied yo the values of a single argument, as in the 24 | `select_these2` example, however it can also be used with ... to transform 25 | all ... values to column names - see `select_these3` example. 26 | } 27 | \examples{ 28 | \dontrun{ 29 | select_these2 <- function(dat, cols){ 30 | select(dat, !!!treat_strings_as_cols(cols)) 31 | } 32 | select_these2(mtcars, cols = c("cyl", "wt")) 33 | 34 | select_these3 <- function(dat, ...){ 35 | dots <- list(...) 36 | select(dat, !!!treat_strings_as_cols(dots)) 37 | } 38 | select_these3(mtcars, "cyl", "wt") 39 | 40 | select_not_these <- function(dat, cols){ 41 | select(dat, -c(!!!treat_strings_as_cols(cols))) 42 | } 43 | select_not_these(mtcars, cols = c("cyl", "wt")) 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /man/treat_strings_as_exprs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/friendlyeval.R 3 | \name{treat_strings_as_exprs} 4 | \alias{treat_strings_as_exprs} 5 | \title{treat_strings_as_exprs(arg)} 6 | \usage{ 7 | treat_strings_as_exprs(arg) 8 | } 9 | \arguments{ 10 | \item{arg}{a vector of strings to be treated as expressions.} 11 | } 12 | \value{ 13 | something that will resolve to a list of expressions when prefixed with `!!!` 14 | } 15 | \description{ 16 | Treat the string values of a character vector as expressions in a dplyr function. 17 | } 18 | \details{ 19 | This will parse a vector of strings and treat them as a list of 20 | expressions to be evaluated in the context of a dplyr function. This may be 21 | convenient when building expressions to evaluate at run time. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | summarise_uppr <- function(dat, ...){ 26 | ## need to capture a character vector 27 | dots <- as.character(list(...)) 28 | functions <- tolower(unlist(dots)) 29 | summarise(dat, !!!treat_strings_as_exprs(functions)) 30 | } 31 | 32 | summarise_uppr(mtcars, 'MEAN(mpg)', 'VAR(mpg)') 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /media/friendlyeval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/friendlyeval/7ea2ed91fac54ad5813a372390d7da9d5ed095a8/media/friendlyeval.gif -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(friendlyeval) 3 | 4 | test_check("friendlyeval") 5 | -------------------------------------------------------------------------------- /tests/testthat/test_friendlyeval.R: -------------------------------------------------------------------------------- 1 | context("check friendlyeval equivalent rlang") 2 | 3 | test_that("friendlyeval is equivalent to rlang functions", { 4 | 5 | # This wrapper is required to test tidyeval in this way. 6 | # See https://github.com/r-lib/testthat/issues/655 7 | expect_equal_ <- function(object, expected, ...) { 8 | force(object) 9 | force(expected) 10 | expect_equal(object, expected, ...) 11 | } 12 | 13 | expect_equal_({ 14 | 15 | double_col <- function(dat, arg){ 16 | dplyr::mutate(dat, result = !!treat_input_as_col(arg)*2) 17 | } 18 | 19 | ## working call form: 20 | double_col(mtcars, cyl) 21 | }, 22 | { 23 | double_col <- function(dat, arg){ 24 | dplyr::mutate(dat, result = !!rlang::ensym(arg)*2) 25 | } 26 | 27 | ## working call form: 28 | double_col(mtcars, cyl) 29 | }) 30 | 31 | 32 | expect_equal_({ 33 | double_col <- function(dat, arg) { 34 | dplyr::mutate(dat, result = !!treat_string_as_col(arg) * 2) 35 | } 36 | 37 | ## working call form: 38 | double_col(mtcars, arg = 'cyl') 39 | 40 | }, 41 | { 42 | double_col <- function(dat, arg) { 43 | dplyr::mutate(dat, result = !!rlang::sym(arg) * 2) 44 | } 45 | 46 | ## working call form: 47 | double_col(mtcars, arg = 'cyl') 48 | }) 49 | 50 | expect_equal_({ 51 | double_col <- function(dat, arg, result) { 52 | ## note usage of ':=' for lhs eval. 53 | dplyr::mutate(dat,!!treat_input_as_col(result) := !!treat_input_as_col(arg) * 2) 54 | } 55 | 56 | ## working call form: 57 | double_col(mtcars, cyl, cylx2) 58 | 59 | 60 | }, 61 | { 62 | double_col <- function(dat, arg, result) { 63 | ## note usage of ':=' for lhs eval. 64 | dplyr::mutate(dat,!!rlang::ensym(result) := !!rlang::ensym(arg) * 2) 65 | } 66 | 67 | ## working call form: 68 | double_col(mtcars, cyl, cylx2) 69 | }) 70 | 71 | expect_equal_({ 72 | double_col <- function(dat, arg, result) { 73 | ## note usage of ':=' for lhs eval. 74 | dplyr::mutate(dat, !!treat_string_as_col(result) := !!treat_string_as_col(arg) * 2) 75 | } 76 | 77 | ## working call form: 78 | double_col(mtcars, arg = 'cyl', result = 'cylx2') 79 | 80 | }, 81 | { 82 | double_col <- function(dat, arg, result) { 83 | ## note usage of ':=' for lhs eval. 84 | dplyr::mutate(dat, !!rlang::sym(result) := !!rlang::sym(arg) * 2) 85 | } 86 | 87 | ## working call form: 88 | double_col(mtcars, arg = 'cyl', result = 'cylx2') 89 | }) 90 | 91 | expect_equal_({ 92 | reverse_group_by <- function(dat, ...) { 93 | ## this expression is split out for readability, but it can be nested into below. 94 | groups <- treat_inputs_as_cols(...) 95 | 96 | dplyr::group_by(dat, !!!rev(groups)) 97 | } 98 | 99 | ## working call form 100 | reverse_group_by(mtcars, gear, am) 101 | }, 102 | { 103 | reverse_group_by <- function(dat, ...) { 104 | ## this expression is split out for readability, but it can be nested into below. 105 | groups <- rlang::ensyms(...) 106 | 107 | dplyr::group_by(dat, !!!rev(groups)) 108 | } 109 | 110 | ## working call form 111 | reverse_group_by(mtcars, gear, am) 112 | }) 113 | 114 | expect_equal_({ 115 | reverse_group_by <- function(dat, columns) { 116 | groups <- treat_strings_as_cols(columns) 117 | 118 | dplyr::group_by(dat, !!!rev(groups)) 119 | } 120 | 121 | ## working call form: 122 | reverse_group_by(mtcars, c('gear', 'am')) 123 | 124 | }, 125 | { 126 | reverse_group_by <- function(dat, columns) { 127 | groups <- rlang::syms(columns) 128 | 129 | dplyr::group_by(dat, !!!rev(groups)) 130 | } 131 | ## working call form: 132 | reverse_group_by(mtcars, c('gear', 'am')) 133 | }) 134 | 135 | expect_equal_({ 136 | reverse_group_by <- function(dat, ...) { 137 | ## note the list() around ... to collect the arguments into a list. 138 | groups <- treat_strings_as_cols(list(...)) 139 | 140 | dplyr::group_by(dat, !!!rev(groups)) 141 | } 142 | 143 | ## working call form: 144 | reverse_group_by(mtcars, 'gear', 'am') 145 | 146 | }, 147 | { 148 | reverse_group_by <- function(dat, ...) { 149 | ## note the list() around ... to collect the arguments into a list. 150 | groups <- rlang::syms(list(...)) 151 | 152 | dplyr::group_by(dat, !!!rev(groups)) 153 | } 154 | ## working call form: 155 | reverse_group_by(mtcars, 'gear', 'am') 156 | }) 157 | 158 | expect_equal_({ 159 | select_these <- function(dat, ...) { 160 | dplyr::select(dat, !!!treat_inputs_as_cols(...)) 161 | } 162 | select_these(mtcars, cyl, wt) 163 | 164 | }, 165 | { 166 | select_these <- function(dat, ...) { 167 | dplyr::select(dat, !!!rlang::ensyms(...)) 168 | } 169 | select_these(mtcars, cyl, wt) 170 | }) 171 | 172 | expect_equal_({ 173 | select_these3 <- function(dat, cols) { 174 | dplyr::select(dat, -c(!!!treat_strings_as_cols(cols))) 175 | } 176 | select_these3(mtcars, c("cyl", "wt")) 177 | }, 178 | { 179 | select_these3 <- function(dat, cols) { 180 | dplyr::select(dat, -c(!!!rlang::syms(cols))) 181 | } 182 | select_these3(mtcars, c("cyl", "wt")) }) 183 | 184 | expect_equal_({ 185 | 186 | filter_same <- function(dat, x, y) { 187 | dplyr::filter(dat, !!treat_input_as_col(x) == !!treat_input_as_col(y)) 188 | } 189 | 190 | filter_same(mtcars, carb, gear) 191 | }, 192 | { 193 | filter_same <- function(dat, x, y) { 194 | dplyr::filter(dat, !!rlang::ensym(x) == !!rlang::ensym(y)) 195 | } 196 | 197 | filter_same(mtcars, carb, gear) 198 | }) 199 | 200 | expect_equal_({ 201 | 202 | ## processing operations from other notation 203 | calc_result <- function(dat, operation){ 204 | operation <- gsub('x', '*', operation) 205 | dplyr::mutate(dat, result = !!treat_string_as_expr(operation)) 206 | } 207 | 208 | calc_result(mtcars, "mpg x hp") 209 | 210 | }, 211 | { 212 | ## processing operations from other notation 213 | calc_result <- function(dat, operation){ 214 | operation <- gsub('x', '*', operation) 215 | dplyr::mutate(dat, result = !!rlang::parse_expr(operation)) 216 | } 217 | 218 | calc_result(mtcars, "mpg x hp") 219 | 220 | }) 221 | 222 | expect_equal_({ 223 | 224 | summarise_uppr <- function(dat, ...){ 225 | ## need to capture a character vector 226 | dots <- as.character(list(...)) 227 | functions <- tolower(unlist(dots)) 228 | dplyr::summarise(dat, !!!treat_strings_as_exprs(functions)) 229 | } 230 | 231 | summarise_uppr(mtcars, 'MEAN(mpg)', 'VAR(mpg)') 232 | }, 233 | { 234 | summarise_uppr <- function(dat, ...){ 235 | ## need to capture a character vector 236 | dots <- as.character(list(...)) 237 | functions <- tolower(unlist(dots)) 238 | dplyr::summarise(dat, !!!(function(x){rlang::parse_exprs(textConnection(x))})(functions)) 239 | } 240 | 241 | summarise_uppr(mtcars, 'MEAN(mpg)', 'VAR(mpg)') 242 | }) 243 | 244 | }) 245 | --------------------------------------------------------------------------------