3

Is there a possibility to add a third y-axis to a plot with ggplot2? I have three different datasources I want to display in the plot. I already added a second y-axis, for the next dataset the scale is again very different, why I'm looking now for a solution.

So far I only found how to add a second axis, for example as shown [here].(https://r-graph-gallery.com/line-chart-dual-Y-axis-ggplot2.html) But I whether there is an possibility to add another one... Thank you!

1
  • @sohp, could you please share your data using dput(data)? So we can help you better Commented Mar 26, 2022 at 18:31

3 Answers 3

8

This is equally clunky, but shows how it can be done from scratch using only CRAN resources.

library(cowplot)
library(patchwork)

p1 <- ggplot(df, aes(Sepal.Width, Sepal.Length)) + 
                   geom_point() + theme(axis.line = element_line())
p2 <- ggplot(df, aes(Sepal.Width, Petal.Width)) + geom_point() +
                   theme(axis.line = element_line())

p3 <- ggplot(df, aes(Sepal.Width, Petal.Length)) + 
  geom_point(aes(color = "Petal.Length")) +
  geom_point(aes(y = Sepal.Length/100, color = "Sepal.Length")) +
  geom_point(aes(y = Petal.Width / 1000, color = "Petal.Width")) +
  theme(axis.line = element_line(),
        plot.margin = margin(10, 10, 10, 30))

wrap_elements(get_plot_component(p1, "ylab-l")) +
  wrap_elements(get_y_axis(p1)) +
  wrap_elements(get_plot_component(p2, "ylab-l")) +
  wrap_elements(get_y_axis(p2)) +
  p3 + 
  plot_layout(widths = c(3, 1, 3, 1, 40))

enter image description here


Data used

df <- iris
df$Sepal.Length <- df$Sepal.Length * 100
df$Petal.Width <- df$Petal.Width * 1000
Sign up to request clarification or add additional context in comments.

6 Comments

Very nice! Extra credit for coloring the axes based on the points' colors :-)
@soph this is just an example using the built-in iris data set. You will have to adapt the code to your own data. You didn't show us your own data, so I have no way to know what your variables are called. If you want code that works with your own data, you need to share it with us. You can edit your question to include the output of dput(df) (where df is the name of your own data frame.
Is it possible for you to share the datasource? So that one can better understand what's going on?
'if something's not worth doing, it's not worth doing well!' - I think you did very well here ;)
@tjebo that made me chuckle. I should have checked my own posts! This does seem to be a bit easier than drawing the axes 'manually'.
|
5

This is a very clunky solution based on extracting elements from previously plotted graphs and editing grid objects. It may or not give you a workable solution.

source("https://raw.githubusercontent.com/davidearn/plague_growth/master/analysis/plots/3axes.R")
set.seed(101)
dd <- data.frame(x=rnorm(20),y=rnorm(20))
library(ggplot2)
gg0 <- ggplot(dd)
g1A <- gg0 + geom_point(aes(x,y))
g1B <- gg0 + geom_point(aes(x,10*y))
g1C <- gg0 + geom_point(aes(x,100*y))
## use return_gtable = TRUE if planning to add further axes
g2 <- combine_axes(g1A,g1B,add_pos="l", return_gtable=TRUE)
g3 <- combine_axes(g2,g1C,add_pos="l")
print(g3)

ugly graph with triple left axes

2 Comments

So combine_axes function does not work for facet plots and adding axes to the bottom?
probably not ...
1

There is a solution that doesn’t make another axis but manipulates the labels on the second axis as HTML using the ggtext package.

First you find the coefficients to rebase the other 2 variables.

## Packages
library(ggtext);library(tidyverse)

## Data
df <- data.frame(category = paste0(rep("type_", 30), c("A", "B", "C")),
                 x_val = rep(1:10, 3), y_val = abs(rnorm(30))*c(100,10,1))
## Rebase
rebase_coef <- df %>% group_by(category) %>% summarise(rebase_coef = max(y_val)) %>%
  ungroup() %>% mutate(rebase_coef = rebase_coef / max(rebase_coef))

df_rebase <- df %>% 
  mutate(y_val = case_when(category == "type_B" ~ y_val/rebase_coef$rebase_coef[2], 
                           category == "type_C" ~ y_val/rebase_coef$rebase_coef[3],
                           TRUE ~ y_val))

Then, create a data frame that will make your labels from a vector that will e used as the primary axis breaks (I use a function that easily converts labels into HTML):

## Y Axis Breaks
vbreaks <- max(df$y_val)*c(1, 0.75, 0.5, 0.25, 0)

## Category colors
vcolor_cat <- c("#0077B7","#8B4500","#4DAF4A"); names(vcolor_cat) <- unique(df$category)

## HTML function
html_fun <- function(x, color = 'white', size = 12) {
  paste0("<span style = 'color:", color,";font-size:", size, "pt'><b>", x, "</b></span>")
}

## 2nd Labels
axis_labs <- data.frame(y_A = vbreaks, y_B = vbreaks/10, y_C = vbreaks/100) %>%
  mutate(ylab_txt = paste0(html_fun(round(y_B,2), color = vcolor_cat[2]), "<br>", 
                           html_fun(round(y_C,2),  color = vcolor_cat[3]) ))

Then you plot:

df_rebase %>% ggplot(aes(x = x_val, y = y_val, color = category)) + geom_point() +
  scale_color_manual(values = vcolor_cat) + 
  scale_y_continuous(breaks = vbreaks, labels = function(x) round(x, 2), name = "1 Thing", 
                     sec.axis = sec_axis(~.*1, labels = axis_labs$ylab_txt, breaks = vbreaks, name = "2 Things")) +
  theme(
    axis.title.y.left = element_textbox_simple(
      hjust = 0.5, vjust = 0.5, halign = 0.5, valign = 0.5,
      orientation = "left-rotated", width = 0.75, fill = "grey90",
      padding = margin(4, 2, 3, 2), margin = margin(2, 2, 2, 0)),
    axis.title.y.right = element_textbox_simple(
      hjust = 0.5, vjust = 0.5, halign = 0.5, valign = 0.5,
      orientation = "right-rotated", width = 0.75, fill = "grey90",
      padding = margin(4, 2, 3, 2), margin = margin(2, 0, 2, 2)),
    axis.text.y.left = element_markdown(
      colour = vcol_metric[1], fill = 'transparent', size = 12, face = "bold",
      hjust = 1, vjust = 0.5, halign = 1, valign = 0.5, lineheight = 0.75,
      padding = ggplot2::margin(3, 1, 1, 1), margin = ggplot2::margin(0, 0, 0, 0),
      align_widths = FALSE, align_heights = FALSE
    ),
    axis.text.y.right = element_markdown(
      fill = 'transparent', hjust = 0, vjust = 0.5, halign = 0, valign = 0.5, lineheight = 0.75,
      padding = ggplot2::margin(3, 1, 1, 1), margin = ggplot2::margin(0, 0, 0, 0),
      align_widths = FALSE, align_heights = FALSE
    ))

3 Y-Axis Plot

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.