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

aspect ratio of plot not working with of space = "free" in facet_grid #3834

Closed
dvaiman opened this issue Feb 25, 2020 · 35 comments · Fixed by #4432
Closed

aspect ratio of plot not working with of space = "free" in facet_grid #3834

dvaiman opened this issue Feb 25, 2020 · 35 comments · Fixed by #4432
Labels
bug an unexpected problem or unintended behavior coord 🗺️ themes 💃
Milestone

Comments

@dvaiman
Copy link

dvaiman commented Feb 25, 2020

Hi, in ggplot I want to set the aspect ratio of the plot to a certain number.
space = "free"in facet_grid does not seem to work with theme(aspect.ratio = 1). Is there a workaround for this. Also I use coord_flip which hinders the use of coord_fixed. This is what I get:

Rplot09

library(ggplot2)

ggplot(mtcars, aes(hp, mpg)) +
  geom_point(aes()) +
  coord_flip()+
  theme(aspect.ratio = 1)+
  facet_grid(vars(cyl), space = "free")
@yutannihilation
Copy link
Member

space = "free" has no effect unless the scales also vary, so probably the bellow example is more appropriate? In my understanding, there's no workaround and specifying aspect.ratio = 1 should be errored as specifying coord_fixed() does.

library(ggplot2)

p <- ggplot(mtcars, aes(hp, mpg)) +
  geom_point(aes()) +
  facet_grid(vars(cyl), scales = "free", space = "free")

# works
p

# fails properly
p + coord_fixed()
#> Error: coord_fixed doesn't support free scales

# do not fail, but the plot is strange
p + theme(aspect.ratio = 1)

Created on 2020-02-29 by the reprex package (v0.3.0)

@dvaiman
Copy link
Author

dvaiman commented Mar 1, 2020

Thanks for the input, I think your example more correctly describes the problem!

@baderstine
Copy link

baderstine commented Oct 21, 2021

@thomasp85

Ah bummer. This change breaks a perfectly fine plot. :(

MWE below... If I want to make a gridded heatmap plot with geom_tile(), y is a factor, and x is character there is really no inherent scale to either axis. However, the only way to guarantee that my output plot has square tiles, while dropping irrelevant categories, is to use theme(aspect.ratio = 1) in combination with facet_grid(..., scales="free_x", and space="free_x") (unless you have other ideas?). Now this plot is impossible because it is blocked by this new error message. Can the error be changed to a warning() instead so that it is not a blocking change? There are valid use cases for this combination.

Example:

y = factor(x=c(1:6), labels=letters[1:6])
x = c("cat","dog","house","farm","road","tree")
xgrp = c("animal","animal","building","building","infrastructure","nature")
xval = rnorm(6)

dat = data.frame(y,x,xgrp, stringsAsFactors=F)

# this plot has the correct aspect ratio, but it includes every single x category in every facet, regardless of whether a value is actually present: 
ggplot(dat, aes(y = y,  x = x)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop=T) # , scales="free_x", space="free_x")


# this plot drops the irrelevant x categories in each facet, however my tiles are no longer guaranteed to be square: 
ggplot(dat, aes(y = y,  x = x)) +
  geom_tile(width = .8, height = .8) +
  # theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop=T, scales="free_x", space="free_x")

# this plot is perfect (in a previous version of ggplot2), but now I'm not allowed to make it:
ggplot(dat, aes(y = y,  x = x)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop=T, scales="free_x", space="free_x")

@clauswilke
Copy link
Member

@baderstine I'm surprised that this ever worked. It's unclear in the general case what exactly the output should be. You can always make individual plots and stitch them together with patchwork. Shouldn't be that much more work. See below for a rough example. You'd have to put in a little more work to remove the y axis labels from the interior plots, but that's maybe 5 additional lines of code or so.

library(tidyverse)
library(patchwork)

y <- factor(x=c(1:6), labels=letters[1:6])
x <- c("cat","dog","house","farm","road","tree")
xgrp <- c("animal","animal","building","building","infrastructure","nature")

data <- tibble(y, x, xgrp)

make_plot <- function(data) {
  ggplot(data, aes(y = y,  x = x)) +
    geom_tile(width = .8, height = .8) +
    scale_y_discrete(limits = letters[1:6]) +
    coord_fixed() +
    facet_grid(cols = vars(xgrp, x))
}

data %>%
  mutate(
    group = interaction(x, xgrp)
  ) %>%
  nest(data = -group) %>%
  mutate(
    plots = map(data, ~make_plot(.x))
  ) %>%
  pull(plots) %>%
  wrap_plots(nrow = 1)

Created on 2021-10-21 by the reprex package (v2.0.0)

@baderstine
Copy link

baderstine commented Oct 26, 2021

Interesting... so something like the following does the trick (although this is digging into a separate patchwork issue thomasp85/patchwork#150)

library(tidyverse)
library(patchwork)

y <- factor(x=c(1:6), labels=letters[1:6])
x <- c("cat","dog","house","farm","road","tree")
xgrp <- c("animal","animal","building","building","infrastructure","nature")

data <- tibble(y, x, xgrp)

make_plot <- function(data) {
  p <- ggplot(data, aes(y = y,  x = x)) +
    geom_tile(width = .8, height = .8) +
    scale_y_discrete(limits = letters[1:6]) +
    coord_fixed() +
    facet_grid(cols = vars(xgrp, x)) + 
    theme(axis.title = element_blank(), 
          axis.ticks.y = element_blank())
  if(data$plotnum != 1)
    p <- p + theme(axis.text.y = element_blank())
  p
}

data %>%
  mutate(
    group = interaction(x, xgrp),
    plotnum = row_number()
  ) %>%
  nest(data = -group) %>%
  mutate(
    plots = map(.x=data, .f=~make_plot(.x))
  ) %>%
  pull(plots) %>%
  wrap_plots(nrow = 1)

@FaizanKhalidMohsin
Copy link

I was just wondering is it possible to have this properly fixed?

@baderstine
Copy link

as in, restore the previous functionality when the x and y axes are discrete or what?

@Gsmith535
Copy link

Yes, please!

@FaizanKhalidMohsin
Copy link

FaizanKhalidMohsin commented Jun 24, 2022

@baderstine @Gsmith535
Yes, please, can space = "free" correctly work for facet_grid().

@baderstine
Copy link

Yep it's a bummer that a plot is possible to render but instead we get an error message.

@yutannihilation
Copy link
Member

restore the previous functionality when the x and y axes are discrete

I'm getting to feel this might sound right, though I'm still in confusion what's the appropriate behavior... Anyway, it's not a good idea to keep discussing on a closed issue. Could you file a new issue describing what's the problem and what would be the "fix", with a minimal reprex?

The rendered version of #3834 (comment) at v3.3.3 tag.

y <- factor(x = c(1:6), labels = letters[1:6])
x <- c("cat", "dog", "house", "farm", "road", "tree")
xgrp <- c("animal", "animal", "building", "building", "infrastructure", "nature")
xval <- rnorm(6)

dat <- data.frame(y, x, xgrp, stringsAsFactors = F)

devtools::load_all("~/GitHub/ggplot2/")
#> ℹ Loading ggplot2

# this plot has the correct aspect ratio, but it includes every single x category in every facet, regardless of whether a value is actually present:
ggplot(dat, aes(y = y, x = x)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop = T) # , scales="free_x", space="free_x")

# this plot drops the irrelevant x categories in each facet, however my tiles are no longer guaranteed to be square:
ggplot(dat, aes(y = y, x = x)) +
  geom_tile(width = .8, height = .8) +
  # theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop = T, scales = "free_x", space = "free_x")

# this plot is perfect (in a previous version of ggplot2), but now I'm not allowed to make it:
ggplot(dat, aes(y = y, x = x)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop = T, scales = "free_x", space = "free_x")

Created on 2022-06-24 by the reprex package (v2.0.1)

@FaizanKhalidMohsin
Copy link

@baderstine @yutannihilation I was just wondering why is this issue closed? Is the proper thing to reopen this issue or to open a new issue. Thank you.

@clauswilke
Copy link
Member

I think the real issue is that we (maybe just me) have confused coord_fixed() with the aspect.ratio setting in the theme at some places in the code. I thought they did the same, but they do not.

library(tidyverse)

df <- tibble(
  x = 1:3,
  y = 2*(1:3),
  z = 3*(1:3)
)

ggplot(df, aes(x, y)) +
  geom_point() +
  theme(aspect.ratio = 1)

ggplot(df, aes(x, y)) +
  geom_point() +
  coord_fixed()

Created on 2022-06-24 by the reprex package (v2.0.1)

Allowing coord_fixed() with free scales is not possible, because it creates facets of all different shapes and sizes and then we don't know how to tile them. But allowing a fixed aspect ratio for all facets should always be possible.

The new issue should focus on this point specifically.

@yutannihilation
Copy link
Member

yutannihilation commented Jun 24, 2022

But allowing a fixed aspect ratio for all facets should always be possible.

In my understanding, aspect.ratio is applied per panel, so it should conflict with scales = "free" scales = "free" + space = "free" because it varies the aspect ratios, at least the scale is continuous, isn't it...?

@yutannihilation

This comment was marked as resolved.

@clauswilke
Copy link
Member

@yutannihilation I think that's the request, yes, and it would be consistent with what theme(aspect.ratio = 1) does for a plot with a single panel.

And then this should also be properly documented, because right now if you read the documentation for aspect.ratio in theme() and for ratio in coord_fixed() they basically use the same language but apparently do entirely different things. There may also be other places in the code where this has been confused.

@baderstine
Copy link

baderstine commented Jun 24, 2022

@yutannihilation better version of the reprex:


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

y <- factor(x = c(1:6), labels = letters[1:6])
x <- c("cat", "dog", "house", "farm", "road", "tree")
xgrp <- c("animal", "animal", "building", "building", "infrastructure", "nature")
xval <- rnorm(6)
dat <- data.frame(y, x, xgrp, stringsAsFactors = F)


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

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

# this plot drops the irrelevant x categories in each facet, however my tiles are no longer guaranteed to be square:
ggplot(dat, aes(y = y, x = x)) +
  geom_tile(width = .8, height = .8) +
  # theme(aspect.ratio = 1) +
  facet_grid(cols = vars(xgrp, x), drop = T, scales = "free_x", space = "free_x")


# this plot is perfect (in ggplot2 v <=3.3.3), but now I'm not allowed to make it:
# I am allowed to supply a custom aspect ratio, so that I can make square tiles, and therefore makes a rather nice heatmap plot.

my.aspect.ratio = length(unique(dat$y))
ggplot(dat, aes(y = y, x = x)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = my.aspect.ratio) +
  facet_grid(cols = vars(xgrp, x), drop = T, scales = "free_x", space = "free_x")
  

@yutannihilation
Copy link
Member

@clauswilke
Sorry, I edited the comment above. Then, I found we can just remove space = "free".

library(ggplot2)

ggplot(mtcars, aes(hp, mpg)) +
  geom_point() +
  theme(aspect.ratio = 1) +
  facet_grid(vars(cyl), scales = "free")

Created on 2022-06-25 by the reprex package (v2.0.1)

@baderstine
Would you mind rendering the reprex by yourself?

@clauswilke
Copy link
Member

@yutannihilation Wait, does @baderstine just have to remove space = "free_x" to get the plot he wants?

@yutannihilation
Copy link
Member

@baderstine
Is this what you want?

library(ggplot2)

y <- factor(x = c(1:6), labels = letters[1:6])
x <- c("cat", "dog", "house", "farm", "road", "tree")
xgrp <- c("animal", "animal", "building", "building", "infrastructure", "nature")
xval <- rnorm(6)
dat <- data.frame(y, x, xgrp, stringsAsFactors = F)

my.aspect.ratio <- length(unique(dat$y))
ggplot(dat, aes(y = y, x = x)) +
  geom_tile(width = .8, height = .8) +
  theme(aspect.ratio = my.aspect.ratio) +
  facet_grid(cols = vars(xgrp, x), drop = T, scales = "free_x")

Created on 2022-06-25 by the reprex package (v2.0.1)

@clauswilke
Copy link
Member

I think he wants the individual facets square rather than the overall plot.

@baderstine
Copy link

Yep, it's about the shape of the individual plotted elements within each facet, not about the shape of the overall plot.

@baderstine
Copy link

I guess i don't have a good enough reprex to demonstrate the issue. If I remove the space = "free_x" then individual facets that contain multiple x values are squished. I'll work on a new example.

@baderstine
Copy link

baderstine commented Jun 24, 2022

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

@yutannihilation
Copy link
Member

Thanks, now I understand your intention, but I don't feel aspect.ratio works as intended (again, I believe it should be reflected to the ratios of each panel). I agree it's nice if ggplot2 can provide some way to do it.

@clauswilke
Do you think what the last example shows is the correct use of aspect.ratio and space = "free*"?

@clauswilke
Copy link
Member

Re-reading the documentation of everything, I think aspect.ratio should set the overall aspect ratio of the plot and space = "free*" should work as shown. So no, the example output does not look right. I don't think the aspect ratio for the overall plot is correct. I think it's awkward to apply the aspect ratio to individual facets, in particular if there's an option space = "free*" that we also expect to work.

We also have to accept that sometimes we have to carefully calculate the width and height of a plot such that individual facets come out just right, and this can't always be the job of ggplot2, sometimes it's on the user. That's what I did for example to make sure that all the individual squares came out right in this chapter of my book: https://clauswilke.com/dataviz/directory-of-visualizations.html

@yutannihilation
Copy link
Member

Hmm. thanks. I don't agree with the idea of the overall aspect ratio of the plot at the moment, but it might be just that I don't read the documentation enough. As it turned out we all have different opinions on this topic, probably a closed issue is too small for the discussion. But let me clarify before filing a new issue. Do you mean both of the following results are wrong as aspect.ratio sets the aspect ratio of the individual facets, not that of the whole plot? Or, are you discussing only about the cases with space = "free*"?

library(ggplot2)

d <- data.frame(
  x = c(1, 2, 3, 1, 1, 2),
  y = c(1, 2, 3, 2, 3, 2),
  g = rep(c("a", "b", "c"), times = c(3, 1, 2))
)

p <- ggplot(d, aes(x, y)) +
  geom_point() +
  theme(aspect.ratio = 1)

p + facet_wrap(vars(g))

p + facet_grid(vars(g))

Created on 2022-06-25 by the reprex package (v2.0.1)

@clauswilke
Copy link
Member

Oh, sorry, maybe I misunderstood and was wrong about the aspect ratio. Are you saying it currently applies consistently to individual facets? Maybe that's fine then, except I don't know what it would/should do when the axis ranges are different in different facets.

@yutannihilation
Copy link
Member

yutannihilation commented Jun 25, 2022

Are you saying it currently applies consistently to individual facets?

Yes, I believe that's the current behavior.

what it would/should do when the axis ranges are different in different facets.

In my understanding, as you commented on #3834 (comment), the aspect ratio is about the frame of the plot/panel and irrelevant to the actual values inside. So, I think the same rule applies to scales = "free" without any problems (while it might not be always a good idea to use it especially when the scale is continuous).

@clauswilke
Copy link
Member

Ok, that seems reasonable to me, but it would probably not produce the plot @baderstine was hoping for.

@yutannihilation
Copy link
Member

Thanks. Yeah, let's think about what option is needed to make it possible again next...

@yutannihilation
Copy link
Member

I was about to file a new issue, but it seems #4584 is the one.

@Bigsealion
Copy link

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.

Is there any way to get the same plot as plot4 in the new version of ggplot2 (e.g., 3.4.0)? Thanks!

@smouksassi
Copy link

have you tried ggforce ?
library(ggforce)
ggplot(dat, aes(y = y, x = x)) +
geom_tile(width = .8, height = .8) +
theme(aspect.ratio = 1) +
facet_row(~ xgrp+ x, drop = T, scales="free_x")+
theme(aspect.ratio = 6)

@venkmurthy
Copy link

venkmurthy commented Aug 11, 2024

I believe the ggh4x package solves this problem if you use ggh4x::facet_grid2 with options space=free and scale=free along with theme(aspect.ratio=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug an unexpected problem or unintended behavior coord 🗺️ themes 💃
Projects
None yet
10 participants