8
cnt = 100
df <- data.frame(x = c(rnorm(cnt, mean = 3), rnorm(cnt, mean = 0)),
                 y = rnorm(2 * cnt), g = rep(0:1, each = cnt))

ggplot(df, aes(x, y, color = as.factor(g))) + 
  stat_density2d(aes(fill = ..level..), alpha = 0.3, geom = "polygon")

This creates a filled contour plot based on factor: enter image description here

I'd like to use a different fill scale for each contour, such that g = 0 has a red fill while g = 1 has a blue fill. Is this possible and if so, how?

Henrik
  • 65,555
  • 14
  • 143
  • 159
robbie
  • 637
  • 1
  • 7
  • 21
  • 3
    In general, no, ggplot only allows you to map each aesthetic once. Sometimes you can work around that with some effort, but generally you can't. See, for example, [here](https://groups.google.com/forum/#!topic/ggplot2/lDvsd4yJ0AE). – joran Dec 09 '13 at 15:44
  • Thanks. I was afraid this was the case. I'll find another way. – robbie Dec 09 '13 at 16:02
  • One possible solution can be found here: http://stackoverflow.com/questions/19791181/density-shadow-around-the-data-with-ggplot2-r/ – bdemarest Dec 10 '13 at 02:02
  • @Henrik, yes, that looks like it will work. I'll try it later today when I get some spare time. Thanks! – robbie Dec 11 '13 at 16:41

1 Answers1

7

As already commented by @joran, the basic design in ggplot is one scale per aesthetic. Work-arounds of various degree of ugliness are therefore required. Often they involve creation of one or more plot object, manipulation of the various components of the object, and then producing a new plot from the manipulated object(s).

Here two plot objects with different fill colour palettes - one red and one blue - are created by setting colours in scale_fill_continuous. In the 'red' plot object, the red fill colours in rows belonging to one of the groups, are replaced with blue colours from the corresponding rows in the 'blue' plot object.

library(ggplot2)
library(grid)
library(gtable)

# plot with red fill
p1 <- ggplot(data = df, aes(x, y, color = as.factor(g))) +
  stat_density2d(aes(fill = ..level..), alpha = 0.3, geom = "polygon") +
  scale_fill_continuous(low = "grey", high = "red", space = "Lab", name = "g = 0") +
  scale_colour_discrete(guide = FALSE) +
  theme_classic()

# plot with blue fill
p2 <- ggplot(data = df, aes(x, y, color = as.factor(g))) +
  stat_density2d(aes(fill = ..level..), alpha = 0.3, geom = "polygon") +
  scale_fill_continuous(low = "grey", high = "blue", space = "Lab", name = "g = 1") +
  scale_colour_discrete(guide = FALSE) +
  theme_classic()


# grab plot data
pp1 <- ggplot_build(p1)
pp2 <- ggplot_build(p2)$data[[1]]


# replace red fill colours in pp1 with blue colours from pp2 when group is 2
pp1$data[[1]]$fill[grep(pattern = "^2", pp2$group)] <- pp2$fill[grep(pattern = "^2", pp2$group)]


# build plot grobs
grob1 <- ggplot_gtable(pp1)
grob2 <- ggplotGrob(p2)

# build legend grobs
leg1 <- gtable_filter(grob1, "guide-box") 
leg2 <- gtable_filter(grob2, "guide-box") 
leg <- gtable:::rbind_gtable(leg1[["grobs"]][[1]],  leg2[["grobs"]][[1]], "first")


# replace legend in 'red' plot
grob1$grobs[grob1$layout$name == "guide-box"][[1]] <- leg


# plot
grid.newpage()
grid.draw(grob1)

enter image description here

Henrik
  • 65,555
  • 14
  • 143
  • 159
  • very nice! How do you use ggsave to save it as a pdf? – PatrickT Nov 24 '14 at 12:16
  • 1
    The first few google hits on "grid.draw ggsave" might help you. Good luck! – Henrik Nov 24 '14 at 12:57
  • They didn't which is why I asked, do you have an exact link? I tried to use the hints from some answers on stackoverflow but it didn't work. I didn't think it would be a big deal otherwise I might have asked a proper question. Thanks. – PatrickT Nov 24 '14 at 15:06
  • 1
    Sorry, I haven't tried the `ggsave` links myself. A non-`ggsave` way would be `pdf("grob1.pdf")`; `grid.newpage()`; `grid.draw(grob1)`; `dev.off()` (adapted from @hadley's answer [**here**](http://r.789695.n4.nabble.com/ggplot2-Changes-to-grobs-not-saved-to-file-output-td866151.html)). However, things have developed a lot `ggplot2`, `gtable` and `grid.extra`, so I might have missed some obvious solutions here. Good luck! – Henrik Nov 24 '14 at 15:24
  • @PatrickT, Perhaps [**this issue**](https://github.com/hadley/ggplot2/issues/1015) is relevant for your trouble using `ggsave` on the grob. – Henrik Nov 24 '14 at 22:38
  • Thanks @Henryk, the ``pdf()`` solution worked. The ``ggsave`` 'workaround' in your second comment I had already tried unsuccessfully (not sure why). Many thanks! – PatrickT Nov 26 '14 at 11:08
  • What if I do not want to have legend? When in the `theme` I set `legend.position = "none"`, then the step `leg1 <- gtable_filter(grob1, "guide-box") ` throws an error: `Error in seq.int(w[1], w[2]) : 'from' must be a finite number In addition: Warning messages: 1: In min(x) : no non-missing arguments to min; returning Inf 2: In max(x) : no non-missing arguments to max; returning -Inf 3: In min(x) : no non-missing arguments to min; returning Inf 4: In max(x) : no non-missing arguments to max; returning -Inf` – OverFlow Police Jun 16 '19 at 23:06
  • @Henrik i want to add in a third plot, which i've done following what you've done above, but i don't know how to add in the third legend any ideas? `# build plot grobs grob1 <- ggplot_gtable(pp1) grob2 <- ggplotGrob(p2) grob3 <- ggplotGrob(p3) # build legend grobs leg1 <- gtable_filter(grob1, "guide-box") leg2 <- gtable_filter(grob2, "guide-box") leg3 <- gtable_filter(grob3, "guide-box") leg <- gtable:::rbind_gtable(leg1[["grobs"]][[1]], c(leg2[["grobs"]][[1]], leg3[["grobs"]][[1]]), "first") # replace legend in 'red' plot grob1$grobs[grob1$layout$name == "guide-box"][[1]] <- leg` – user3324491 Oct 02 '19 at 13:03