tidyverse / ggplot2

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

allow `coord_fixed()` with `facet_grid(..., scales = "free", space = "free")` when x and y scales are discrete #4584

Closed archavan closed 3 weeks ago

archavan commented 3 years ago

It makes sense that it’s not possible to mix facet_grid(..., scales = "free", space = "free") with coord_fixed() in most situations. However, I think that it should be possible and makes sense to allow this when both, x and y, scales are discrete.

For example, a use case in this reprex with ggplot2 version 3.3.4:

library(tidyverse) # with ggplot2 v3.3.4
set.seed(1)
df <- expand_grid(x = letters[1:6], y = LETTERS[1:2]) %>% 
  mutate(w = case_when(x %in% letters[1:2] ~ "1",
                       x %in% letters[3:6] ~ "2")) %>% 
  mutate(value = rnorm(6*2))

gg <- ggplot(df) +
    geom_point(aes(x, y, color = value)) +
    facet_grid(cols = vars(w), scales = "free", space = "free")

gg

Here, we have different numbers and levels of factors in x scale, so it’s necessary to use scales = "free" and space = "free" for efficient use of space.

When I make plots like this one, I like to make sure that the breaks on x and y axes are placed equidistantly (so the background grid is made of squares), which would generally be accomplished with coord_fixed() but not possible in this case since we are using free scales and space.

gg + coord_fixed()
#> Error: coord_fixed doesn't support free scales

As of version 3.3.3 of ggplot2 it was possible to still accomplish this without coord_fixed() by the (perhaps hacky and off-label) use of theme(aspect.ratio = 1), also pointed out in this SO post. See reprex below with version 3.3.3. This is however no longer possible since this bug was fixed (#4432).

library(tidyverse) # with ggplot2 v3.3.3
set.seed(1)
df <- expand_grid(x = letters[1:6], y = LETTERS[1:2]) %>% 
  mutate(w = case_when(x %in% letters[1:2] ~ "1",
                       x %in% letters[3:6] ~ "2")) %>% 
  mutate(value = rnorm(6*2))

gg <- ggplot(df) +
  geom_point(aes(x, y, color = value)) +
  facet_grid(cols = vars(w), scales = "free", space = "free")

gg + theme(aspect.ratio = 1)

It would be great if this can be accomplished legitimately with coord_fixed() rather than by using hacks. Based on my admittedly superficial understanding, I think that the use of coord_fixed() when faceting with free scales and space should be legitimate as long as the x and y scales are discrete.

Created on 2021-08-12 by the reprex package (v2.0.0)

Gsmith535 commented 3 years ago

I would like to second this (or at least something similar), for an additional use-example while using geom_tile() in order to constrain the rectangular shapes. The ability to combine coord/aspect specifications and free scaling was especially convenient for multi-panel, faceted tile plots, and even further if different aesthetics are desired. Can there somehow be exceptions for discrete axes perhaps?

library(tidyverse) # with ggplot2 v3.3.3
library(gridExtra)
set.seed(1)
df <- expand_grid(x = letters[1:6], y = LETTERS[1:2]) %>% 
  mutate(w = case_when(x %in% letters[1:2] ~ "1",
                       x %in% letters[3:6] ~ "2")) %>% 
  mutate(value = rnorm(6*2))

gg <- ggplot(df) +
  geom_tile(aes(x, y, fill = value)) +
  facet_grid(cols = vars(w), scales = "free", space = "free") +
  scale_fill_distiller(palette="Greens")

A <- gg
A

B <- gg + theme(aspect.ratio = 1)
B

C <- gg + coord_fixed()
C
#Error: coord_fixed doesn't support free scales
#Run `rlang::last_error()` to see where the error occurred.

D <- gg + coord_equal()
D
#Error: coord_fixed doesn't support free scales
#Run `rlang::last_error()` to see where the error occurred.

grid.arrange(A, B) 

df2 <- expand_grid(x = letters[17:26], y = LETTERS[1:2]) %>% 
  mutate(w = case_when(x %in% letters[17:23] ~ "3",
                       x %in% letters[24:26] ~ "4")) %>% 
  mutate(value = rnorm(5*4))

gg2 <- ggplot(df2) +
  geom_tile(aes(x, y, fill = value)) +
  facet_grid(cols = vars(w), scales = "free", space = "free") +
  scale_fill_distiller(palette="Blues")

E <- gg2
E

F <- gg2 + theme(aspect.ratio = 1)
F

grid.arrange(A, E, B, F, ncol = 2, widths = c(0.7,1)) 

Please let me know if anything in this request was unclear.

thomasp85 commented 2 years ago

I'm generally fine with this if it can be implemented in a clean way

yutannihilation commented 2 years ago

Copying @baderstine's comment from https://github.com/tidyverse/ggplot2/issues/3834#issuecomment-1165825262, which describes why the combination of theme(aspect.ratio = ) and facet_grid(space = "free") was useful. On #3834, we came to the conclusion the combination is probably against the semantics of aspect.ratio, but this usage (the last example) should be something that ggplot2 should provide if possible. I think the combination of coord_fixed() and facet_grid(scales = "free, space = "free") should produce this?


ok, this should help:

devtools::install_version("ggplot2", "3.3.3")
library(ggplot2)

ggplot(dat, aes(y = y, x = var)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = 1) +
  facet_grid(cols = vars(vargrp, varunt), drop = T) # , scales="free_x", space="free_x")

Plot 1: has the correct aspect ratio, but it includes every single x category in every facet, regardless of whether a value is actually present:

plot1

ggplot(dat, aes(y = y, x = var)) +
  geom_tile(width = .8, height = .8) +
  # theme(aspect.ratio = 1) +
  facet_grid(cols = vars(vargrp, varunt), drop = T, scales="free_x", space="free_x")

Plot 2: drops the irrelevant x categories in each facet, however my tiles are not the shape that i want (square):

plot2

my.aspect.ratio = length(unique(dat$y))

# without space = "free_x"
ggplot(dat, aes(y = y, x = var, color=xval)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = my.aspect.ratio) +
  facet_grid(cols = vars(vargrp, varunt), drop = T, scales="free_x")

Plot 3: drops irrelevant x categories and each facet's size is constant but the facets with more x categories are now squished... more categories, more squish. :( plot3

# this plot is perfect (in ggplot2 v <=3.3.3), but now I'm not allowed to make it:
# I supply a custom aspect ratio, so that I can change the shape (aspect ratio) of my resulting tiles.

ggplot(dat, aes(y = y, x = var, color=xval)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = my.aspect.ratio) +
  facet_grid(cols = vars(vargrp, varunt), drop = T, scales="free_x", space="free_x")

Plot 4: drops irrelevant x categories and allows the size of each x facet to vary based on how many categories are in it, and with a specific aspect ratio applied, i can now make nice little squarish boxes. plot4

baptiste commented 2 years ago

Copying @baptiste's comment from #3834 (comment)

not me, @baderstine I believe

yutannihilation commented 2 years ago

Aw, yes... Sorry for the noise.