ggplot2: facet_wrap strip color based on variable in data set
Asked Answered
Z

5

85

Is there a way to fill the strips of facets created with facet_wrap based on a variable supplied with the data frame?

Example data:

MYdata <- data.frame(fruit = rep(c("apple", "orange", "plum", "banana", "pear", "grape")), farm = rep(c(0,1,3,6,9,12), each=6), weight = rnorm(36, 10000, 2500), size=rep(c("small", "large")))

Example plot:

p1 = ggplot(data = MYdata, aes(x = farm, y = weight)) + geom_jitter(position = position_jitter(width = 0.3), aes(color = factor(farm)), size = 2.5, alpha = 1) + facet_wrap(~fruit)

I know how to change the background color of the strips (e.g. to orange):

p1 + theme(strip.background = element_rect(fill="orange"))

facet_wrap and orange strip color

Is there a way to pass on the values in the variable size in MYdata to the parameter fill in element_rect?

Basically, instead of 1 color for all strips I would like the strip background color of small fruits (apple, plum, pear) to be green and the background color of large fruits (orange, banana, grape) to be red.

Zoophobia answered 18/10, 2013 at 1:24 Comment(5)
There isn't an easy way to do this, it has been asked before on another site though.Audiometer
Thanks, nograpes. I looked but couldn't find where it had been asked before.Zoophobia
If you get that code to work, you should post it as an answer.Audiometer
I would love to know how to do that, it is a great ideaAbamp
+1 for this feature. Would be nice to be able to specify within theme.Limitative
E
73

With a little bit of work, you can combine your plot with a dummy gtable that has the right grobs,

enter image description here

d <- data.frame(fruit = rep(c("apple", "orange", "plum", "banana", "pear", "grape")), 
                farm = rep(c(0,1,3,6,9,12), each=6), 
                weight = rnorm(36, 10000, 2500), 
                size=rep(c("small", "large")))

p1 = ggplot(data = d, aes(x = farm, y = weight)) + 
  geom_jitter(position = position_jitter(width = 0.3), 
              aes(color = factor(farm)), size = 2.5, alpha = 1) + 
  facet_wrap(~fruit)

dummy <- ggplot(data = d, aes(x = farm, y = weight))+ facet_wrap(~fruit) + 
  geom_rect(aes(fill=size), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) +
  theme_minimal()

library(gtable)

g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(dummy)

gtable_select <- function (x, ...) 
{
  matches <- c(...)
  x$layout <- x$layout[matches, , drop = FALSE]
  x$grobs <- x$grobs[matches]
  x
}

panels <- grepl(pattern="panel", g2$layout$name)
strips <- grepl(pattern="strip_t", g2$layout$name)
g2$layout$t[panels] <- g2$layout$t[panels] - 1
g2$layout$b[panels] <- g2$layout$b[panels] - 1

new_strips <- gtable_select(g2, panels | strips)
grid.newpage()
grid.draw(new_strips)

gtable_stack <- function(g1, g2){
  g1$grobs <- c(g1$grobs, g2$grobs)
  g1$layout <- transform(g1$layout, z= z-max(z), name="g2")
  g1$layout <- rbind(g1$layout, g2$layout)
  g1
}
## ideally you'd remove the old strips, for now they're just covered
new_plot <- gtable_stack(g1, new_strips)
grid.newpage()
grid.draw(new_plot)
Egide answered 5/2, 2014 at 22:25 Comment(9)
This just [R]ocked my world! Been trying to do this for ages!Lethia
Thanks for this! Any idea how to include the legend for the strip colors as well?Appealing
Extremely useful, thanks! That's going into a paper I'm writing at the moment (and I'm citing this answer in the code I'll eventually archive).Ukulele
@duckertito You could have edited the post. I did it now. In the current version ggplot2_2.2.1 you have to grepl for "strip-t" instead of strip_t. Also changed this in my edit. Thanks for the nice solution btw!Dissension
Yup, works for me too (with @andrasz change). I wish I could put a legend for the strip color though. Any idea how to do that?Sukkah
This is exactly what I need, Thanks. But, the facet labels do not show up when I run this code.Holbert
I'm also having trouble with the facet labels not showing up. Any solution found yet?Slave
kinda late answer...I have just encountered the same issue and solve it by adding this line stript <- grepl(pattern="strip-t", g2$layout$name) and then replace new_strips <- gtable_select(g2, panels | strips) with new_strips <- gtable_select(g2, panels | strips | stript)Kei
When I try this I get an error: Error in grid.Call.graphics(C_setviewport, vp, TRUE) : invalid 'layout.pos.col', yet I can't seem to figure out how to fix this. Any suggestions?Woken
S
32

If you want to have different fills to the strip backgrounds, you can use facets in ggh4x to set a more complicated strip with strip_themed(). No hassle with gtables and your plot remains a ggplot, so you can add the usual layers/scales/theme options etc afterwards.

library(ggh4x)
#> Loading required package: ggplot2

# Only colour strips in x-direction
strip <- strip_themed(background_x = elem_list_rect(fill = rainbow(7)))

# Wrap variant
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap2(~ class, strip = strip)

It works for the grid layout too, but if you want to colour the vertical strips, you'd need to set the background_y argument in strip_themed() too.

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_grid2(year ~ cyl, strip = strip)

Created on 2023-01-04 by the reprex package (v2.0.1)

Disclaimer: I'm the author of ggh4x

Sara answered 4/1, 2023 at 20:27 Comment(9)
Def, best solution! Very nice ggplot2 extension. It's so easy to use! This should be the answer now!Nutritive
I agree. This is the ggplot2 extension is a great solution. It works for changing all elements of the strip text (e.g. font, face, size, color) and rectangle (fill, etc.) . It is intuitive too. I just wish the help pages integrated into R Studio a little better. (R studio, doesn't find the function names in the help tab even when the library is loaded). Other than that.. Perfect!Acrid
This is fantastic! Great package @teunbrand, thanks for the contributionsBlush
I don't see how this remotely answers the question, which is to facet the strip colours based on size of fruit.Dye
I'm sorry that this answer wasn't satisfactory for your purposes. When you find a better method, feel free to post it as an answer.Sara
The problem is that, albeit it is a great answer, it does not really answer the missing part: how to make it correspond to the other variable. That part is easier, regardless, so you still got my upvoteSubsist
Hmmm... this solution seems great but I get the following error: Error in rbind_dfs(values[has_all]) : could not find function "rbind_dfs" 12. unique(rbind_dfs(values[has_all])) 11. vars_combine(...) 10. self$vars_combine(data, params$plot_env, rows, drop = params$drop) 9. compute_layout(..., self = self) 8. self$facet$compute_layout(data, self$facet_params) 7. setup(..., self = self) 6. layout$setup(data, plot$data, plot$plot_env) 5. ggplot_build.ggplot(x) 4. ggplot_build(x) 3. print.ggplot(p) 2. print(p) 1. plotADT(harmonized) Buckie
@CarmenSandoval perhaps similar to this? github.com/teunbrand/ggh4x/issues/130Sara
The question was about making different facet_strip backgrounds and this answer provides a mechanism to do so. To use this answer for the original question, you would just have to sub out the fill = rainbow(7) argument appropriately. In this case you could easily manually do something like fill = c("blue", "red", "red", "red", "blue, "blue") to match the fruit size categories. This could also be automated.Girlie
W
5

You can find an updated answer to this question here.

g <- ggplot_gtable(ggplot_build(p))
stripr <- which(grepl('strip-r', g$layout$name))
fills <- c("red","green","blue","yellow")
k <- 1
for (i in stripr) {
  j <- which(grepl('rect', g$grobs[[i]]$grobs[[1]]$childrenOrder))
  g$grobs[[i]]$grobs[[1]]$children[[j]]$gp$fill <- fills[k]
  k <- k+1
}
grid::grid.draw(g)

enter image description here

Writhen answered 6/2, 2020 at 19:46 Comment(3)
Anyone have an idea on how to add a legend for the strip colours?Ginoginsberg
@MaxJ. If you're not using the "color" or "fill" aesthetic, you could probably create a dummy legend that matches the strip colors instead of the data. But that's another hassle on top of the hassle of modifying the gtable. Perhaps ggh4x offers an easier solution?Writhen
I was able to create a legend by creating a dummy plot and add it via cowplot but I do not know how to do it in ggplot directly.Ginoginsberg
A
0

I would love to know how to do that, it is a great idea. One idea is to generate each chart independently with a different color as you do and then use something like multiplot or viewports to show then side by side - it will require a bit more work.

if you want to extract the legend, which you will need for this approach - here is some code from Hadley that I found a while back

g_legend<-function(a.gplot){
  tmp <- ggplot_gtable(ggplot_build(a.gplot))
  leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
  legend <- tmp$grobs[[leg]]
  return(legend)}

see how it is extracted it from chart p, and then I took it out of the plot legend <- g_legend(p) lwidth <- sum(legend$width) #if you want to define the viewport based on this p <- p + theme(legend.position="none")

then you eventually draw it

grid.newpage()
vp <- viewport(width = 1, height = 1)
#print(p, vp = vp)

submain <- viewport(width = 0.9, height = 0.9, x = 0.5, y = 1,just=c("center","top"))
print(p, vp = submain)
sublegend <- viewport(width = 0.5, height = 0.2, x = 0.5, y = 0.0,just=c("center","bottom"))
print(arrangeGrob(legend), vp = sublegend)

Good luck

Abamp answered 5/2, 2014 at 19:1 Comment(0)
N
-1

It's not directly for differently coloring your facets but here you have another (very quick and simpler) solution, based on facet by two variables (size ~ fruit) instead one (~ fruit):

ggplot(data = MYdata, aes(x = farm, y = weight)) + 
  geom_jitter(position = position_jitter(width = 0.3), 
      aes(color = factor(farm)), size = 2.5, alpha = 1) + 
  facet_wrap(size ~ fruit)

enter image description here

Nesto answered 4/3, 2020 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.