[r] How to change facet labels?

I have used the following ggplot command:

ggplot(survey, aes(x = age)) + stat_bin(aes(n = nrow(h3), y = ..count.. / n), binwidth = 10)
  + scale_y_continuous(formatter = "percent", breaks = c(0, 0.1, 0.2))
  + facet_grid(hospital ~ .)
  + theme(panel.background = theme_blank())

to produce

alt text

I'd like to change the facet labels, however, to something shorter (like Hosp 1, Hosp 2...) because they are too long now and look cramped (increasing the height of the graph is not an option, it would take too much space in the document). I looked at the facet_grid help page but cannot figure out how.

This question is related to r ggplot2 symbols facet plotmath

The answer is


This is working for me.

Define a factor:

hospitals.factor<- factor( c("H0","H1","H2") )

and use, in ggplot():

facet_grid( hospitals.factor[hospital] ~ . )

Here's how I did it with facet_grid(yfacet~xfacet) using ggplot2, version 2.2.1:

facet_grid(
    yfacet~xfacet,
    labeller = labeller(
        yfacet = c(`0` = "an y label", `1` = "another y label"),
        xfacet = c(`10` = "an x label", `20` = "another x label")
    )
)

Note that this does not contain a call to as_labeller() -- something that I struggled with for a while.

This approach is inspired by the last example on the help page Coerce to labeller function.


This solution is very close to what @domi has, but is designed to shorten the name by fetching first 4 letters and last number.

library(ggplot2)

# simulate some data
xy <- data.frame(hospital = rep(paste("Hospital #", 1:3, sep = ""), each = 30),
                 value = rnorm(90))

shortener <- function(string) {
  abb <- substr(string, start = 1, stop = 4) # fetch only first 4 strings
  num <- gsub("^.*(\\d{1})$", "\\1", string) # using regular expression, fetch last number
  out <- paste(abb, num) # put everything together
  out
}

ggplot(xy, aes(x = value)) +
  theme_bw() +
  geom_histogram() +
  facet_grid(hospital ~ ., labeller = labeller(hospital = shortener))

enter image description here


Here is a solution that avoids editing your data:

Say your plot is facetted by the group part of your dataframe, which has levels control, test1, test2, then create a list named by those values:

hospital_names <- list(
  'Hospital#1'="Some Hospital",
  'Hospital#2'="Another Hospital",
  'Hospital#3'="Hospital Number 3",
  'Hospital#4'="The Other Hospital"
)

Then create a 'labeller' function, and push it into your facet_grid call:

hospital_labeller <- function(variable,value){
  return(hospital_names[value])
}

ggplot(survey,aes(x=age)) + stat_bin(aes(n=nrow(h3),y=..count../n), binwidth=10)
 + facet_grid(hospital ~ ., labeller=hospital_labeller)
 ...

This uses the levels of the data frame to index the hospital_names list, returning the list values (the correct names).


Please note that this only works if you only have one faceting variable. If you have two facets, then your labeller function needs to return a different name vector for each facet. You can do this with something like :

plot_labeller <- function(variable,value){
  if (variable=='facet1') {
    return(facet1_names[value])
  } else {
    return(facet2_names[value])
  }
}

Where facet1_names and facet2_names are pre-defined lists of names indexed by the facet index names ('Hostpital#1', etc.).


Edit: The above method fails if you pass a variable/value combination that the labeller doesn't know. You can add a fail-safe for unknown variables like this:

plot_labeller <- function(variable,value){
  if (variable=='facet1') {
    return(facet1_names[value])
  } else if (variable=='facet2') {
    return(facet2_names[value])
  } else {
    return(as.character(value))
  }
}

Answer adapted from how to change strip.text labels in ggplot with facet and margin=TRUE


edit: WARNING: if you're using this method to facet by a character column, you may be getting incorrect labels. See this bug report. fixed in recent versions of ggplot2.


Note that this solution will not work nicely in case ggplot will show less factors than your variable actually contains (which could happen if you had been for example subsetting):

 library(ggplot2)
 labeli <- function(variable, value){
  names_li <- list("versicolor"="versi", "virginica"="virg")
  return(names_li[value])
 }

 dat <- subset(iris,Species!="setosa")
 ggplot(dat, aes(Petal.Length)) + stat_bin() + facet_grid(Species ~ ., labeller=labeli)

A simple solution (besides adding all unused factors in names_li, which can be tedious) is to drop the unused factors with droplevels(), either in the original dataset, or in the labbeler function, see:

labeli2 <- function(variable, value){
  value <- droplevels(value)
  names_li <- list("versicolor"="versi", "virginica"="virg")
  return(names_li[value])
}

dat <- subset(iris,Species!="setosa")
ggplot(dat, aes(Petal.Length)) + stat_bin() + facet_grid(Species ~ ., labeller=labeli2)

Both facet_wrap and facet_grid also accept input from ifelse as an argument. So if the variable used for faceting is logical, the solution is very simple:

facet_wrap(~ifelse(variable, "Label if true", "Label if false"))

If the variable has more categories, the ifelse statement needs to be nested.

As a side effect, this also allows the creation of the groups to be faceted within the ggplot call.


If you have two facets hospital and room but want to rename just one, you can use:

facet_grid( hospital ~ room, labeller = labeller(hospital = as_labeller(hospital_names)))

For renaming two facets using the vector-based approach (as in naught101's answer), you can do:

facet_grid( hospital ~ room, labeller = labeller(hospital = as_labeller(hospital_names),
                                                 room = as_labeller(room_names)))

I feel like I should add my answer to this because it took me quite long to make this work:

This answer is for you if:

  • you do not want to edit your original data
  • if you need expressions (bquote) in your labels and
  • if you want the flexibility of a separate labelling name-vector

I basically put the labels in a named vector so labels would not get confused or switched. The labeller expression could probably be simpler, but this at least works (improvements are very welcome). Note the ` (back quotes) to protect the facet-factor.

n <- 10
x <- seq(0, 300, length.out = n)

# I have my data in a "long" format
my_data <- data.frame(
  Type = as.factor(c(rep('dl/l', n), rep('alpha', n))),
  T = c(x, x),
  Value = c(x*0.1, sqrt(x))
)

# the label names as a named vector
type_names <- c(
  `nonsense` = "this is just here because it looks good",
  `dl/l` = Linear~Expansion~~Delta*L/L[Ref]~"="~"[%]", # bquote expression
  `alpha` = Linear~Expansion~Coefficient~~alpha~"="~"[1/K]"
  )


ggplot() + 
  geom_point(data = my_data, mapping = aes(T, Value)) + 
  facet_wrap(. ~ Type, scales="free_y", 
             labeller = label_bquote(.(as.expression(
               eval(parse(text = paste0('type_names', '$`', Type, '`')))
               )))) +
  labs(x="Temperature [K]", y="", colour = "") +
  theme(legend.position = 'none')

enter image description here


Have you tried changing the specific levels of your Hospital vector?

levels(survey$hospital)[levels(survey$hospital) == "Hospital #1"] <- "Hosp 1"
levels(survey$hospital)[levels(survey$hospital) == "Hospital #2"] <- "Hosp 2"
levels(survey$hospital)[levels(survey$hospital) == "Hospital #3"] <- "Hosp 3"

Simple solution (from here):

p <- ggplot(mtcars, aes(disp, drat)) + geom_point()
# Example (old labels)
p + facet_wrap(~am)


to_string <- as_labeller(c(`0` = "Zero", `1` = "One"))
# Example (New labels)
p + facet_wrap(~am, labeller = to_string)

Here's another solution that's in the spirit of the one given by @naught101, but simpler and also does not throw a warning on the latest version of ggplot2.

Basically, you first create a named character vector

hospital_names <- c(
                    `Hospital#1` = "Some Hospital",
                    `Hospital#2` = "Another Hospital",
                    `Hospital#3` = "Hospital Number 3",
                    `Hospital#4` = "The Other Hospital"
                    )

And then you use it as a labeller, just by modifying the last line of the code given by @naught101 to

... + facet_grid(hospital ~ ., labeller = as_labeller(hospital_names))

Hope this helps.


Adding another solution similar to @domi's with parsing mathematical symbols, superscript, subscript, parenthesis/bracket, .etc.

library(tidyverse)
theme_set(theme_bw(base_size = 18))

### create separate name vectors
# run `demo(plotmath)` for more examples of mathematical annotation in R
am_names <- c(
  `0` = "delta^{15}*N-NO[3]^-{}",
  `1` = "sqrt(x,y)"
)

# use `scriptstyle` to reduce the size of the parentheses &
# `bgroup` to make adding `)` possible 
cyl_names <- c(
  `4` = 'scriptstyle(bgroup("", a, ")"))~T~-~5*"%"',
  `6` = 'scriptstyle(bgroup("", b, ")"))~T~+~10~degree*C',
  `8` = 'scriptstyle(bgroup("", c, ")"))~T~+~30*"%"'
)

ggplot(mtcars, aes(wt, mpg)) + 
  geom_jitter() +
  facet_grid(am ~ cyl,
             labeller = labeller(am  = as_labeller(am_names,  label_parsed),
                                 cyl = as_labeller(cyl_names, label_parsed))
             ) +
  geom_text(x = 4, y = 25, size = 4, nudge_y = 1,
            parse = TRUE, check_overlap = TRUE,
            label = as.character(expression(paste("Log"["10"], bgroup("(", frac("x", "y"), ")")))))

### OR create new variables then assign labels directly
# reverse facet orders just for fun
mtcars <- mtcars %>% 
  mutate(am2  = factor(am,  labels = am_names),
         cyl2 = factor(cyl, labels = rev(cyl_names), levels = rev(attr(cyl_names, "names")))
  )

ggplot(mtcars, aes(wt, mpg)) + 
  geom_jitter() +
  facet_grid(am2 ~ cyl2,
             labeller = label_parsed) +
  annotate("text", x = 4, y = 30, size = 5,
           parse = TRUE, 
           label = as.character(expression(paste("speed [", m * s^{-1}, "]"))))

Created on 2019-03-30 by the reprex package (v0.2.1.9000)


After struggling for a while, what I found is that we can use fct_relevel() and fct_recode() from forcats in conjunction to change the order of the facets as well fix the facet labels. I am not sure if it's supported by design, but it works! Check out the plots below:

library(tidyverse)

before <- mpg %>%
  ggplot(aes(displ, hwy)) + 
  geom_point() +
  facet_wrap(~class)
before

after <- mpg %>%
  ggplot(aes(displ, hwy)) + 
  geom_point() + 
  facet_wrap(
    vars(
      # Change factor level name
      fct_recode(class, "motorbike" = "2seater") %>% 
        # Change factor level order
        fct_relevel("compact")
    )
  )
after

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


I think all other solutions are really helpful to do this, but there is yet another way.

I assume:

  • you have installed the dplyr package, which has the convenient mutate command, and
  • your dataset is named survey.

    survey %>% mutate(Hosp1 = Hospital1, Hosp2 = Hospital2,........)

This command helps you to rename columns, yet all other columns are kept.

Then do the same facet_wrap, you are fine now.


I have another way to achieve the same goal without changing the underlying data:

ggplot(transform(survey, survey = factor(survey,
        labels = c("Hosp 1", "Hosp 2", "Hosp 3", "Hosp 4"))), aes(x = age)) +
  stat_bin(aes(n = nrow(h3),y=..count../n), binwidth = 10) +
  scale_y_continuous(formatter = "percent", breaks = c(0, 0.1, 0.2)) +
  facet_grid(hospital ~ .) +
  opts(panel.background = theme_blank())

What I did above is changing the labels of the factor in the original data frame, and that is the only difference compared with your original code.


Just extending naught101's answer -- credit goes to him

plot_labeller <- function(variable,value, facetVar1='<name-of-1st-facetting-var>', var1NamesMapping=<pass-list-of-name-mappings-here>, facetVar2='', var2NamesMapping=list() )
{
  #print (variable)
  #print (value)
  if (variable==facetVar1) 
    {
      value <- as.character(value)
      return(var1NamesMapping[value])
    } 
  else if (variable==facetVar2) 
    {
      value <- as.character(value)
      return(var2NamesMapping[value])
    } 
  else 
    {
      return(as.character(value))
    }
}

What you have to do is create a list with name-to-name mapping

clusteringDistance_names <- list(
  '100'="100",
  '200'="200",
  '300'="300",
  '400'="400",
  '600'="500"
)

and redefine plot_labeller() with new default arguments:

plot_labeller <- function(variable,value, facetVar1='clusteringDistance', var1NamesMapping=clusteringDistance_names, facetVar2='', var1NamesMapping=list() )

And then:

ggplot() + 
  facet_grid(clusteringDistance ~ . , labeller=plot_labeller) 

Alternatively you can create a dedicated function for each of the label changes you want to have.


The EASIEST way to change WITHOUT modifying the underlying data is:

  1. Create an object using the as_labeller function adding the back tick mark for each of the default values:
# Necessary to put RH% into the facet labels
hum.names <- as_labeller(
     c(`50` = "RH% 50", `60` = "RH% 60",`70` = "RH% 70", 
       `80` = "RH% 80",`90` = "RH% 90", `100` = "RH% 100"))
  1. We add into the GGplot:
    ggplot(dataframe, aes(x = Temperature.C, y = fit)) + 
        geom_line() + 
        facet_wrap(~Humidity.RH., nrow = 2, labeller = hum.names)

The labeller function defintion with variable, value as arguments would not work for me. Also if you want to use expression you need to use lapply and can not simply use arr[val], as the argument to the function is a data.frame.

This code did work:

libary(latex2exp)
library(ggplot2)
arr <- list('virginica'=TeX("x_1"), "versicolor"=TeX("x_2"), "setosa"=TeX("x_3"))
mylabel <- function(val) { return(lapply(val, function(x) arr[x])) }
ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) + geom_line() + facet_wrap(~Species, labeller=mylabel)

Since I'm not yet allowed to comment on posts, I'm posting this separately as an addendum to Vince's answer and son520804's answer . Credit goes to them.

Son520804:

using Iris data:

I assume:
You have installed the dplyr package, which has the convenient mutate command, and your dataset is named survey. survey %>% mutate(Hosp1 = Hospital1, Hosp2 = Hospital2,........) This command helps you to rename columns, yet all other columns are kept. Then do the same facet_wrap, you are fine now.

Using the iris example of Vince and the partial code of son520804, I did this with the mutate function and achieved an easy solution without touching the original dataset. The trick is to create a stand-in name vector and use mutate() inside the pipe to correct the facet names temporarily:

i <- iris

levels(i$Species)
[1] "setosa"     "versicolor" "virginica"

new_names <- c(
  rep("Bristle-pointed iris", 50), 
  rep("Poison flag iris",50), 
  rep("Virginia iris", 50))

i %>% mutate(Species=new_names) %>% 
ggplot(aes(Petal.Length))+
    stat_bin()+
    facet_grid(Species ~ .)

In this example you can see the levels of i$Species is temporarily changed to corresponding common names contained in the new_names vector. The line containing

mutate(Species=new_names) %>%

can easily be removed to reveal the original naming.

Word of caution: This may easily introduce errors in names if the new_name vector is not correctly set up. It would probably be much cleaner to use a separate function to replace the variable strings. Keep in mind that the new_name vector may need to be repeated in different ways to match the order of your original dataset. Please double - and - triple check that this is correctly achieved.


Examples related to r

How to get AIC from Conway–Maxwell-Poisson regression via COM-poisson package in R? R : how to simply repeat a command? session not created: This version of ChromeDriver only supports Chrome version 74 error with ChromeDriver Chrome using Selenium How to show code but hide output in RMarkdown? remove kernel on jupyter notebook Function to calculate R2 (R-squared) in R Center Plot title in ggplot2 R ggplot2: stat_count() must not be used with a y aesthetic error in Bar graph R multiple conditions in if statement What does "The following object is masked from 'package:xxx'" mean?

Examples related to ggplot2

Center Plot title in ggplot2 R ggplot2: stat_count() must not be used with a y aesthetic error in Bar graph Saving a high resolution image in R Change bar plot colour in geom_bar with ggplot2 in r Remove legend ggplot 2.2 Remove all of x axis labels in ggplot Changing fonts in ggplot2 Explain ggplot2 warning: "Removed k rows containing missing values" Error: package or namespace load failed for ggplot2 and for data.table In R, dealing with Error: ggplot2 doesn't know how to deal with data of class numeric

Examples related to symbols

Enums in Javascript with ES6 HTML for the Pause symbol in audio and video control Undefined Symbols for architecture x86_64: Compiling problems Is there Unicode glyph Symbol to represent "Search" Best way to encode Degree Celsius symbol into web page? Placing Unicode character in CSS content value Regex to remove letters, symbols except numbers Meaning of *& and **& in C++ Reference — What does this symbol mean in PHP? How do you do the "therefore" (?) symbol on a Mac or in Textmate?

Examples related to facet

Setting individual axis limits with facet_wrap and scales = "free" in ggplot2 Fixing the order of facets in ggplot How to change facet labels?

Examples related to plotmath

How to change facet labels?