ChatGPT解决这个技术问题 Extra ChatGPT

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

https://imgur.com/lXK7C.png

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.

Most answers are very verbose. I found a simple answer (community.rstudio.com/t/changing-sep-in-labeller/7369/2), and made an example with it. See down below.

A
Axeman

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.


Nice, but will not work with facet_wrap, whereas @Vince solution will work with facet_wrap too.
@ArnaudAmzallag: Correct, though if someone feels like donating some time, it could in the future.
Added a fail-safe for unknown facetting variables.
Notice: This does not work in ggplot2 v.2 - the labeller function has changed. @mbirons answer works stackoverflow.com/a/34811062/162832
Interesting, but this doesn't always work, whereas editing the factors always does.
m
mbiron

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.


Which verison of ggplot2 is as_labeller in? I have found some source code for on the CRAN GitHub repository, but after upgrading to the latest version (on CRAN!) I don't seem to have the function.
This is cool. What happens when you have two variables in your facet grid though? Like hospital ~ gender or something? Is there a way to use labellers on both axes? I can't see anything obvious in the docs.
Note if you started with naught's answer, this one only works with a c() not a list().
One great part of this is that this works with both axes of the facet grid!
an answer to @naught101 's question would be the answer by domi : stackoverflow.com/a/37778906/8124725 Without this addition, this doesn't work for me, yielding NA's for the variable that I did not include.
V
Vince

Change the underlying factor level names with something like:

# Using the Iris data
> i <- iris
> levels(i$Species)
[1] "setosa"     "versicolor" "virginica" 
> levels(i$Species) <- c("S", "Ve", "Vi")
> ggplot(i, aes(Petal.Length)) + stat_bin() + facet_grid(Species ~ .)

@wishihadabettername: To avoid changing underlying data, you can use: ggplot(transform(iris, Species = c("S", "Ve", "Vi")[as.numeric(Species)]), aes(Petal.Length)) + stat_bin() + facet_grid(Species ~ .)
related... if you want the panel label to be a bquote() expression (e.g., levels(x$measurements) <- c(bquote(Area ~~ (cm^2)), bquote(Length ~~ (cm)))) it will not appear in mathematical expression. How would one show expressions as facet labels?
related for including expressions in the facet label, use labeller option to facet_grid: stackoverflow.com/questions/37089052/…
J
J_F

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 works!!! I wasn't able to apply the other solutions because some of the suggested solutions were deprecated on current ggplot2 versions.
You can construct these named vectors with setNames() stackoverflow.com/a/22428439/3362993
R
Rich Pauloo

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

Create an object using as_labeller(). If the column names begin with a number or contain spaces or special characters, don't forget to use back tick marks:

# 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"))

Add to the ggplot:

    ggplot(dataframe, aes(x = Temperature.C, y = fit)) + 
        geom_line() + 
        facet_wrap(~Humidity.RH., nrow = 2, labeller = hum_names)

This I think is the most elegant method -- it is effective and works with ggplot2 version 3.0.0.9000
but it does not work when there are two facets, e.g., type~Humidity
@DenisCousineau in that case use labeller = labeller(Type = c(...), Humidity = c(...)) where the ... are the key value pairs
Also I'd note that if you are just prefixing everything with RH%, a more robust solution would be to replace step 1 in this answer with hum_names <- as_labeller(function(x) paste('RH%', x))
d
domi

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

This gives NAs for all the labels for me :(
T
Tung

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"), ")")))))

https://i.imgur.com/ccGQ9Yv.png

### 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}, "]"))))

https://i.imgur.com/Jpvowh8.png

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


N
Nick

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)

R
Roman Luštrik

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

https://i.stack.imgur.com/MttJJ.png


M
Matifou

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)

l
lillemets

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.


D
Donald Duck

This is working for me.

Define a factor:

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

and use, in ggplot():

facet_grid( hospitals.factor[hospital] ~ . )

r
reox

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)

A
Alexander Kielland

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.


It may be a bit nicer to use: new_names <- c('setosa' = 'Bristle-pointed iris', 'versicolor' = 'Poison flag iris', 'virginica' = 'Virginia iris') and then in the mutate you could create a new column thusly: mutate(Spec = new_names[Species])
u
user4786271

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.


y
ytu

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.


j
jwpfox

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.


sorry, it does not work as it also changes the column content
This is incorrect, as: 1. the different Hosp1, Hosp2... variables do not exist. The original question uses one single column called "hospital" in which the strings are contained 2. even if you had different columns, your command would look for objects called Hospital1, Hospital2, etc. and would throw an error because they don't exist. 3. as @Jens said, if you used strings instead, i.e. "Hospital1", it would fill the whole column with that value. You might be looking for a mutate() combined with a case_when()? Not sure why this was upvoted as it definitely would not work.
d
dani

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')

https://i.stack.imgur.com/ZAGeQ.png


p
philiporlando

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"

S
Samuel Saari

A one liner from mishabalyasin :

facet_grid(.~vs, labeller = purrr::partial(label_both, sep = " #"))

See it in action

library(reprex)
library(tidyverse)

mtcars %>% 
  ggplot(aes(x="", y=gear,fill=factor(gear), group=am)) +
  geom_bar(stat="identity", width=1) +
  coord_polar("y", start=0) +
  facet_grid(.~vs, labeller = purrr::partial(label_both, sep = " #"))

https://i.imgur.com/KxEaEm2.png

Created on 2021-07-09 by the reprex package (v2.0.0)


J
Juan Pablo Carvallo

My approach to this issue these days is to use dplyr::case_when to produce a labeler within the facet_grid or facet_wrap function. This is an extension of the solution proposed by @lillemets

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(case_when(hospital == "Hospital #1" ~ "Hosp1",
                         hospital == "Hospital #2" ~ "Hosp2") ~ .)
  + theme(panel.background = theme_blank())

What's nice is that if you have a second facet label to change you just use the same approach on the other side of the ~ within facet_grid


A
Ashirwad

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

https://i.imgur.com/AEB5g9p.png

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

https://i.imgur.com/8ZO8PWN.png

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