Repository navigation
Expand file tree
/
Copy pathmodify.Rmd
More file actions
236 lines (156 loc) · 7.01 KB
/
Copy pathmodify.Rmd
File metadata and controls
236 lines (156 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
```{r setup, include = FALSE}
source("setup.R")
library("dplyr")
```
# Modifying inputs
With the quote-and-unquote pattern, quoted arguments are passed to other functions as is. In many cases you'll find this to be too restrictive. This chapter will guide you through the steps required to pass custom argument names and custom quoted expressions.
## Modifying names
When your function creates new columns in a data frame it's often a good idea to give them names that reflect the meaning of those columns. In this section you'll learn how to:
* Create default names for quoted arguments.
* Unquote names.
### Default argument names
If you are familiar with dplyr you have probably noticed that new columns are given default names when you don't supply one explictly to `mutate()` or `summarise()`. These default names are not practical for further manipulation but they are helpful to remind rushed users what their new column is about:
```{r}
starwars %>% summarise(average = mean(height, na.rm = TRUE))
starwars %>% summarise(mean(height, na.rm = TRUE))
```
You can create default names by applying `as_label()` to any expressions:
```{r}
var1 <- quote(height)
var2 <- quote(mean(height))
as_label(var1)
as_label(var2)
```
Including automatically quoted arguments:
```{r}
arg_name <- function(var) {
var <- enquo(var)
as_label(var)
}
arg_name(height)
arg_name(mean(height))
```
Lists of quoted expressions require a different approach because we don't want to override user-supplied names. The easiest way is to call `enquos()` with `.named = TRUE`. With this option, all unnamed arguments get a default name:
```{r}
args_names <- function(...) {
vars <- enquos(..., .named = TRUE)
names(vars)
}
args_names(mean(height), weight)
args_names(avg = mean(height), weight)
```
### Unquoting argument names
Argument names are one of the most common occurrence of quotation in R. There is no fundamental difference between these two ways of creating a `"myname"` string:
```{r}
names(c(Mickey = NA))
as_label(quote(Mickey))
```
Where there is quotation it is natural to have unquotation. For this reason, tidy eval makes it possible to use `!!` to unquote names. Unfortunately we'll have to use a somewhat peculiar syntax to unquote names because using complex expressions on the left-hand side of `=` is not valid R code:
```{r, error = TRUE}
nm <- "Mickey"
args_names(!!nm = 1)
```
Instead you'll have to unquote of the LHS of `:=`. This vestigial operator is interpreted by tidy eval functions in exactly the same way as `=` but with `!!` support:
```{r}
nm <- "Mickey"
args_names(!!nm := 1)
```
Another way of achieving the same result is to splice a named list of arguments:
```{r}
args <- setNames(list(1), nm)
args_names(!!!args)
```
This works because `!!!` uses the names of the list as argument names. This is a great pattern when you are dealing with multiple arguments:
```{r}
nms <- c("Mickey", "Minnie")
args <- setNames(list(1, 2), nms)
args_names(!!!args)
```
### Prefixing quoted arguments
Now that we know how to unquote argument, let's apply informative prefixes to the names of the columns created in `grouped_mean()`. We'll start with the summary variable:
1. Get the default name of the quoted summary variable.
2. Prepend it with a prefix.
3. Unquote it with `!!` and `:=`.
```{r}
grouped_mean2 <- function(.data, .summary_var, ...) {
summary_var <- enquo(.summary_var)
group_vars <- enquos(...)
# Get and modify the default name
summary_nm <- as_label(summary_var)
summary_nm <- paste0("avg_", summary_nm)
.data %>%
group_by(!!!group_vars) %>%
summarise(!!summary_nm := mean(!!summary_var)) # Unquote the name
}
grouped_mean2(mtcars, disp, cyl, am)
names(grouped_mean2(mtcars, disp, cyl, am))
```
Regarding the grouping variables, this is a case where explictly quoting and unquoting `...` pays off because we need to change the names of the list of quoted dots:
- Give default names to quoted dots with `.named = TRUE`.
- Prepend the names of the list with a prefix.
- Unquote-splice the list of quoted arguments as usual.
```{r}
grouped_mean2 <- function(.data, .summary_var, ...) {
summary_var <- enquo(.summary_var)
# Quote the dots with default names
group_vars <- enquos(..., .named = TRUE)
summary_nm <- as_label(summary_var)
summary_nm <- paste0("avg_", summary_nm)
# Modify the names of the list of quoted dots
names(group_vars) <- paste0("groups_", names(group_vars))
.data %>%
group_by(!!!group_vars) %>% # Unquote-splice as usual
summarise(!!summary_nm := mean(!!summary_var))
}
grouped_mean2(mtcars, disp, cyl, am)
names(grouped_mean2(mtcars, disp, cyl, am))
```
## Modifying quoted expressions
The quote-and-unquote pattern is a powerful and versatile technique. In this section we'll use it for modifying quoted arguments.
In [dealing with multiple arguments](#multiple), we have created a version of `grouped_mean()` that takes multiple grouping variables. Say we would like to take multiple summary variables instead. We could start by replacing `summary_var` with the `...` argument:
```{r}
grouped_mean3 <- function(.data, .group_var, ...) {
group_var <- enquo(.group_var)
summary_vars <- enquos(..., .named = TRUE)
.data %>%
group_by(!!group_var) %>%
summarise(!!!summary_vars) # How do we take the mean?
}
```
The quoting part is easy. But how do we go about taking the average of each argument before passing them on to `summarise()`? We'll have to modify the list of summary variables.
### Expanding quoted expressions with `expr()`
Quoting and unquoting is an effective technique for modifying quoted expressions. But we'll need to add one more function to our toolbox to work around the lack of unquoting support in `quote()`.
As we saw, the fundamental quoting function in R is `quote()`. All it does is return its quoted argument:
```{r}
quote(mean(mass))
```
`quote()` does not support quasiquotation but tidy eval provides a variant that does. With `expr()`, you can quote expressions with full unquoting support:
```{r}
vars <- list(quote(mass), quote(height))
expr(mean(!!vars[[1]]))
expr(group_by(!!!vars))
```
Note what just happened: by quoting-and-unquoting, we have expanded existing quoted expressions! This is the key to modifying expressions before passing them on to other quoting functions. For instance we could loop over the summary variables and unquote each of them in a `mean()` expression:
```{r}
purrr::map(vars, function(var) expr(mean(!!var, na.rm = TRUE)))
```
Let's fix `grouped_mean3()` using this pattern:
```{r}
grouped_mean3 <- function(.data, .group_var, ...) {
group_var <- enquo(.group_var)
summary_vars <- enquos(..., .named = TRUE)
# Wrap the summary variables with mean()
summary_vars <- purrr::map(summary_vars, function(var) {
expr(mean(!!var, na.rm = TRUE))
})
# Prefix the names with `avg_`
names(summary_vars) <- paste0("avg_", names(summary_vars))
.data %>%
group_by(!!group_var) %>%
summarise(!!!summary_vars)
}
```
```{r}
grouped_mean3(starwars, species, height)
grouped_mean3(starwars, species, height, mass)
```