@@ -170,6 +170,67 @@ Facet <- ggproto("Facet", NULL,
170
170
171
171
# Helpers -----------------------------------------------------------------
172
172
173
+ # ' Quote faceting variables
174
+ # '
175
+ # ' @description
176
+ # '
177
+ # ' Just like [aes()], `vars()` is a [quoting function][rlang::quotation]
178
+ # ' that takes inputs to be evaluated in the context of a dataset.
179
+ # ' These inputs can be:
180
+ # '
181
+ # ' * variable names
182
+ # ' * complex expressions
183
+ # '
184
+ # ' In both cases, the results (the vectors that the variable
185
+ # ' represents or the results of the expressions) are used to form
186
+ # ' faceting groups.
187
+ # '
188
+ # ' @param ... Variables or expressions automatically quoted. These are
189
+ # ' evaluated in the context of the data to form faceting groups. Can
190
+ # ' be named (the names are passed to a [labeller][labellers]).
191
+ # '
192
+ # ' @seealso [aes()], [facet_wrap()], [facet_grid()]
193
+ # ' @export
194
+ # ' @examples
195
+ # ' p <- ggplot(diamonds) + geom_point(aes(carat, price))
196
+ # ' p + facet_wrap(vars(cut, clarity))
197
+ # '
198
+ # ' # vars() makes it easy to pass variables from wrapper functions:
199
+ # ' wrap_by <- function(...) {
200
+ # ' facet_wrap(vars(...), labeller = label_both)
201
+ # ' }
202
+ # ' p + wrap_by(cut)
203
+ # ' p + wrap_by(cut, clarity)
204
+ # '
205
+ # '
206
+ # ' # You can also supply expressions to vars(). In this case it's often a
207
+ # ' # good idea to supply a name as well:
208
+ # ' p + wrap_by(depth = cut_number(depth, 3))
209
+ # '
210
+ # ' # Let's create another function for cutting and wrapping a
211
+ # ' # variable. This time it will take a named argument instead of dots,
212
+ # ' # so we'll have to use the "enquote and unquote" pattern:
213
+ # ' wrap_cut <- function(var, n = 3) {
214
+ # ' # Let's enquote the named argument `var` to make it auto-quoting:
215
+ # ' var <- enquo(var)
216
+ # '
217
+ # ' # `quo_name()` will create a nice default name:
218
+ # ' nm <- quo_name(var)
219
+ # '
220
+ # ' # Now let's unquote everything at the right place. Note that we also
221
+ # ' # unquote `n` just in case the data frame has a column named
222
+ # ' # `n`. The latter would have precedence over our local variable
223
+ # ' # because the data is always masking the environment.
224
+ # ' wrap_by(!!nm := cut_number(!!var, !!n))
225
+ # ' }
226
+ # '
227
+ # ' # Thanks to tidy eval idioms we now have another useful wrapper:
228
+ # ' p + wrap_cut(depth)
229
+ vars <- function (... ) {
230
+ rlang :: quos(... )
231
+ }
232
+
233
+
173
234
# ' Is this object a faceting specification?
174
235
# '
175
236
# ' @param x object to test
@@ -203,38 +264,180 @@ df.grid <- function(a, b) {
203
264
))
204
265
}
205
266
267
+ # A facets spec is a list of facets. A grid facetting needs two facets
268
+ # while a wrap facetting flattens all dimensions and thus accepts any
269
+ # number of facets.
270
+ #
271
+ # A facets is a list of grouping variables. They are typically
272
+ # supplied as variable names but can be expressions.
273
+ #
274
+ # as_facets() is complex due to historical baggage but its main
275
+ # purpose is to create a facets spec from a formula: a + b ~ c + d
276
+ # creates a facets list with two components, each of which bundles two
277
+ # facetting variables.
278
+
279
+ as_facets_list <- function (x ) {
280
+ if (inherits(x , " mapping" )) {
281
+ stop(" Please use `vars()` to supply facet variables" )
282
+ }
283
+ if (inherits(x , " quosures" )) {
284
+ x <- rlang :: quos_auto_name(x )
285
+ return (list (x ))
286
+ }
287
+
288
+ # This needs to happen early because we might get a formula.
289
+ # facet_grid() directly converted strings to a formula while
290
+ # facet_wrap() called as.quoted(). Hence this is a little more
291
+ # complicated for backward compatibility.
292
+ if (rlang :: is_string(x )) {
293
+ x <- rlang :: parse_expr(x )
294
+ }
295
+
296
+ # At this level formulas are coerced to lists of lists for backward
297
+ # compatibility with facet_grid(). The LHS and RHS are treated as
298
+ # distinct facet dimensions and `+` defines multiple facet variables
299
+ # inside each dimension.
300
+ if (rlang :: is_formula(x )) {
301
+ return (f_as_facets_list(x ))
302
+ }
303
+
304
+ # For backward-compatibility with facet_wrap()
305
+ if (! rlang :: is_bare_list(x )) {
306
+ x <- as_quoted(x )
307
+ }
308
+
309
+ # If we have a list there are two possibilities. We may already have
310
+ # a proper facet spec structure. Otherwise we coerce each element
311
+ # with as_quoted() for backward compatibility with facet_grid().
312
+ if (is.list(x )) {
313
+ x <- lapply(x , as_facets )
314
+ }
315
+
316
+ if (sum(vapply(x , length , integer(1 ))) == 0L ) {
317
+ stop(" Must specify at least one variable to facet by" , call. = FALSE )
318
+ }
319
+
320
+ x
321
+ }
322
+
323
+ # Compatibility with plyr::as.quoted()
324
+ as_quoted <- function (x ) {
325
+ if (is.character(x )) {
326
+ return (rlang :: parse_exprs(x ))
327
+ }
328
+ if (is.null(x )) {
329
+ return (list ())
330
+ }
331
+ if (rlang :: is_formula(x )) {
332
+ return (simplify(x ))
333
+ }
334
+ list (x )
335
+ }
336
+ # From plyr:::as.quoted.formula
337
+ simplify <- function (x ) {
338
+ if (length(x ) == 2 && rlang :: is_symbol(x [[1 ]], " ~" )) {
339
+ return (simplify(x [[2 ]]))
340
+ }
341
+ if (length(x ) < 3 ) {
342
+ return (list (x ))
343
+ }
344
+ op <- x [[1 ]]; a <- x [[2 ]]; b <- x [[3 ]]
345
+
346
+ if (rlang :: is_symbol(op , c(" +" , " *" , " ~" ))) {
347
+ c(simplify(a ), simplify(b ))
348
+ } else if (rlang :: is_symbol(op , " -" )) {
349
+ c(simplify(a ), expr(- !! simplify(b )))
350
+ } else {
351
+ list (x )
352
+ }
353
+ }
354
+
355
+ f_as_facets_list <- function (f ) {
356
+ lhs <- function (x ) if (length(x ) == 2 ) NULL else x [- 3 ]
357
+ rhs <- function (x ) if (length(x ) == 2 ) x else x [- 2 ]
358
+
359
+ rows <- f_as_facets(lhs(f ))
360
+ cols <- f_as_facets(rhs(f ))
361
+
362
+ if (length(rows ) + length(cols ) == 0 ) {
363
+ stop(" Must specify at least one variable to facet by" , call. = FALSE )
364
+ }
365
+
366
+ if (length(rows )) {
367
+ list (rows , cols )
368
+ } else {
369
+ list (cols )
370
+ }
371
+ }
372
+
373
+ as_facets <- function (x ) {
374
+ if (is_facets(x )) {
375
+ return (x )
376
+ }
377
+
378
+ if (rlang :: is_formula(x )) {
379
+ # Use different formula method because plyr's does not handle the
380
+ # environment correctly.
381
+ f_as_facets(x )
382
+ } else {
383
+ vars <- as_quoted(x )
384
+ rlang :: as_quosures(vars , globalenv(), named = TRUE )
385
+ }
386
+ }
387
+ f_as_facets <- function (f ) {
388
+ if (is.null(f )) {
389
+ return (rlang :: as_quosures(list ()))
390
+ }
391
+
392
+ env <- rlang :: f_env(f ) %|| % globalenv()
393
+
394
+ # as.quoted() handles `+` specifications
395
+ vars <- plyr :: as.quoted(f )
396
+
397
+ # `.` in formulas is ignored
398
+ vars <- discard_dots(vars )
399
+
400
+ rlang :: as_quosures(vars , env , named = TRUE )
401
+ }
402
+ discard_dots <- function (x ) {
403
+ x [! vapply(x , identical , logical (1 ), as.name(" ." ))]
404
+ }
405
+
406
+ is_facets <- function (x ) {
407
+ if (! is.list(x )) {
408
+ return (FALSE )
409
+ }
410
+ if (! length(x )) {
411
+ return (FALSE )
412
+ }
413
+ all(vapply(x , rlang :: is_quosure , logical (1 )))
414
+ }
415
+
416
+
206
417
# When evaluating variables in a facet specification, we evaluate bare
207
418
# variables and expressions slightly differently. Bare variables should
208
419
# always succeed, even if the variable doesn't exist in the data frame:
209
420
# that makes it possible to repeat data across multiple factors. But
210
421
# when evaluating an expression, you want to see any errors. That does
211
422
# mean you can't have background data when faceting by an expression,
212
423
# but that seems like a reasonable tradeoff.
213
- eval_facet_vars <- function (vars , data , env = emptyenv()) {
214
- nms <- names(vars )
215
- out <- list ()
216
-
217
- for (i in seq_along(vars )) {
218
- out [[ nms [[i ]] ]] <- eval_facet_var(vars [[i ]], data , env = env )
219
- }
220
-
221
- tibble :: as_tibble(out )
424
+ eval_facets <- function (facets , data , env = globalenv()) {
425
+ vars <- compact(lapply(facets , eval_facet , data , env = env ))
426
+ tibble :: as_tibble(vars )
222
427
}
428
+ eval_facet <- function (facet , data , env = emptyenv()) {
429
+ if (rlang :: quo_is_symbol(facet )) {
430
+ facet <- as.character(rlang :: quo_get_expr(facet ))
223
431
224
- eval_facet_var <- function (var , data , env = emptyenv()) {
225
- if (is.name(var )) {
226
- var <- as.character(var )
227
- if (var %in% names(data )) {
228
- data [[var ]]
432
+ if (facet %in% names(data )) {
433
+ out <- data [[facet ]]
229
434
} else {
230
- NULL
435
+ out <- NULL
231
436
}
232
- } else if (is.call(var )) {
233
- eval(var , envir = data , enclos = env )
234
- } else {
235
- stop(" Must use either variable name or expression when faceting" ,
236
- call. = FALSE )
437
+ return (out )
237
438
}
439
+
440
+ rlang :: eval_tidy(facet , data , env )
238
441
}
239
442
240
443
layout_null <- function () {
@@ -325,7 +528,7 @@ combine_vars <- function(data, env = emptyenv(), vars = NULL, drop = TRUE) {
325
528
if (length(vars ) == 0 ) return (data.frame ())
326
529
327
530
# For each layer, compute the facet values
328
- values <- compact(plyr :: llply(data , eval_facet_vars , vars = vars , env = env ))
531
+ values <- compact(plyr :: llply(data , eval_facets , facets = vars , env = env ))
329
532
330
533
# Form the base data frame which contains all combinations of faceting
331
534
# variables that appear in the data
0 commit comments