2

I am creating contour plots of different subsets of my data using ggplot2 stat_contour_filled function. However, for each it produces different color scales, which makes it hard to compare them. I have tried using the "breaks=" option, but for some reason it does not work. A MWE is:

library(ggplot2)
library(gridExtra)

v <- list()
v[[1]] <- ggplot(faithfuld, aes(waiting, eruptions, z = density)) + stat_contour_filled(breaks=(0:10)*0.005)
v[[2]] <- ggplot(faithfuld, aes(waiting, eruptions, z = 1.1*density)) + stat_contour_filled(breaks=(0:10)*0.005)

of = "mwe.png"
png(of)
print(do.call(grid.arrange,  v))
dev.off()

It produces the following plotResult of the MWE.

How can I get the same color levels for both contour plots?

Ankush
  • 149
  • 5

2 Answers2

3

The fundamental problem here is that discrete scales drop empty levels. You can use drop = FALSE to tell the relevant scale to not drop empty levels.

library(ggplot2)

breaks <- (0:10)*0.005

ggplot(faithfuld, aes(waiting, eruptions, z = density)) + 
  stat_contour_filled(breaks = (0:10)*0.005) + 
  scale_fill_viridis_d(drop = FALSE)

ggplot(faithfuld, aes(waiting, eruptions, z = 1.1*density)) + 
  stat_contour_filled(breaks = (0:10)*0.005) +
  scale_fill_viridis_d(drop = FALSE)

Created on 2020-11-17 by the reprex package (v0.3.0)

Alternatively, you could set the limits explicitly. It's a bit awkward though because it requires turning the break values into formatted strings.

library(ggplot2)

make_break_labels <- function(breaks, digits = 3) {
  n <- length(breaks)
  interval_low <- breaks[1:(n-1)]
  interval_high <- breaks[2:n]
  label_low <- format(as.numeric(interval_low), digits = digits, trim = TRUE)
  label_high <- format(as.numeric(interval_high), digits = digits, trim = TRUE)
  sprintf("(%s, %s]", label_low, label_high)
}

breaks <- (0:10)*0.005
break_labels <- make_break_labels(breaks)

ggplot(faithfuld, aes(waiting, eruptions, z = density)) + 
  stat_contour_filled(breaks = (0:10)*0.005) + 
  scale_fill_viridis_d(limits = break_labels)

ggplot(faithfuld, aes(waiting, eruptions, z = 1.1*density)) + 
  stat_contour_filled(breaks = (0:10)*0.005) +
  scale_fill_viridis_d(limits = break_labels)

Created on 2020-11-17 by the reprex package (v0.3.0)

Claus Wilke
  • 16,992
  • 7
  • 53
  • 104
0

This looks like a bug in ggplot2, maybe in the iso_to_polygon function here: https://github.com/tidyverse/ggplot2/blob/b76fa9639215785f8e94874d3bdf02225bae898a/R/stat-contour.r#L284 . If levels have no data in them (your first plot has nothing greater than 0.04, so the top two bands are empty), then they are silently discarded.

Edited again:

Here's a partial workaround. If you specify the colour scale to use drop = FALSE, no levels will be dropped. For example,

v <- list()
v[[1]] <- ggplot(faithfuld, aes(waiting, eruptions, z = density)) +
          stat_contour_filled(breaks=(0:10)*0.005) +
          scale_fill_viridis_d(drop = FALSE)
v[[2]] <- ggplot(faithfuld, aes(waiting, eruptions, z = 1.1*density)) +
          stat_contour_filled(breaks=(0:10)*0.005) +
          scale_fill_viridis_d(drop = FALSE)
print(do.call(grid.arrange,  v))

Here I've set the colours to scale_fill_viridis_d(); choose a different palette if you like. I don't know how to change the drop argument to FALSE without specifying a scale.

user2554330
  • 37,248
  • 4
  • 43
  • 90
  • I wouldn't call this a bug. Scales by default drop empty factor levels, and a simple `drop=FALSE` in the scale prevents this from happening. – Claus Wilke Nov 17 '20 at 18:22
  • I would think that the default for a filled contour scale should be `drop = FALSE`, and no `scale_*` term should be needed. It doesn't really make sense for a user to specify the breaks and have them ignored just because some bins are empty. But I'll edit my answer to give a `drop = FALSE` workaround, which is better than the one I gave. – user2554330 Nov 17 '20 at 20:15