Search code examples
rggplot2facetfacet-wrapgeom-tile

Faceting geom_tile in ggplot2 with different axis scales produces a banded image


I'm having some unexpected behavior in ggplot with geom_tile() and faceting.

Here's the classic "volcano" tile:

library(ggplot2)
library(reshape2)
volcano %>%
  reshape2::melt() %>%
  ggplot() + geom_tile(aes(x = Var1, y = Var2, fill = value))

enter image description here

Good. Now let's add an index column and facet the image...

volcano %>%
  reshape2::melt() %>%
  cross_join(tibble(idx = c(1,2,3))) %>%
  ggplot() + geom_tile(aes(x = Var1, y = Var2, fill = value)) +
    facet_wrap(vars(idx))

enter image description here

OK. That's nice. Now I'm going to create different scales on the x axis and use the scales = "free" parameter...

volcano %>%
  reshape2::melt() %>%
  cross_join(tibble(idx = c(1,2,3))) %>%
  mutate(Var1 = Var1/idx) %>%
  ggplot() + geom_tile(aes(x = Var1, y = Var2, fill = value)) +
  facet_wrap(vars(idx), scales = "free")
}

enter image description here

Note the x-axis values differ between the last graph and this one.

It's not that ggplot has a fundamental problem with the re-scaled axis. Here I plot the third panel of the prior image by itself with no faceting.

volcano %>%
  reshape2::melt() %>%
  cross_join(tibble(idx = c(1,2,3))) %>%
  mutate(Var1 = Var1/idx) %>%
  filter(idx == 3) %>%
  ggplot() + geom_tile(aes(x = Var1, y = Var2, fill = value))

enter image description here

The problem is faceting with different scales. The problem persists even if I don't use scales = "free" parameter...

volcano %>%
  reshape2::melt() %>%
  cross_join(tibble(idx = c(1,2,3))) %>%
  mutate(Var1 = Var1/idx) %>%
  ggplot() + geom_tile(aes(x = Var1, y = Var2, fill = value)) +
  facet_wrap(vars(idx))

enter image description here

Seems lake a bug to me, but I don't see an issue report when I search "geom_tile facet" on github tidyverse/ggplot.

Anyone out there have an idea how I could get a proper plot without the banding?

UPDATE: Here is a small example that shows what is going on in more detail. ggplot appears to be calculating the width of each column in the tile using one scale, and applying that width to all of the plots...

# make some data
data.frame(x = 1:10, y = rep(1:10, each=10), value = runif(100)) %>%

  # duplicate the data and alter the scale of the second set
  bind_rows(., mutate(., x=x/3), .id = "idx") %>%

  # plot with faceting
  ggplot() + geom_tile(aes(x=x, y=y, fill = value)) + facet_wrap("idx")

enter image description here


Solution

  • You could vary tile width by facet. ggplot2 tiles are based on what ggplot2 identifies as the "resolution" of the data.

    volcano %>%
      reshape2::melt() %>%
      cross_join(tibble(idx = c(1,2,3))) %>%
      mutate(width = max(diff(Var1), na.rm = TRUE), .by = idx) |>
       ggplot() + 
      geom_tile(aes(x = Var1, y = Var2, fill = value, width = width)) +
      facet_wrap(vars(idx), scales = "free")
    

    enter image description here