Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow empty facet specs #3162

Merged
merged 26 commits into from
Apr 11, 2019

Conversation

yutannihilation
Copy link
Member

@yutannihilation yutannihilation commented Feb 23, 2019

Fix #3070, fix #2986.

Summary

This PR compacts facet specs automatically (#2986) and allow empty specs (#3070).

I admit this makes the implementation of FacetGrid and FacetWrap a bit complicated in that it needs some special treatments for the case of 0-variable. But, on the other hand, accepting empty specs makes the process of building facet specs simpler and clearer. So, I believe this change is worth making.

Major changes (visible to users)

  • Compact facet specs automatically.
    • If the input is NULL, treat it as an empty quosures object (quos()).
    • If the input is a quosures object, remove NULLs in it.
  • Allow empty specs. When the spec is empty,
    • facet_wrap() generates a single plot with a strip. A string (all) is used for the strip label.
    • facet_grid() generates a single plot without a strip.

Internal changes

To make the major changes, I want to define the internal facet specs more strictly as following:

  • FacetWrap requires a quosures object (e.g. quos(foo, bar))
  • FacetGrid requires a list of two quosures objects, rows and cols (e.g. list(rows = quos(foo), cols = quos(bar)))

Accordingly, the corresponding functions below return these specs.

  • grid_as_facet_list()
  • wrap_as_facet_list() (new)

Under these functions, as_facet_list() is responsible for translating a spec (e.g. a quosures object, a formula, and a character) to a list of quosures. It's also responsible for the basic validation (e.g. raise an error when the spec is an uneval object), so the upper functions should always use this for the input even it's just a simple quosures object.

Note that the results of as_facet_list() are not compacted; compacting these specs is basically the role of the upper functions, grid_as_facet_list() and wrap_as_facet_list(). For convenience, a new internal function compact_facets() can be used as a short cut of flattening and compacting the spec.

Minor changes

  • facet_wrap() and facet_grid() now properly rejects aes()

Example usages

library(ggplot2)

d <- data.frame(x = 1:10, g = rep(c("a", "b"), each = 5))
p <- ggplot(d, aes(x, x)) + geom_point()

# NULLs are compacted automatically
p + facet_wrap(vars(NULL, g, NULL))

# accepts empty specs
p + facet_wrap(NULL)

# accepts empty specs
p + facet_grid(NULL, NULL)

Created on 2019-03-03 by the reprex package (v0.2.1)

@clauswilke
Copy link
Member

I think this is good in principle. Not entirely sure about the (empty) label, though. Would it be possible to just use an empty string?

@yutannihilation
Copy link
Member Author

yutannihilation commented Feb 24, 2019

Oh, nice idea. Thanks. But, we cannot use an empty string as a variable name, so it seems I need some tweaks.

list("" = "")
#> Error: attempt to use zero-length variable name

@clauswilke
Copy link
Member

And the label and the variable name should be the same, I assume.

So maybe (empty) is fine, or alternatively how about (all data). After all, faceting usually indicates subsets of data, and here the subset is everything.

@yutannihilation
Copy link
Member Author

I forgot "(all)" is used when facet_grid(margins = TRUE). Probably we should use this label in facet_wrap() too, (and in facet_grid() too...?).

@clauswilke
Copy link
Member

Sounds good to me.

@yutannihilation
Copy link
Member Author

Two small corrections:

we cannot use an empty string as a variable name

We can, and it seems to work with the current code of ggplot2.

l <- setNames(list(""), "")
names(l)
#> [1] ""

Created on 2019-02-25 by the reprex package (v0.2.1.9000)

"(all)" is used

(all) label is not coded in ggplot2, but is returned by reshape2::add_margins().

Anyway I'll use (all) since it would be slightly look nicer when labeller = label_both.

@yutannihilation
Copy link
Member Author

Is this a typo of "uneval"...? 🤔

ggplot2/R/facet-.r

Lines 278 to 280 in 9df0a07

if (inherits(x, "mapping")) {
stop("Please use `vars()` to supply facet variables")
}

@yutannihilation
Copy link
Member Author

@lionel- @thomasp85 I think the proposal about empty facet specs is almost fixed, though I need to add more test cases before asking for revewing. I'd appreciate if you give some feedbacks and concerns :)

Copy link
Member

@lionel- lionel- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach looks good to me!

tests/testthat/test-facet-.r Outdated Show resolved Hide resolved
R/facet-grid-.r Outdated
free = free, space_free = space_free, labeller = labeller,
as.table = as.table, switch = switch, drop = drop)
)
}

# returns a list containing exactly two quosures `rows` and `cols`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it's not clear whether you're mentioning two single quosure or two quosure lists. I would use "quosure lists" or "lists of quosures" instead of just the plural "quosures".

Also we generally capitalise comments. No full stop, unless there are more than one sentence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Actually I'm struggling about the wording here...

Also we generally capitalise comments. No full stop, unless there are more than one sentence.

I see.

@yutannihilation yutannihilation changed the title WIP: allow empty facets allow empty facet specs Mar 7, 2019
@yutannihilation
Copy link
Member Author

Note that, I gave up testing strip labels ("(all)") because it seems there's no other way than visual tests, but I feel it's not worth for the cost.

Copy link
Member

@lionel- lionel- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

R/facet-.r Outdated
compact_facets <- function(x) {
x <- rlang::flatten_if(x, rlang::is_list)
null <- vapply(x, rlang::quo_is_null, logical(1))
rlang::as_quosures(x[!null])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be new_quosures(). The casting form converts formulas to quosures etc, and at this point we should have standardised the inputs already.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks!

@yutannihilation
Copy link
Member Author

@thomasp85 Do you have any comments? Since this PR is mostly about facet specs and I don't think the change is big, I expect this doesn't get in the way of #3062. But, if you have any concerns, please tell me. If there's no major concern, I'll merge.

@yutannihilation
Copy link
Member Author

Will merge this after the release of 3.2.

@thomasp85 thomasp85 merged commit a208db9 into tidyverse:master Apr 11, 2019
@yutannihilation
Copy link
Member Author

Thanks!

@yutannihilation yutannihilation deleted the feature/allow-empty-facets branch April 11, 2019 09:02
@paleolimbot paleolimbot mentioned this pull request May 6, 2019
@lock
Copy link

lock bot commented Oct 8, 2019

This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/

@lock lock bot locked and limited conversation to collaborators Oct 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should facet + empty spec be a noop? Should we compact facet grid specs?
4 participants