98

I often have numeric values for faceting. I wish to provide sufficient information to interpret these faceting values in a supplemental title, similar to the axis titles. The labeller options repeat much unnecessary text and are unusable for longer variable titles.

Any suggestions?

The default:

test<-data.frame(x=1:20, y=21:40, facet.a=rep(c(1,2),10), facet.b=rep(c(1,2), each=20))
qplot(data=test, x=x, y=y, facets=facet.b~facet.a)

enter image description here

What I would love:

enter image description here

The best I can do in ggplot:

qplot(data=test, x=x, y=y)+facet_grid(facet.b~facet.a, labeller=label_both)

enter image description here

As indicated by @Hendy, similar to: add a secondary y axis to ggplot2 plots - make it perfect

Community
  • 1
  • 1
Etienne Low-Décarie
  • 13,063
  • 17
  • 65
  • 87
  • 1
    Holy cow. I was *just* looking for this, found this via google... and now see it was asked a minute ago. [This question](http://stackoverflow.com/questions/9096671/add-a-secondary-y-axis-to-ggplot2-plots-make-it-perfect) seems to be asking the same thing based on his comment, despite the title that might imply otherwise. Great mockups to illustrate. This is exactly my problem -- it'd be nice if I didn't have to explain my numerical facet categories. The graph should speak for itself with a simple label explaining what the faceting variables were. – Hendy Jul 05 '12 at 22:19
  • I asked a [similar question in the past.](http://stackoverflow.com/q/7603949/707145) – MYaseen208 Jul 05 '12 at 23:00
  • 3
    I inquired with Winston Chang (don't know his SO handle), one of the main ggplot developers, via email. He doesn't think this is currently an option but might consider adding it. He suggested I add an issue on github, [so I did](https://github.com/hadley/ggplot2/issues/612) – Hendy Jul 06 '12 at 16:37
  • 1
    Looks like this came up on the mailing list and Winston let me know. He created a branch with a general implementation of this for top labels. See the thread [here](https://groups.google.com/forum/?fromgroups#!topic/ggplot2-dev/_gpWsOWrSDk). @hadley: you are correct. I have no idea how hard it is to do in general. If this never happens... my gratitude for ggplot still prevails :) – Hendy Jul 09 '12 at 00:49
  • 1
    At some point I stop trying to wrangle R/ggplot2 and make some desired modifications using an image editor. – Andy Aug 27 '12 at 15:24
  • 1
    I second what Andy said. Save the plot in SVG format, and pop the result into a vector editor, such as Inkscape. You can edit the plot perfectly. Is this an option in your case? – Christian Sep 26 '12 at 23:30
  • 2
    any update on best practice here? – Arthur Yip Aug 08 '19 at 19:29
  • 1
    I know this is old, but for those still looking for an answer; have a look at the answer I gave here: https://stackoverflow.com/questions/40316169/nested-facets-in-ggplot2-spanning-groups/ – teunbrand Jun 26 '20 at 13:30

6 Answers6

50

As the latest ggplot2 uses gtable internally, it is quite easy to modify a figure:

library(ggplot2)
test <- data.frame(x=1:20, y=21:40, 
                   facet.a=rep(c(1,2),10), 
                   facet.b=rep(c(1,2), each=20))
p <- qplot(data=test, x=x, y=y, facets=facet.b~facet.a)

# get gtable object
z <- ggplotGrob(p)

library(grid)
library(gtable)
# add label for right strip
z <- gtable_add_cols(z, unit(z$widths[[7]], 'cm'), 7)
z <- gtable_add_grob(z, 
                     list(rectGrob(gp = gpar(col = NA, fill = gray(0.5))),
                          textGrob("Variable 1", rot = -90, gp = gpar(col = gray(1)))),
                     4, 8, 6, name = paste(runif(2)))

# add label for top strip
z <- gtable_add_rows(z, unit(z$heights[[3]], 'cm'), 2)
z <- gtable_add_grob(z, 
                     list(rectGrob(gp = gpar(col = NA, fill = gray(0.5))),
                          textGrob("Variable 2", gp = gpar(col = gray(1)))),
                     3, 4, 3, 6, name = paste(runif(2)))

# add margins
z <- gtable_add_cols(z, unit(1/8, "line"), 7)
z <- gtable_add_rows(z, unit(1/8, "line"), 3)

# draw it
grid.newpage()
grid.draw(z)

enter image description here

Of course, you can write a function that automatically add the strip labels. A future version of ggplot2 may have this functionality; not sure though.

rawr
  • 20,481
  • 4
  • 44
  • 78
kohske
  • 65,572
  • 8
  • 165
  • 155
  • -- I am wondering if this is still the cleanest approach, as of version 0.9.3.1. I don't see something that looks like "this fuctionality" in latest documentation http://docs.ggplot2.org/0.9.3.1/ – yosukesabai Jun 22 '13 at 15:35
  • 1
    Version 0.9.3.1 provides `ggplotGrob()` that is same as `ggplot_gtable(ggplot_build())`. I think this is still the best way. – kohske Jun 23 '13 at 04:56
  • This approach overwrites the legend when there is one. I wonder if there is a way to prevent this. – papirrin Feb 19 '14 at 10:43
  • 1
    If I am not mistaken, this solution is no more valid as some functions are now deprecated. – Remi.b Mar 01 '16 at 06:34
  • 1
    @Remi.b try it now – rawr Apr 29 '16 at 16:36
  • Oh yes, I figured out but missed the opportunity to make the edit you did. I have used this "kind of code" in a number of presentation already. It is very handy. Thanks it works fine! – Remi.b Apr 29 '16 at 16:40
  • 3
    This is not working with ggplot2 3.2.1 and R 3.6.1. No error message but I don't get the labels on the resulting plot. – Calimo Sep 09 '19 at 16:16
  • It would be really helpful to have more explanation for what the code is doing. For example, I have no idea what `z$widths[[7]]` is the width of. In my application, its value is 1null, and I have no idea how adding a column with a null width helps. Oddly, that is not what that `gtable_add_cols` seems to do, as it does add space to the right for some reason. – randy Mar 26 '21 at 03:58
23

The secondary axis is now an option: https://ggplot2.tidyverse.org/reference/sec_axis.html

# Basic faceted plot
p <- ggplot(mtcars, aes(cyl, mpg)) +
  geom_point() +
  facet_grid(vs ~ am)
    
    # Create a simple secondary axis for the facets (use the appropriate scale_x function)
p + 
  scale_y_continuous(sec.axis = sec_axis(~ . , name = "SECOND Y AXIS", breaks = NULL, labels = NULL)) +
  scale_x_continuous(sec.axis = sec_axis(~ . , name = "SECOND X AXIS", breaks = NULL, labels = NULL))

Plot output

Martin Gal
  • 16,640
  • 5
  • 21
  • 39
LizLaw
  • 231
  • 2
  • 2
5

There may be a better way to do it, but you can :

fac1 = factor(rep(c('a','b'),10))
fac2 = factor(rep(c('a','b'),10))
data = data.frame(x=1:10, y=1:10, fac1=fac1, fac2=fac2)
p = ggplot(data,aes(x,y)) + ggplot2::geom_point() + 
facet_grid(fac1~fac2)
p + theme(plot.margin = unit(c(1.5,1.5,0.2,0.2), "cm"))
grid::grid.text(unit(0.98,"npc"),0.5,label = 'label ar right', rot = 270) # right
grid::grid.text(unit(0.5,"npc"),unit(.98,'npc'),label = 'label at top', rot = 0)   # top
Diogo
  • 842
  • 2
  • 11
  • 15
3

Apologies for replying to this decade old question, but wanted to put it out there that you can use ggh4x::facet_nested() to put spanning strips in a plot. Downside is that you'd need an extra package, upside is that you don't need to mess about with the gtables.

# install.packages("ggh4x")
library(ggplot2)

test<-data.frame(x=1:20, y=21:40, facet.a=rep(c(1,2),10), facet.b=rep(c(1,2), each=20))

ggplot(test, aes(x, y)) +
  geom_point() +
  ggh4x::facet_nested("Facet B" + facet.b ~ "Facet A" + facet.a)

Created on 2022-09-08 by the reprex package (v2.0.1)

Disclaimer: I wrote ggh4x.

teunbrand
  • 33,645
  • 4
  • 37
  • 63
  • Hi. I am trying to use ggh4x but I get an error. There is no package called 'ggh4x'. – Kevin Santos Sep 08 '22 at 20:02
  • 1
    @KevinSantos have you tried `install.packages("ggh4x")` first? – tjebo Sep 08 '22 at 20:09
  • That was my error. But now, in every facet add all options. I mean, in facet A appears all options for A and B, in facet B appears all options for A and B. – Kevin Santos Sep 09 '22 at 13:39
  • I'm not sure how this answer deviates from the desired solution. If your use-case is different than in the question above, feel free to post a new question. – teunbrand Sep 09 '22 at 14:33
  • @teunbrand, thank you very much for writing such a useful package. I am just wondering the behavior in you answer is not documented, and would you like to add some docs to explain a little about it? – Liang Zhang May 22 '23 at 15:50
  • @LiangZhang what behaviour in particular couldn't you find back in the documentation? – teunbrand May 22 '23 at 17:39
  • @teunbrand I meant that I didn't find documentation of the syntax `"Facet B" + facet.b ~ "Facet A" + facet.a` in `facet_nested()`. – Liang Zhang May 23 '23 at 02:22
  • Ah right. That isn't syntax specific to `facet_nested()`, you can do the same with `facet_grid()` and `facet_wrap()`. As long as it is extending ggplot2's behaviour, I didn't feel I had to document this specifically. – teunbrand May 23 '23 at 06:34
  • I understand it now. The `+` here is just add another column with given string in place for view. – Liang Zhang May 25 '23 at 13:47
1

In addition to the method outlined by kohske, you can add a border to the boxes added by changing

col=NA

to

col=gray(0.5), linetype=1

Also, change

fill=gray(0.5)

for

fill=grey(0.8)

and

gp=gpar(col=gray(1))

to

gp=gpar(col=gray(0))

If you want the new bars to match the facet labels

ie

z <- gtable_add_grob(z, 
      list(rectGrob(gp = gpar(col = gray(0.5), linetype=1, fill = gray(0.8))),
      textGrob("Variable 1", rot = -90, gp = gpar(col = gray(0)))),
      4, 8, 6, name = paste(runif(2)))
Shannon Hodges
  • 163
  • 1
  • 6
1

My preferred solution is to use gridExtra. You can put in text or you can use calls to grid.text to get some formatting options.

library(ggplot2)
# Basic faceted plot

p <- ggplot(mtcars, aes(cyl, mpg)) +
  geom_point() +
  facet_grid(vs ~ am)

grid.arrange(p,top='Top Label', right='Right Label')

enter image description here

groceryheist
  • 1,538
  • 17
  • 24