65

I have a basic bar graph I've created from ggplot2. The y variable contains both positive and negative values and about half the vector of values are negative. I would like to customize the axis labels such that when the y value of that corresponding x factor is a negative, its label is red. Here's a reproducible example:

#Create data
x <- c("a","b","c","d","e","f")
y <- c("10", "9","-10","11","-3","-15")
data <- data.frame(x, y)
data$y <- as.numeric(as.character(data$y))

data$category <- ifelse(as.numeric(data$y)<0, 0, 1)
data$category <- as.factor(data$category)

#Graph
library(cowplot) #theme
library(ggplot2)

ggplot(data, aes(x=x, y=y)) + 
  geom_bar(stat = "identity", aes(fill=category)) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  theme(axis.text.x = element_text(colour = "black"))

enter image description here

What I need is a way to change the label colors of "c", "e", and "f" to the color of my choosing. I tried toggling theme(aes(axis.text.x=element_text(colour=Air_pricier))) but that produced an error.

starball
  • 20,030
  • 7
  • 43
  • 238
Cyrus Mohammadian
  • 4,982
  • 6
  • 33
  • 62

3 Answers3

112

You can provide a vector of colors to the axis.text.x option of theme():

a <- ifelse(data$category == 0, "red", "blue")

ggplot(data, aes(x = x, y = y)) + 
    geom_bar(stat = "identity", aes(fill = category)) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1, colour = a))

enter image description here

Sumedh
  • 4,835
  • 2
  • 17
  • 32
  • 3
    Is there a more automatic manner to set the axis label color to match the legend colors? This is especially needed when there are more than 2 categories that are not easily defined from each other except by name. – andemexoax Mar 07 '19 at 19:10
  • 1
    @andemexoax use `mystrings <- c("vector","of","categories")` then `a <- ifelse(data$category %in% mystrings, "red", "blue")` – Justapigeon Aug 06 '19 at 23:11
  • '@Sumedh' what if I want ONLY the label to be set in different color? – mjs Jan 16 '20 at 11:23
  • 14
    This is nice, though generates a warning message for me `Warning message: Vectorized input to "element_text()" is not officially supported. Results may be unexpected or may change in future versions of ggplot2. ` – Mark Neal May 06 '20 at 04:53
  • I also tried this in combination with `facet_wrap`, and it looks like it has to be the same colour scheme across all facets; i.e. you can't have different colour sets on different facets. – S. Robinson Jan 31 '22 at 21:46
  • 3
    The warning message `Warning message: Vectorized input to "element_text()" is not officially supported. Results may be unexpected or may change in future versions of ggplot2.` can be avoided by using `ggtext::element_markdown` instead of `element_text` which officially supports vectorized input. – Matthias Munz Dec 19 '22 at 14:19
24

I, too, get the warning message mentioned in @Mark Neal's comment; it makes me nervous. Here's an alternative approach with the ggtext package. You can wrap the categories for the x-axis in <span>s and specify the color you want, and then use element_markdown in the theme:

library(ggtext)
library(tidyverse)

data %>%
  mutate(x.label = paste("<span style = 'color: ",
                         ifelse(y > 0, "black", "red"),
                         ";'>",
                         x,
                         "</span>", sep = ""),
         x.label = fct_reorder(x.label, as.character(x))) %>%
  ggplot(aes(x=x.label, y=y)) + 
  geom_bar(stat = "identity", aes(fill=category)) +
  theme(axis.text.x = element_markdown(angle = 45, hjust = 1))

enter image description here

A. S. K.
  • 2,504
  • 13
  • 22
  • Is there any other alternative using the ggplot2 standard verbs (without using an extension such as ggtext)? – A. Kassambara Dec 09 '20 at 06:54
  • Not that I've been able to find. But if there's a standard `ggplot2` solution, I would love to use that instead! – A. S. K. Dec 09 '20 at 16:02
  • 2
    This works nicely. you can also define this in the labels of `scale_x_discrete` so you don't have to change the x-values in your data. You can also create the labels in the discrete scale with glue templates for maximum flexibility and convenience. – snaut Nov 03 '21 at 15:36
6

Building on a-s-k's answer I put this in a more flexible form using glue templates and a discrete scale. With this option you don't have to change your data but just define a labeler in the scale that does everything for you, this is handy if you want to color the x-axis in many similar plots with different data.

(In the case of the original question the color depends on more of the data, than just the x-values, but I guess this could still be handy for some users.)

library(ggtext)
library(tidyverse)
library(glue)

#Create data
x <- c("a","b","c","d","e","f")
y <- c("10", "9","-10","11","-3","-15")
data <- data.frame(x, y)
data$y <- as.numeric(as.character(data$y))

data$category <- ifelse(as.numeric(data$y)<0, 0, 1)
data$category <- as.factor(data$category)

# create the labels 
my_labels <- glue_data(
  data, 
  "<span style='color: {if_else(category==0, 'red', 'blue')}'>{x}</span>"
  )
names(my_labels) <- data$x

# plot as you normally would
# use element_markdown as axis.text.x
# and the labels defined before as labels in a discrete scale
ggplot(data, aes(x=x, y=y)) + 
  geom_bar(stat = "identity", aes(fill=category)) +
  theme(
    axis.text.x = element_markdown(angle = 45, hjust = 1)
  ) + 
  scale_x_discrete(labels=my_labels)
snaut
  • 2,261
  • 18
  • 37