Curly-Curly, the successor of Bang-Bang
RWriting functions that take data frame columns as arguments is a problem that most R users have been
confronted with at some point. There are different ways to tackle this issue, and this blog post will
focus on the solution provided by the latest release of the {rlang}
package. You can read the
announcement here, which explains really
well what was wrong with the old syntax, and how the new syntax works now.
I have written about the problem of writing functions that use data frame columns as arguments
three years ago
and two year ago too.
Last year, I wrote a
blog post that showed how to map a list of functions to a list of datasets with a list of columns
as arguments that used the !!quo(column_name)
syntax (the !!
is pronounced bang-bang).
Now, there is a new sheriff in town, {{}}
, introduced in {rlang}
version 0.4.0 that makes
things even easier. The suggested pronunciation of {{}}
is curly-curly, but there is no
consensus yet.
First, let’s load the {tidyverse}
:
library(tidyverse)
Let’s suppose that I need to write a function that takes a data frame, as well as a column from this data frame as arguments:
how_many_na <- function(dataframe, column_name){
dataframe %>%
filter(is.na(column_name)) %>%
count()
}
Let’s try this function out on the starwars
data:
data(starwars)
head(starwars)
## # A tibble: 6 x 13
## name height mass hair_color skin_color eye_color birth_year gender
## <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
## 1 Luke… 172 77 blond fair blue 19 male
## 2 C-3PO 167 75 <NA> gold yellow 112 <NA>
## 3 R2-D2 96 32 <NA> white, bl… red 33 <NA>
## 4 Dart… 202 136 none white yellow 41.9 male
## 5 Leia… 150 49 brown light brown 19 female
## 6 Owen… 178 120 brown, gr… light blue 52 male
## # … with 5 more variables: homeworld <chr>, species <chr>, films <list>,
## # vehicles <list>, starships <list>
As you can see, there are missing values in the hair_color
column. Let’s try to count how many
missing values are in this column:
how_many_na(starwars, hair_color)
Error: object 'hair_color' not found
R cannot find the hair_color
column, and yet it is in the data! Well, this is actually exactly
the issue. The issue is that the column is inside the dataframe, but when calling the function
with hair_color
as the second argument, R is looking for a variable called hair_color
that
does not exist. What about trying with "hair_color"
?
how_many_na(starwars, "hair_color")
## # A tibble: 1 x 1
## n
## <int>
## 1 0
Now we get something, but something wrong!
One way to solve this issue, is to not use the filter()
function, and instead rely on base R:
how_many_na_base <- function(dataframe, column_name){
na_index <- is.na(dataframe[, column_name])
nrow(dataframe[na_index, column_name])
}
how_many_na_base(starwars, "hair_color")
## [1] 5
This works, but not using the {tidyverse}
at all is not an option, at least for me. For instance,
the next function, which uses a grouping variable, would be difficult to implement without the
{tidyverse}
:
summarise_groups <- function(dataframe, grouping_var, column_name){
dataframe %>%
group_by(grouping_var) %>%
summarise(mean(column_name, na.rm = TRUE))
}
Calling this function results in the following error message:
Error: Column `grouping_var` is unknown
Before the release of {rlang}
0.4.0 this is was the solution:
summarise_groups <- function(dataframe, grouping_var, column_name){
grouping_var <- enquo(grouping_var)
column_name <- enquo(column_name)
mean_name <- paste0("mean_", quo_name(column_name))
dataframe %>%
group_by(!!grouping_var) %>%
summarise(!!(mean_name) := mean(!!column_name, na.rm = TRUE))
}
The core of the function remained very similar to the version from before, but now one has to
use the enquo()
-!!
syntax. While not overly difficult to use, it is cumbersome.
Now this can be simplified using the new {{}}
syntax:
summarise_groups <- function(dataframe, grouping_var, column_name){
dataframe %>%
group_by({{grouping_var}}) %>%
summarise({{column_name}} := mean({{column_name}}, na.rm = TRUE))
}
Much easier and cleaner! You still have to use the :=
operator instead of =
for the column name
however. Also, from my understanding, if you want to modify the column names, for instance in this
case return "mean_height"
instead of height
you have to keep using the enquo()
-!!
syntax.
Hope you enjoyed! If you found this blog post useful, you might want to follow me on twitter for blog post updates and buy me an espresso or paypal.me.