What Is the "Embracing Operator" '{{ }}'

What is the embracing operator `{{ }}`?

It's called curly-curly operator (see ?"{{}}").

It's useful when passing an argument that has to be substituted in place before being evaluated in another context.

See this simple example (although a bit awkward as we could simple quote the "cyl" when calling the function here):

library(dplyr)

# does not work
get_var <- function(data, column) {
data %>% select(column)
}

get_var(mtcars, cyl)
#> Error: object 'cyl' not found

# works
get_var <- function(data, column) {
data %>% select({{ column }})
}

get_var(mtcars, cyl)
#> cyl
#> Mazda RX4 6
#> Mazda RX4 Wag 6
#> Datsun 710 4
#> Hornet 4 Drive 6
#> Hornet Sportabout 8
#> ...

Created on 2020-07-08 by the reprex package (v0.3.0)

Or maybe a better example:

library(dplyr)

# does not work
get_var <- function(data, column, value) {
data %>% filter(column == value)
}

get_var(mtcars, cyl, 6)
#> Error: Problem with `filter()` input `..1`.
#> x object 'cyl' not found
#> i Input `..1` is `column == value`.

# works
get_var <- function(data, column, value) {
data %>% filter({{ column }} == value)
}

get_var(mtcars, cyl, 6)
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
#> Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
#> Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
#> Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
#> Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
#> Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4
#> Ferrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6

Created on 2020-07-08 by the reprex package (v0.3.0)

What if I want to use embracing operator with `starts_with()`?

Try the following code:

library(dplyr)

test2 <- function(var) {
x <- deparse(substitute(var))
mtcars |> select(starts_with(x))
}
test2(m) |> head()

Output:

                   mpg
Mazda RX4 21.0
Mazda RX4 Wag 21.0
Datsun 710 22.8
Hornet 4 Drive 21.4
Hornet Sportabout 18.7
Valiant 18.1

embracing operator inside mutate function

Try this function -

library(dplyr)

change_indicators <- function(data, var){
val <- deparse(substitute(var))
col1 <- paste0('cont_chg_', val)
col2 <- paste0('cat_chg_', val)
col3 <- paste0('bi_chg_', val)

data %>%
# select only percent rent burden vars
select(tractid, year, contains("prburden")) %>%
# group by tractid and count # of tracts by group
group_by(tractid) %>%
# create rent burden change indicator - continuous
mutate(!! col1 := {{var}}[year == 2019] - {{var}}[year == 2000],
!! col2 := case_when(.data[[col1]] < 0 ~ "negative",
.data[[col1]] == 0 ~ "zero",
.data[[col1]] > 0 ~ "positive" ,
TRUE ~ NA_character_),
!!col3 := case_when(.data[[col2]] == "negative" ~ "loss",
.data[[col2]] == "positive" ~ "gain",
TRUE ~ NA_character_)) %>%
glimpse()

}
  • Use {{var}} when using the column names directly.
  • I am not sure if "cont_chg_{{ var }}" works, I prefer to use .data pronoun there.
  • To assign column names use !!name := to create new columns.
  • Replaced "NA" with NA_character_
  • Combined everything into one mutate call.
data %>%
ungroup %>%
change_indicators(prburden)

#Rows: 219,246
#Columns: 10
#Groups: tractid [73,082]
#$ tractid <chr> "01001020100", "01001020100", "01001020100"…
#$ year <chr> "2000", "2013", "2019", "2000", "2013", "20…
#$ prburden_no <dbl> 60.73620, 67.88991, 44.64286, 46.07843, 42.…
#$ prburden <dbl> 14.110429, 13.761468, 35.119048, 16.666667,…
#$ prburden_sev <dbl> 17.177914, 18.348624, 18.452381, 26.470588,…
#$ prburden_not <dbl> 7.975460, 0.000000, 1.785714, 10.784314, 10…
#$ prburden_all <dbl> 31.28834, 32.11009, 53.57143, 43.13725, 46.…
#$ cont_chg_prburden <dbl> 21.008618, 21.008618, 21.008618, 7.457847, …
#$ cat_chg_prburden <chr> "positive", "positive", "positive", "positi…
#$ bi_chg_prburden <chr> "gain", "gain", "gain", "gain", "gain", "ga…

What does !! operator mean in R

The !! and {{ operators are placeholders to flag a variable as having been quoted. They are usually only needed if you intend to program with the tidyverse.
The tidyverse likes to leverage NSE (non-standard Evaluation) in order to reduce the amount of repetition. The most frequent application is towards the "data.frame" class, in which expressions/symbols are evaluated in the context of a data.frame before searching other scopes.
In order for this to work, some special functions (like in the package dplyr) have arguments that are quoted. To quote an expression, is to save the symbols that make up the expression and prevent the evaluation (in the context of tidyverse they use "quosures", which is like a quoted expression except it contains a reference to the environment the expression was made).
While NSE is great for interactive use, it is notably harder to program with.
Lets consider the dplyr::select

 library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union

iris <- as_tibble(iris)

my_select <- function(.data, col) {
select(.data, col)
}

select(iris, Species)
#> # A tibble: 150 × 1
#> Species
#> <fct>
#> 1 setosa
#> 2 setosa
#> 3 setosa
#> 4 setosa
#> 5 setosa
#> 6 setosa
#> 7 setosa
#> 8 setosa
#> 9 setosa
#> 10 setosa
#> # … with 140 more rows
my_select(iris, Species)
#> Error: object 'Species' not found

we encounter an error because within the scope of my_select
the col argument is evaluated with standard evaluation and
cannot find a variable named Species.

If we attempt to create a variable in the global environemnt, we see that the funciton
works - but it isn't behaving to the heuristics of the tidyverse. In fact,
they produce a note to inform you that this is ambiguous use.

 Species <- "Sepal.Width"
my_select(iris, Species)
#> Note: Using an external vector in selections is ambiguous.
#> ℹ Use `all_of(col)` instead of `col` to silence this message.
#> ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
#> This message is displayed once per session.
#> # A tibble: 150 × 1
#> Sepal.Width
#> <dbl>
#> 1 3.5
#> 2 3
#> 3 3.2
#> 4 3.1
#> 5 3.6
#> 6 3.9
#> 7 3.4
#> 8 3.4
#> 9 2.9
#> 10 3.1
#> # … with 140 more rows

To remedy this, we need
to prevent evaluation with enquo() and unquote with !! or just use {{.

 my_select2 <- function(.data, col) {
col_quo <- enquo(col)
select(.data, !!col_quo) #attempting to find whatever symbols were passed to `col` arugment
}
#' `{{` enables the user to skip using the `enquo()` step.
my_select3 <- function(.data, col) {
select(.data, {{col}})
}

my_select2(iris, Species)
#> # A tibble: 150 × 1
#> Species
#> <fct>
#> 1 setosa
#> 2 setosa
#> 3 setosa
#> 4 setosa
#> 5 setosa
#> 6 setosa
#> 7 setosa
#> 8 setosa
#> 9 setosa
#> 10 setosa
#> # … with 140 more rows
my_select3(iris, Species)
#> # A tibble: 150 × 1
#> Species
#> <fct>
#> 1 setosa
#> 2 setosa
#> 3 setosa
#> 4 setosa
#> 5 setosa
#> 6 setosa
#> 7 setosa
#> 8 setosa
#> 9 setosa
#> 10 setosa
#> # … with 140 more rows

In summary, you really only need !! and {{ if you are trying to apply NSE programatically
or do some type of programming on the language.

!!! is used to splice a list/vector of some sort into arguments of some quoting expression.

 library(rlang)
quo_let <- quo(paste(!!!LETTERS))
quo_let
#> <quosure>
#> expr: ^paste("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",
#> "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
#> "Z")
#> env: global
eval_tidy(quo_let)
#> [1] "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"

Created on 2021-08-30 by the reprex package (v2.0.1)

Using strings in functions in dplyr

The {{}} operator is not meant to be used with strings, it's meant for use with symbols. With strings you can either do

palmerpenguins::penguins %>% 
select(c(1, 2, 8)) %>%
names %>%
map(
function(i)
penguins %>%
group_by(!!rlang::sym(i)) %>%
tally
)

or

palmerpenguins::penguins %>% 
select(c(1, 2, 8)) %>%
names %>%
map(
function(i)
penguins %>%
group_by(.data[[i]]) %>%
tally
)

the latter of which is the currently preferred method by the tidyverse authors.



Related Topics



Leave a reply



Submit