2

I'm trying to great a plot with a grouped boxplot over the top of a set of polygons, and would like to use the fill aesthetic to colour the polygons based on a continuous variable, and the boxplot based on a separate grouping variable. I've found several posts that helped me get the boxplot over the polygons, and have found someone else who had a related problem, but nothing with a solution.

This is very nearly the type of plot I want:

rectangle <- data.frame(x = c("Fair", "Very Good", "Fair", "Very Good"),
lower = c(rep(3000, 2), rep(5500, 2)),
upper = c(rep(5000, 2), rep(7000, 2)),
band = c(1,1,2,2))
ggplot() + geom_blank(data=diamonds, aes(x=cut, y=price, colour = color)) +
geom_rect(data=rectangle, aes(xmin=-Inf, xmax=Inf, 
                                    ymin=lower, ymax=upper, fill = band), alpha=0.1) +
geom_boxplot(data=diamonds, aes(x=cut, y=price, colour = color)) 

But I'd like to use 'fill' instead of 'colour' to do the boxplots, as it will work much better in the context I actually need it. If I use fill in both the geom_rect and the geom_boxplot I end up with an error:

ggplot() + geom_blank(data=diamonds, aes(x=cut, y=price, colour = color)) +
geom_rect(data=rectangle, aes(xmin=-Inf, xmax=Inf, 
                                ymin=lower, ymax=upper, fill = band), alpha=0.1) +
geom_boxplot(data=diamonds, aes(x=cut, y=price, fill = color))

##Error: Discrete value supplied to continuous scale 

Is there a way I can complete this figure they way I need? Thanks!

tjebo
  • 21,977
  • 7
  • 58
  • 94
CMB
  • 35
  • 5

2 Answers2

3

That became very simple with ggnewscale:

library(ggplot2)
library(ggnewscale)

rectangle <- data.frame(x = c("Fair", "Very Good", "Fair", "Very Good"),
                        lower = c(rep(3000, 2), rep(5500, 2)),
                        upper = c(rep(5000, 2), rep(7000, 2)),
                        band = c(1,1,2,2))
ggplot() + geom_blank(data=diamonds, aes(x=cut, y=price, colour = color)) +
  geom_rect(data=rectangle, aes(xmin=-Inf, xmax=Inf, 
                                ymin=lower, ymax=upper, fill = band), alpha=0.1) +
  ggnewscale::new_scale_fill()+
  geom_boxplot(data=diamonds, aes(x=cut, y=price, colour = color, fill = color)) 

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

tjebo
  • 21,977
  • 7
  • 58
  • 94
2

Following an update from the OP, it seems the desire is for a continuous gradient to be applied to the polygons under the boxplot.

To my knowledge, there is no direct way to do this in ggplot2, but there is a workaround. This is to create a plot with the polygons first, then convert them into a grob. This grob can then be added as an annotation layer under the boxplots in the final plot.

First, create the polygons in a plot and store it:

g <- ggplot() + 
     geom_blank(data = diamonds, aes(x = cut, y = price)) +
     geom_rect(data = rectangle, 
               aes(xmin = -Inf, xmax = Inf, ymin = lower, ymax = upper, fill = band), 
               alpha = 0.1) +
     scale_fill_continuous(type = "gradient", low = "blue", high = "red")

plot(g)

This gives the polygons we want to underplot:

enter image description here

Now we can convert this plot into a grob tree and extract just the polygons like this:

g        <- ggplotGrob(g)
polygons <- g[7]$grobs[[2]]  # I found this by exploring the grob tree

Finally, we add out polygons grob as a seperate layer in our final plot, whilst doing whatever we like with the boxplots.

ggplot() + 
  geom_blank(data = diamonds, aes(x = cut, y = price, colour = color)) +
  annotation_custom(polygons) +
  geom_boxplot(data=diamonds, aes(x = cut, y = price, fill = color))

enter image description here

And if you want the underlying polygons to have a smooth gradient fill, you need to split them up into smaller bands and fill them that way. For example, if you define rectangle like this:

rectangle <- data.frame(lower = c(seq(3000, 5000, length.out = 201)[-201], 
                                  seq(5500, 7000, length.out = 201)[-201]),
                        upper = c(seq(3000, 5000, length.out = 201)[-1], 
                                  seq(5500, 7000, length.out = 201)[-1]),
                        band = c(1:200, 301:500))

You get this (I have increased the alpha of the polygons to make them more obvious):

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87