17
votes

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.

5
See this: had.co.nz/ggplot2/docs/label_parsed.html You have to rename you labels and then use the label = parsed argument. - Tyler Rinker
@TylerRinker in won't work because facet_wrap doesn't has a labeller param. Only facet_grid does, and I don't know why. - Luciano Selzer
Hopefully the facet_wrap() wasn't a hard requirement. - Tommy O'Dell

5 Answers

23
votes

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.

12
votes

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)
9
votes

As of ggplot2 2.1.0 labeller has been implemented for facet_wrap.

8
votes

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)
}) 
1
votes

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.