tidyverse / ggplot2

An implementation of the Grammar of Graphics in R
https://ggplot2.tidyverse.org
Other
6.5k stars 2.02k forks source link

Feature request: allow blank panels of `facet_wrap()` to be at the beginning #5212

Closed BrianDiggs closed 5 months ago

BrianDiggs commented 1 year ago

Existing behavior

When using facet_wrap() and there are fewer levels than will completely fill the created grid, the last panels will be left blank. Which panels are considered "last" depends on the values of the parameters dir and as.table.

Requested behavior

The ability to have the first panels blank rather than the last. "End-justified" rather than "begin-justified" to put it another way.

Examples

Drawing the base of the example from the examples in the facet_wrap() help page.

library("ggplot2")

p <- ggplot(mpg, aes(displ, hwy)) + geom_point()

Since I'm only interested in the arrangement of the panels, I'm just going to summarize the output with a text grid showing where each panel is. X represents a blank spot.

# Existing behavior
p + facet_wrap(vars(class))
# 123
# 456
# 7XX
p + facet_wrap(vars(class), dir = "v")
# 147
# 25X
# 36X
p + facet_wrap(vars(class), as.table = FALSE)
# 7XX
# 456
# 123
p + facet_wrap(vars(class), dir = "v", as.table = FALSE)
# 741
# X52
# X63

As a separate note, I would never have guessed what the combination of dir = "v" and as.table = FALSE would have given me. I would have assumed it would have been:

# 36X
# 25X 
# 147

But that's not the point of this feature request.

# Desired behavior
p + facet_wrap(vars(class), justify = "end")
# XX1
# 234
# 567
p + facet_wrap(vars(class), dir = "v", justify = "end")
# X25
# X36
# 147
p + facet_wrap(vars(class), as.table = FALSE, justify = "end")
# 567
# 234
# XX1
p + facet_wrap(vars(class), dir = "v", as.table = FALSE, justify = "end")
# 52X
# 63X
# 741

Implementation suggestions

I have demonstrated these by adding a justify argument to facet_wrap(), but I am not recommending that as an implementation. Primarily, adding another argument just increases the number of combination of arguments that have to be accounted for and tested.

A suggestion which may be better is a scrapping of the as.table argument and replacing it with an argument which specifies which corner to begin placing panels in (values "ul" for upper left, "ll" for lower left, "ur" for upper right, "lr" for lower right). The existing dir argument would then specify which direction panels were to go next. For example:

p + facet_wrap(vars(class), start = "ul", dir = "h") # the defaults
# 123
# 456
# 7XX
p + facet_wrap(vars(class), start = "ul", dir = "v")
# 147
# 25X
# 36X
p + facet_wrap(vars(class), start = "ll", dir = "h") 
# 7XX
# 456
# 123
p + facet_wrap(vars(class), start = "ll", dir = "v")
# 36X
# 25X
# 147
p + facet_wrap(vars(class), start = "ur", dir = "h") 
# 321
# 654
# XX7
p + facet_wrap(vars(class), start = "ur", dir = "v")
# 741
# X52
# X63
p + facet_wrap(vars(class), start = "lr", dir = "h") 
# XX7
# 654
# 321
p + facet_wrap(vars(class), start = "lr", dir = "v")
# X63
# X52
# 741

These 8 combinations map to the 8 combinations of dir/as.table/justify, but sometimes requiring the order of the faceting variable to be reversed.

teunbrand commented 1 year ago

I like the idea in principle, but I have doubts what the most intuitive argument name / values are. Moreover, the facet code is pretty complex already and I wonder whether this might befit an extension better.

teunbrand commented 6 months ago

Giving this some more thought, I think it might be cleaner to give the dir argument more options. I'd propose that dir can take 2 of the letters trbl (top, right, bottom, left), where (1) the first letter indicates along which direction the panel increases by 1 and (2) the second letters indicates the orthogonal direction. For example, giving dir = "lt" says you start in the left-to-right direction and grow in the top-to-bottom direction. For completeness, that would give the following layouts:

p + facet_wrap(vars(class), dir = "lt") # the defaults
# 123
# 456
# 7XX
p + facet_wrap(vars(class), dir = "tl")
# 147
# 25X
# 36X
p + facet_wrap(vars(class), dir = "lb") 
# 7XX
# 456
# 123
p + facet_wrap(vars(class), dir = "bl")
# 36X
# 25X
# 147
p + facet_wrap(vars(class), dir = "rt") 
# 321
# 654
# XX7
p + facet_wrap(vars(class), dir = "tr")
# 741
# X52
# X63
p + facet_wrap(vars(class), dir = "rb") 
# XX7
# 654
# 321
p + facet_wrap(vars(class),  dir = "br")
# X63
# X52
# 741