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!
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))
Data used
df <- iris
df$Sepal.Length <- df$Sepal.Length * 100
df$Petal.Width <- df$Petal.Width * 1000
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)
Related
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!
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))
Data used
df <- iris
df$Sepal.Length <- df$Sepal.Length * 100
df$Petal.Width <- df$Petal.Width * 1000
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)
I would like to plot 3 graphics beside each other via the ggplot2 and gridExtra packages. The graphic on the left side has a ylab, the other 2 graphics do not. All three graphics should have the same size and the space between the graphics should be reduced as much as possible. However, due to the ylab of the graphic on the left side, I am either not able to reduce the space as much as I want; or I am cutting off the ylab.
Consider the following example in R:
library("ggplot2")
library("gridExtra")
# Example data
df <- data.frame(x = 1:10,
y = 1:10)
# Plots
ggp1 <- ggplot(df, aes(x, y)) +
geom_line() + theme_bw() +
ylab("Here is the ylab")
ggp2 <- ggplot(df, aes(x, y)) +
geom_line() + theme_bw() +
ylab("")
ggp3 <- ggplot(df, aes(x, y)) +
geom_line() + theme_bw() +
ylab("")
# Arrange grids
grid.arrange(ggp1, ggp2, ggp3, ncol = 3)
The space between the graphics should be reduced as much as possible.
All graphics should have the same size.
The ylab of the graphic on the left side should be kept.
I was trying to fix the problem with plot.margin, but unfortunately that didn't work.
I would suggest to cbind() the gtables, with the axis removed. null units automatically ensure equal panel widths.
lg <- lapply(list(ggp1,ggp2,ggp3),ggplotGrob)
rm_axis <- function(g){
lay <- g[["layout"]]
cp <- lay[lay$name == "panel",]
g[,-c(1:(cp$l-1))]
}
lg[-1] <- lapply(lg[-1], rm_axis)
grid::grid.draw(do.call(gtable_cbind, lg))
Adding theme (axis.title.y = element_blank()) to ggp2 and ggp3 will reduce the space between them.
library("ggplot2")
library("gridExtra")
# Example data
df <- data.frame(x = 1:10,
y = 1:10)
# Plots
ggp1 <- ggplot(df, aes(x, y)) +
geom_line() + theme_bw() +
ylab("Here is the ylab")
ggp2 <- ggplot(df, aes(x, y)) +
geom_line() + theme_bw() +
ylab("") + theme (axis.title.y = element_blank())
ggp3 <- ggplot(df, aes(x, y)) +
geom_line() + theme_bw() +
ylab("") + theme (axis.title.y = element_blank())
# Arrange grids
grid.arrange(ggp1, ggp2, ggp3, ncol = 3)
I would like to combine some graphs together using cowplot. But I cannot change the margin sizes. I want to use only one y-axes, but than the margin is still quite large, which I want to decrease. I have used the plot.margin code from ggplot, although that works when I look at the single plot, it doesn't seem to work when the plots are combined.
I have made some example code:
library(ggplot2)
library(cowplot)
x <- c("a", "b")
y1 <- c(3,6)
y2 <- c(10,15)
data1 <- data.frame(x,y1)
data2 <- data.frame(x, y2)
ylab1 <- ylab("Very nice y values")
xlab1 <- xlab("Very nice factors")
plot1 <- ggplot(data1, aes(x=x, y = y1)) +
geom_bar(stat ="identity", position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0.5,0.5,0.5,0.5), "cm")) + xlab1 + ylab1
plot1
ylab2 <- ylab("")
xlab2 <- xlab("Very nice factors")
plot2 <- ggplot(data2, aes(x=x, y = y2)) +
geom_bar(stat = "identity",position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0.5,0.5,0.5,-0.5), "cm")) + xlab2 + ylab2
plot2
plot3 <- plot_grid(plot1, plot2, labels = c("A", "B"), align = "hv",nrow = 1, ncol = 2)
plot3 # Quite large margin between the two plots
I am aware that I could avoid this problem by using facets, however my real plot is rather more complicated than this graph.
Increasing the space between plots in plot_grid was also addressed in this issue.
An extra interesting solution is the one suggested in this comment - try to add an extra empty plot between the two plots and adjust the relative columns widths:
plot4 <- plot_grid(plot1, NULL, plot2, rel_widths = c(1, 0, 1), align = "hv",
labels = c("A", "B"), nrow = 1)
plot4
Can even try negative values in rel_widths, which gives better results:
plot5 <- plot_grid(plot1, NULL, plot2, rel_widths = c(1, -0.1, 1), align = "hv",
labels = c("A", "B"), nrow = 1)
plot5
So, try a combination of adjusting the plot.margin (as answered by #J.Con) and adding an extra empty plot with tweaking rel_widths.
EDIT 2019-12-11
Also check out this comment of the author of cowplot (Claus Wilke):
For those kinds of problems I would now recommend the patchwork library. It's inherently difficult with plot_grid(), due to its underlying design
So, a fast example with patchwork based on their vignette Adding Annotation and Style goes like this:
library(patchwork)
plot3 <- plot1 + plot2 +
plot_annotation(tag_levels = 'A') &
theme(plot.tag = element_text(size = 8))
plot3
Created on 2019-12-11 by the reprex package (v0.3.0)
Your plot.margins were actually working against you. Set them to zero to fill up that white space.
plot1 <- ggplot(data1, aes(x=x, y = y1)) +
geom_bar(stat ="identity", position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0,0,0,0), "cm")) + xlab1 + ylab1
plot1
ylab2 <- ylab("")
xlab2 <- xlab("Very nice factors")
plot2 <- ggplot(data2, aes(x=x, y = y2)) +
geom_bar(stat = "identity",position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0,0,0,0), "cm")) + xlab2 + ylab2
plot2
plot3 <- plot_grid(plot1, plot2, labels = c("A", "B"), align = "hv",nrow = 1, ncol = 2)
plot3
The geom_line CUR_MTH_UNEARN_REV_EUR is plotted correctly as a numeric. My goal is to add a second numeric geom_line (i.e., CUR_MTH_EARN_REV_EUR). Here's the code:
library("ggthemes")
library("gridExtra")
library("grid")
p = ggplot(f, aes(DTE_OF_REPORT_EUR, CUR_MTH_UNEARN_REV_EUR, label=(CUR_MTH_UNEARN_REV_EUR)))
+ geom_point(size=ifelse(f$CUR_MTH_UNEARN_REV_EUR<8.0, 11, 5), color=ifelse(f$CUR_MTH_UNEARN_REV_EUR<8.0, '#CC0000', 'black'))
+ geom_line(size=2,aes(group=1)) + geom_rangeframe() + theme_wsj()
+ theme(axis.text.x=element_text(angle=50, size=20, vjust=0.7))
+ geom_smooth(aes(group=1), method="loess", colour = "#CC0000", lwd=2)
+ geom_text(aes(label=CUR_MTH_UNEARN_REV_EUR), hjust=-0.5, vjust=0.5, fontface="bold")
+ ggtitle("Unearned Revenue by Service Code 'BS', in CSG Months, Jul. 2014-Aug. 2015")
+ theme(plot.title = element_text(lineheight=.8, face="bold"))
p
Text1 = textGrob("Source: Revenue Assurance and Quality Control", gp=gpar(fontsize=7))
p2 = p + annotation_custom(grob = Text1, ymin = -0.2, ymax = -30)
p2
format(round(f$CUR_MTH_UNEARN_REV_EUR, 2), nsmall = 2)
f$ScoreRounded <- round(f$CUR_MTH_UNEARN_REV_EUR, 1)
f$DTE_OF_REPORT_EUR <- factor(f$DTE_OF_REPORT_EUR, levels=unique(as.character(f$DTE_OF_REPORT_EUR)))
Hope this helps as a start. You can just add things, but you need to have the correct aes.
#data with x and two y-variables
set.seed(123)
f <- data.frame(x=1:10, var1=sample(7:10,10,T),
var2=sample(5:7,10,T))
#as you want sizing by a measure, make a flag
f$var1_threshold <- f$var1 <9
#example with adding different geoms
#not that it's unnecessary to use my data (f)
#in the call to aes, as everything I need is already
#inside f
p <- ggplot(f, aes(x=x)) +
geom_point(aes(y=var1,size=var1_threshold, color=var1_threshold))+
#colors and size in aes allows for legend generation.
scale_size_manual(values=c("FALSE"=5,"TRUE"=8)) +
scale_color_manual(values=c("FALSE"='#CC0000',"TRUE"='black')) +
geom_line(aes(y=var1),size=1) +
geom_line(aes(y=var2),size=1) +
geom_smooth(aes(y=var1), colour="#CC0000") +
geom_smooth(aes(y=var2), colour="black")
I realize that the align.plots function from the ggExtra package has been deprecated and removed. However, I am using my own version as it seems to provide the specific functionality I need. I have looked into faceting to solve my problem but I don't think it will work for my particular issue. What seems to be the problem is that the top-to-bottom images don't align when I use coord_equal on one of them. This doesn't seem to affect left-to-right though. Here is a simplified (or at least as simple as I can make it) version of what I am trying to achieve.
Create some dummy data frames:
source('https://raw.github.com/jbryer/multilevelPSA/master/r/align.R')
require(psych)
df = data.frame(x=rnorm(100, mean=50, sd=10),
y=rnorm(100, mean=48, sd=10),
group=rep(letters[1:10], 10))
dfx = describe.by(df$x, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfx) = c('group', 'x', 'x.n', 'x.min', 'x.max')
dfy = describe.by(df$y, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfy) = c('group', 'y', 'y.n', 'y.min', 'y.max')
df2 = cbind(dfx, dfy[,2:ncol(dfy)])
range = c(0,100)
This will setup the three plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
opts(legend.position='none') +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1)
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + opts(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + opts(legend.position='none')
The alignment top to bottom does not work with coord_equal
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Broken Plot http://bryer.org/alignplots1.png
The fix is to add respect=TRUE to the grid.layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)
But if I don't use coord_equal the alignment works fine:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1a, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Working Plot http://bryer.org/alignplots2.png
ggplot2 now has ggplotGrob(), which may help with this.
First, we need to update the code used to generate the plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1) + theme_minimal() + theme(legend.position='none')
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p4 <- ggplot(df, aes(x = group, y = y)) +
geom_blank() +
theme(line = element_blank(),
rect = element_blank(),
text = element_blank(),
title = element_blank())
p4 will be blank; we just need the grob to draw.
Then we load the grid package and draw the grobs in a list arranged in rows and columns using cbind() and rbind().
library(grid)
grid.newpage()
grid.draw(
cbind(
rbind(ggplotGrob(p3), ggplotGrob(p4), size = "first"),
rbind(ggplotGrob(p1), ggplotGrob(p2), size = "first"),
size = "first"))
I'm not sure if this method will let you plot p3 in a different width and p2 in a different height, as you have them in the original example; I normally need a grid of similarly-sized graphs, and haven't needed to figure out different sizes.
This answer is partially based on partially based on https://stackoverflow.com/a/17463184/393354
Here is an example:
m <- matrix(c(3, 1, 0, 2), 2, byrow = T)
lay <- gglayout(m, widths = c(1, 3), heights = c(3, 1))
ggtable(p1, p2, p3, layout = lay)
you can use this by
install.packages('devtools')
library(devtools)
dev_mode()
install_github("ggplot2", "kohske", "cutting-edge")
library(ggplot2)
note that this branch is experimental, so maybe there are bugs.
To solve the problem using the align.plots method, specify respect=TRUE on the layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)