18

I wonder how to change the facet label to math formula in ggplot2.

d <- ggplot(diamonds, aes(carat, price, fill = ..density..)) +
  xlim(0, 2) + stat_binhex(na.rm = TRUE) + opts(aspect.ratio = 1)
d + facet_wrap(~ color, ncol = 4)

enter image description here

For example, I want to change facet label from D to Y[1], where 1 is subscript. Thanks in advance for your help.

I found this answer but it does not work for me. I'm using R 2.15.1 and ggplot2 0.9.1.

Community
  • 1
  • 1
MYaseen208
  • 22,666
  • 37
  • 165
  • 309

5 Answers5

24

You can edit the grobs in the gtable,

ggplot(diamonds, aes(carat, price, fill = ..density..)) +
  xlim(0, 2) + stat_binhex(na.rm = TRUE) + facet_wrap(~ color, ncol = 4)


for(ii in 1:7)
grid.gedit(gPath(paste0("strip_t-", ii), "strip.text"), 
           grep=TRUE, label=bquote(gamma[.(ii)]))

enter image description here

alternatively, if you want to save a grob,

g <- ggplotGrob(d)
gg <- g$grobs

strips <- grep("strip_t", names(gg))
for(ii in strips)
  gg[[ii]] <- editGrob(getGrob(gg[[ii]], "strip.text", 
                               grep=TRUE, global=TRUE), 
                       label=bquote(gamma[.(ii)]))

g$grobs <- gg

using ggsave would require extra (ugly) work, since one has to fool the test for class ggplot... I reckon it will be easier to call pdf() ; grid.draw(g); dev.off() explicitly.


Edit by Roland:

I made a small correction and wrapped it in a function:

facet_wrap_labeller <- function(gg.plot,labels=NULL) {
  #works with R 3.0.1 and ggplot2 0.9.3.1
  require(gridExtra)
  
  g <- ggplotGrob(gg.plot)
  gg <- g$grobs      
  strips <- grep("strip_t", names(gg))
    
  for(ii in seq_along(labels))  {
    modgrob <- getGrob(gg[[strips[ii]]], "strip.text", 
                       grep=TRUE, global=TRUE)
    gg[[strips[ii]]]$children[[modgrob$name]] <- editGrob(modgrob,label=labels[ii])
  }
  
  g$grobs <- gg
  class(g) = c("arrange", "ggplot",class(g)) 
  g
}

This allows to print nicely and even ggsave can be used.

Community
  • 1
  • 1
baptiste
  • 75,767
  • 19
  • 198
  • 294
  • Any idea how to change and save the grob, so that `ggsave` can be used? – Roland Jun 06 '13 at 15:39
  • 2
    @Roland, my alternative [solution](http://stackoverflow.com/questions/13297155/add-floating-axis-labels-in-facet-wrap-plot/13316126#13316126) ("Edit 3") concerns this exact issue. `class(g) <- c("facetAdjust", "gtable", "ggplot")` and `print.facetAdjust` from my answer would help here. – Julius Vainora Jun 06 '13 at 18:31
  • 2
    @Roland if you use the gridExtra package, you can use `class(g) = c("arrange", "ggplot",class(g))` as the `print.arrange` method will work with `ggsave`. – baptiste Jun 07 '13 at 12:49
  • `no applicable method for 'validGrob' applied to an object of class "c('arrange', 'gtable', 'ggplot')"` – Roland Jun 07 '13 at 12:55
  • strange, do you have gridExtra loaded? – baptiste Jun 07 '13 at 13:03
  • Yes, I changed that inside the function. – Roland Jun 07 '13 at 13:09
  • I don't get this error, but took the opportunity to fix a typo you had in the function. – baptiste Jun 07 '13 at 13:22
  • My bad, I missed one of the classes. – Roland Jun 07 '13 at 13:26
  • @Roland my labels are of the form K[12] or K[12:3] but your function doesn't give the parts in square brackets as subscripts? What could be missing? – nstjhp Aug 12 '14 at 17:24
  • @nstjhp Well, did you wrap your labels into `bquote` or `expression`? – Roland Aug 13 '14 at 07:01
  • @Roland I tried many combinations but presumably in the wrong place. When I tried expression in the label argument to editGrob then all I got was "ii" as subscript to "labels" for all facets (as could be expected I suppose). What is the correct way of wrapping the labels vector? – nstjhp Aug 13 '14 at 11:51
  • @nstjhp Please ask a new question (including a reproducible example). – Roland Aug 13 '14 at 12:34
  • Could the above solution be modified to work with two factors (i.e. facet_wrap( ~ A + B)). – Meep Jul 02 '15 at 23:03
13

Perhaps somebody has changed the name of the edit-Grob function at some point. (Edit: It was removed by @hadley about 8 months ago.) There is no geditGrob but just editGrob from pkg:grid seems to work:

 d <- ggplot(diamonds, aes(carat, price, fill = ..density..)) +
              xlim(0, 2) + stat_binhex(na.rm = TRUE) + opts(aspect.ratio = 1)

 #Note: changes in ggplot2 functions cause this to fail from the very beginning now.
 # Frank Harrell's answer this year suggests `facet_warp` now accepts `labeller`


 d <- d + facet_wrap(~ color, ncol = 4)
 grob <- ggplotGrob(d)
 strip_elem <- grid.ls(getGrob(grob, "strip.text.x", grep=TRUE, global=TRUE))$name
#strip.text.x.text.1535
#strip.text.x.text.1541
#strip.text.x.text.1547
#strip.text.x.text.1553
#strip.text.x.text.1559
#strip.text.x.text.1565
#strip.text.x.text.1571
grob <- editGrob(grob, strip_elem[1], label=expression(Y[1]))
grid.draw(grob)
IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • 9
    as of ggplot 0.9.2 this answer no longer works (at least for me). `strip_elem <- grid.ls(getGrob(grob, "strip.text.x", grep=TRUE, global=TRUE))$name` gives `Error in getGrob(grob, "strip.text.x", grep = TRUE, global = TRUE) : It is only valid to get a child from a 'gTree'` any idea on how to make it work? – Tyler Rinker Sep 10 '12 at 05:17
9

As of ggplot2 2.1.0 labeller has been implemented for facet_wrap.

Frank Harrell
  • 1,954
  • 2
  • 18
  • 36
8

Just came across this very useful function from roland and baptiste but needed a slightly different use case where the original wrap headers should be converted by a function rather than provided as a fixed value. I'm posting a slightly modified version of the original function in case it's useful to anybody else. It allows both the use of named (fixed value) expressions for the wrap strips, as well as use of custom functions and the functions already provided by ggplot2 for the facet_grid labeller parameter (such as label_parsed and label_bquote).

facet_wrap_labeller <- function(gg.plot, labels = NULL, labeller = label_value) {
  #works with R 3.1.2 and ggplot2 1.0.1
  require(gridExtra)

  # old labels
  g <- ggplotGrob(gg.plot)
  gg <- g$grobs      
  strips <- grep("strip_t", names(gg))
  modgrobs <- lapply(strips, function(i) {
    getGrob(gg[[i]], "strip.text", grep=TRUE, global=TRUE)
  })
  old_labels <- sapply(modgrobs, function(i) i$label)

  # find new labels
  if (is.null(labels)) # no labels given, use labeller function
    new_labels <- labeller(names(gg.plot$facet$facets), old_labels)
  else if (is.null(names(labels))) # unnamed list of labels, take them in order
    new_labels <- as.list(labels)
  else { # named list of labels, go by name where provided, otherwise keep old
    new_labels <- sapply(as.list(old_labels), function(i) {
      if (!is.null(labels[[i]])) labels[[i]] else i
    })
  }

  # replace labels
  for(i in 1:length(strips))  {
    gg[[strips[i]]]$children[[modgrobs[[i]]$name]] <- 
       editGrob(modgrobs[[i]], label=new_labels[[i]])
  }

  g$grobs <- gg
  class(g) = c("arrange", "ggplot",class(g))
  return(g) 
}

Update / warning

For newer versions of the gridExtra package you'll get the error Error: No layers in plot when running this function because arrange is no longer in gridExtra and R tries to interpret it as a ggplot. You can fix this by (re-)introducing the print function for the arrange class:

print.arrange <- function(x){
    grid::grid.draw(x)
}

This should now allow the plot to render and you can use ggsave() e.g. like so: ggsave("test.pdf", plot = facet_wrap_labeller(p, labeller = label_parsed))

Examples

A couple of use case examples:

# artificial data frame
data <- data.frame(x=runif(16), y=runif(16), panel = rep(c("alpha", "beta", "gamma","delta"), 4))
p <- ggplot(data, aes(x,y)) + geom_point() + facet_wrap(~panel)

# no changes, wrap panel headers stay the same
facet_wrap_labeller(p) 

# replace each panel title statically
facet_wrap_labeller(p, labels = expression(alpha^1, beta^1, gamma^1, delta^1)) 

# only alpha and delta are replaced
facet_wrap_labeller(p, labels = expression(alpha = alpha^2, delta = delta^2)) 

# parse original labels
facet_wrap_labeller(p, labeller = label_parsed) 

# use original labels but modifying them via bquote
facet_wrap_labeller(p, labeller = label_bquote(.(x)^3)) 

# custom function (e.g. for latex to expression conversion)
library(latex2exp)
facet_wrap_labeller(p, labeller = function(var, val) { 
  lapply(paste0("$\\sum\\", val, "$"), latex2exp)
}) 
sebkopf
  • 2,335
  • 19
  • 18
  • Hmmm. I'm getting `Error: No layers in plot` whenever I call the `facet_wrap_labeller` function. I'm loading the same version of ggplot2 (1.0.1)... although my R version is newer (3.2.2). Anyone else running into the same problem? – Grant Nov 04 '15 at 23:16
  • You are right, thanks for reporting. As far as I can tell, the problem is that the newest version of gridExtra (which you'll have gotten after installing R 3.2.2) no longer has the `arrange` class so it tries to plot as a `ggplot` but it's no longer a `ggplot`, it's already the `gtable`/`grob` object so it fails with the error message you observed. I don't know if there is a new way to do this using gridExtra, maybe @baptiste has an idea? For now, you can do the following: remove the line `class(g) = c("arrange", "ggplot",class(g))` and use `grid.arrange(facet_wrap_labeller(p))` on your plots – sebkopf Nov 05 '15 at 20:17
  • @Grant, scratch that, easier to just (re-)introduce `print.arrange`. I updated the answer accordingly, basically just add: `print.arrange <- function(x){ grid::grid.draw(x) }` – sebkopf Nov 05 '15 at 20:48
2

Thank you to the other answers and comments for mentioning label_parsed. It was still not really clear how to use the label parser so I add a simple reproducible example here.

library(dplyr)
library(ggplot2)

# Create a first facet variable with examples of math formulas
iris2 <- iris %>%
    mutate(species_math = factor(Species,
                            levels = c("setosa", "versicolor", "virginica"),
                            labels = c("m^2",
                                       expression(bar(x) == sum(frac(x[i], n), i==1, n) * beta * Q[t-1]),
                                       bquote(pi == .(pi)))))

# Create a second facet variable with mean lengths
# This illustrates how to pass a numeric vector inside a formula
iris_mean <- iris2 %>%
    group_by(Species) %>%
    summarise(across(ends_with("Length"), mean), .groups="drop")

iris2$mean_length <- factor(iris2$Species,
                           levels =  c("setosa", "versicolor", "virginica"),
                           labels = mapply(function(p, s) bquote(bar(p) == .(p) ~ bar(s) ==.(s)),
                                            round(iris_mean$Petal.Length,3), round(iris_mean$Sepal.Length,3)))


iris2 %>%
    ggplot(aes(x = Petal.Length, y = Petal.Width)) +
    geom_point() +
    facet_wrap(species_math ~ mean_length + Species, labeller = labeller(species_math = label_parsed, mean_length = label_parsed))

ggsave("~/downloads/formula_in_facet.png",
       width = 12, height = 8, units = "cm")

enter image description here

As shown in the example above, the labeller can parse:

  1. A character vector such as "m^2" for simple formulas
  2. An expression for more complex math with indices
  3. The output of bquote to include numerical values in the formula. See also this answer on how to use bquote with numerical vectors of more than one value.
  4. see this other answer on how to apply the labeller to one of the faceting variables only. In our case we apply it to the species_math variable only.

The syntax is different from Latex math formulas because label_parsed interprets labels as plotmath expressions. For example indices are written x_i in Latex and x[i] in plot math expressions, and Greek letters are written directly as alpha instead of \alpha in Latex. You can find many formulas in the help page of the plotmath function. Good luck with the plotmath examples.

Paul Rougieux
  • 10,289
  • 4
  • 68
  • 110