I would like to remove the redundancy of strip labels when using facet_wrap() and faceting with two variables and both scales free.
For example, this facet_wrap version of the following graph
library(ggplot2)
dt <- txhousing[txhousing$year %in% 2000:2002 & txhousing$month %in% 1:3,]
ggplot(dt, aes(median, sales)) +
geom_point() +
facet_wrap(c("year", "month"),
labeller = "label_both",
scales = "free")
should have the looks of this facet_grid version of it, where the strip labels are at the top and right edge of the graph (could be bottom and left edge as well).
ggplot(dt, aes(median, sales)) +
geom_point() +
facet_grid(c("year", "month"),
labeller = "label_both",
scales = "free")
Unfortunately, using facet_grid is not an option because, as far as I understand, it doesn't allow scales to be "completely free" - see here or here
One attempt that I thought about would be to produce separate plots and then combine them:
library(cowplot)
theme_set(theme_gray())
p1 <- ggplot(dt[dt$year == 2000,], aes(median, sales)) +
geom_point() +
facet_wrap("month", scales = "free") +
labs(y = "2000") +
theme(axis.title.x = element_blank())
p2 <- ggplot(dt[dt$year == 2001,], aes(median, sales)) +
geom_point() +
facet_wrap("month", scales = "free") +
labs(y = "2001") +
theme(strip.background = element_blank(),
strip.text.x = element_blank(),
axis.title.x = element_blank())
p3 <- ggplot(dt[dt$year == 2002,], aes(median, sales)) +
geom_point() +
facet_wrap("month", scales = "free") +
labs(y = "2002") +
theme(strip.background = element_blank(),
strip.text.x = element_blank())
plot_grid(p1, p2, p3, nrow = 3)
I am ok with the above hackish attempt, but I wonder if there is something in facet_wrap that could allow the desired output. I feel that I miss something obvious about it and maybe my search for an answer didn't include the proper key words (I have the feeling that this question was addressed before).
This does not seem easy, but one way is to use grid graphics to insert panel strips from a facet_grid plot into one created as a facet_wrap. Something like this:
First lets create two plots using facet_grid and facet_wrap.
dt <- txhousing[txhousing$year %in% 2000:2002 & txhousing$month %in% 1:3,]
g1 = ggplot(dt, aes(median, sales)) +
geom_point() +
facet_wrap(c("year", "month"), scales = "free") +
theme(strip.background = element_blank(),
strip.text = element_blank())
g2 = ggplot(dt, aes(median, sales)) +
geom_point() +
facet_grid(c("year", "month"), scales = "free")
Now we can fairly easily replace the top facet strips of g1 with those from g2
library(grid)
library(gtable)
gt1 = ggplot_gtable(ggplot_build(g1))
gt2 = ggplot_gtable(ggplot_build(g2))
gt1$grobs[grep('strip-t.+1$', gt1$layout$name)] = gt2$grobs[grep('strip-t', gt2$layout$name)]
grid.draw(gt1)
Adding the right hand panel strips need us to first add a new column in the grid layout, then paste the relevant strip grobs into it:
gt.side1 = gtable_filter(gt2, 'strip-r-1')
gt.side2 = gtable_filter(gt2, 'strip-r-2')
gt.side3 = gtable_filter(gt2, 'strip-r-3')
gt1 = gtable_add_cols(gt1, widths=gt.side1$widths[1], pos = -1)
gt1 = gtable_add_grob(gt1, zeroGrob(), t = 1, l = ncol(gt1), b=nrow(gt1))
panel_id <- gt1$layout[grep('panel-.+1$', gt1$layout$name),]
gt1 = gtable_add_grob(gt1, gt.side1, t = panel_id$t[1], l = ncol(gt1))
gt1 = gtable_add_grob(gt1, gt.side2, t = panel_id$t[2], l = ncol(gt1))
gt1 = gtable_add_grob(gt1, gt.side3, t = panel_id$t[3], l = ncol(gt1))
grid.newpage()
grid.draw(gt1)
I am not sure you can do this by just using facet_wrap, so probably your attempt is the way to go. But IMO it needs an improvement. Presently, you are missing actual y-lab (sales) and it kinda misguides what is plotted in y- axis
You could improve what you are doing by adding another plot title row by using gtable and grid.
p1 <- ggplot(dt[dt$year == 2000,], aes(median, sales)) +
geom_point() +
facet_wrap("month", scales = "free") +
theme(axis.title.x = element_blank())
p2 <- ggplot(dt[dt$year == 2001,], aes(median, sales)) +
geom_point() +
facet_wrap("month", scales = "free") +
theme(axis.title.x = element_blank())
p3 <- ggplot(dt[dt$year == 2002,], aes(median, sales)) +
geom_point() +
facet_wrap("month", scales = "free")
Note that the labs are removed from the above plots.
if ( !require(grid) ) { install.packages("grid"); library(grid) }
if ( !require(gtable ) ) { install.packages("gtable"); library(gtable) }
z1 <- ggplotGrob(p1) # Generate a ggplot2 plot grob
z1 <- gtable_add_rows(z1, unit(0.6, 'cm'), 2) # add new rows in specified position
z1 <- gtable_add_grob(z1,
list(rectGrob(gp = gpar(col = NA, fill = gray(0.7))),
textGrob("2000", gp = gpar(col = "black",cex=0.9))),
t=2, l=4, b=3, r=13, name = paste(runif(2))) #add grobs into the table
Note that in step 3, getting the exact values for t (top extent), l(left extent), b (bottom extent) and r(right extent) might need trial and error method
Now repeat the above steps for p2 and p3
z2 <- ggplotGrob(p2)
z2 <- gtable_add_rows(z2, unit(0.6, 'cm'), 2)
z2 <- gtable_add_grob(z2,
list(rectGrob(gp = gpar(col = NA, fill = gray(0.7))),
textGrob("2001", gp = gpar(col = "black",cex=0.9))),
t=2, l=4, b=3, r=13, name = paste(runif(2)))
z3 <- ggplotGrob(p3)
z3 <- gtable_add_rows(z3, unit(0.6, 'cm'), 2)
z3 <- gtable_add_grob(z3,
list(rectGrob(gp = gpar(col = NA, fill = gray(0.7))),
textGrob("2002", gp = gpar(col = "black",cex=0.9))),
t=2, l=4, b=3, r=13, name = paste(runif(2)))
finally, plotting
plot_grid(z1, z2, z3, nrow = 3)
You can also have the years indicated in the column like in facet_grid instead of row. In that case, you have to add a column by using gtable_add_cols. But make sure to (a) add the column at the correct position in step-2, and (b) get the correct values for t, l, b and r in step-3.
You can achieve that using facet_grid2 function of ggh4x package like
library(ggplot2)
library(ggh4x)
dt <- txhousing[txhousing$year %in% 2000:2002 & txhousing$month %in% 1:3,]
ggplot(dt, aes(median, sales)) +
geom_point() +
facet_grid2(c("year", "month"),
labeller = "label_both",
scales = "free", independent = "all")
Related
I am doing a scatterplot with a facet_grid() like that:
library(ggplot2)
ggplot(df, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2)
I want the y axis title y to be in the middle of each row like this (paint solution):
The numbers of facet rows is two in this example because df$group2 has two different values. For my actual use case there may be more than two rows depending on the used facet variable; the y axis title is supposed to be in the middle of each facet row.
Best solution so far is adding spaces which is a mess since using y axis titles of different length shifts the text away from the middle of the rows. It must be with ggplot2, i.e. without the usage of additional packages. I make a package and do not want to rely on/ include too many packages.
Data used here:
df <- data.frame(x= rnorm(100), y= rnorm(100),
group1= rep(0:1, 50), group2= rep(2:3, each= 50))
Without using another package, I felt that the best method would be to build upon the spaces solution you linked in the original question. So I wrote a function to make the label spacing a little bit more robust.
ylabel <- function(label1,label2){
L1 <- nchar(label1)
L2 <- nchar(label2)
scaler <- ifelse(L1 + L2 > 8, 4, 0)
space1 = paste0(rep("",27 - (L1/2)),collapse = " ")
space2 = paste0(rep("",44 - (L1/2 + L2/2) - scaler), collapse = " ")
space3 = paste0(rep("",22 - (L2/2)), collapse = " ")
paste0(space1,label1,space2,label2,space3)
}
Application:
test <- ylabel("automobiles", "trucks")
ggplot(df, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2) +
ylab(test)
Still playing around with the scaler parameter, it's not perfect:
test2 <- ylabel("super long label", "a")
ggplot(df, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2) +
ylab(test2)
Will continue to refine the function/parameters, but am thinking this will get you close to what you're looking for.
You can copy the axis labels into new grobs in the gtable. Note that although this uses the grid and gtable packages, these are already imported by ggplot2, so this does not add any new dependencies that are not already available and used internally by ggplot.
library(grid)
library(gtable)
g = ggplot(df, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2)
gt = ggplot_gtable(ggplot_build(g))
which.ylab = grep('ylab-l', gt$layout$name)
gt = gtable_add_grob(gt, gt$grobs[which.ylab], 8, 3)
gt = gtable_add_grob(gt, gt$grobs[which.ylab], 10, 3)
gt = gtable_filter(gt, 'ylab-l', invert = TRUE) # remove the original axis title
grid.draw(gt)
The above works for OP's example with just two facets. If we want to generalise this for an arbitrary number of facets we can do that simply enough by searching the gtable to see which rows contain y-axes.
gt = ggplot_gtable(ggplot_build(g))
which.ylab = grep('ylab-l', gt$layout$name)
which.axes = grep('axis-l', gt$layout$name)
axis.rows = gt$layout$t[which.axes]
label.col = gt$layout$l[which.ylab]
gt = gtable::gtable_add_grob(gt, rep(gt$grobs[which.ylab], length(axis.rows)), axis.rows, label.col)
gt = gtable::gtable_filter (gt, 'ylab-l', invert = TRUE)
grid::grid.draw(gt)
In the version above, I also use :: to explicitly specify the namespace for the functions from the grid and gtable packages. This will allow the code to work without even loading the additional packages into the search path.
Demonstrating this code with another example with four facet rows:
df <- data.frame(x= rnorm(100), y= rnorm(100),
group1= rep(1:4, 25), group2= rep(1:2, each= 50))
You may consider switching to library(cowplot) for more control
The following code could be added to a function, but I left it long for clarity. Create 4 dataframes and feed them to four plots. Then arrange the plots
library(tidyverse)
df <- data.frame(x= rnorm(100), y= rnorm(100),
group1= rep(0:1, 50), group2= rep(2:3, each= 50))
library(cowplot)
df1 <- df %>%
filter(group2 == 2) %>%
filter(group1 == 0)
df2 <- df %>%
filter(group2 == 3) %>%
filter(group1 == 0)
df3 <- df %>%
filter(group2 == 2) %>%
filter(group1 == 1)
df4 <- df %>%
filter(group2 == 3) %>%
filter(group1 == 1)
plot1 <- ggplot(df1, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2)+
xlim(c(-3, 3))+
ylim(c(-3, 2))+
theme(strip.text.y = element_blank(),
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
plot1
plot2 <- ggplot(df2, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2)+
xlim(c(-3, 3))+
ylim(c(-3, 2))+
theme(axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
plot2
plot3 <- ggplot(df3, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2)+
xlim(c(-3, 3))+
ylim(c(-3, 2))+
theme(strip.text.x = element_blank(),
strip.text.y = element_blank())
plot3
plot4 <- ggplot(df4, aes(x, y)) +
geom_point() +
facet_grid(group1 ~ group2)+
xlim(c(-3, 3))+
ylim(c(-3, 2))+
theme(axis.title.y = element_blank(),
strip.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank())
plot4
plot_grid(plot1, plot2, plot3, plot4)
Here is a version with annotation, using ggplot2 only. It should be scalable.
No messing with grobs. The disadvantage is that the x positioning and the plot margins need to be semi-manually defined and this might not be very robust.
library(ggplot2)
df <- data.frame(x= rnorm(100), y= rnorm(100),
group1= rep(0:1, 50), group2= rep(2:3, each= 50))
## define a new data frame based on your groups, so this is scalable
annotate_ylab <- function(df, x, y, group1, group2, label = "label") {
## make group2 a factor, so you know which column will be to the left
df[[group2]] <- factor(df[[group2]])
lab_df <- data.frame(
## x positioning is a bit tricky,
## I think a moderately robust method is to
## set it relativ to the range of your values
x = min(df[[x]]) - 0.2 * diff(range(df[[x]])),
y = mean(df[[y]]),
g1 = unique(df[[group1]]),
## draw only on the left column
g2 = levels(df[[group2]])[1],
label = label
)
names(lab_df) <- c(x, y, group1, group2, "label")
lab_df
}
y_df <- annotate_ylab(df, "x", "y", "group1", "group2", "y")
ggplot(df, aes(x, y)) +
geom_point() +
geom_text(data = y_df, aes(x, y, label = label), angle = 90) +
facet_grid(group1 ~ group2) +
coord_cartesian(xlim = range(df$x), clip = "off") +
theme(axis.title.y = element_blank(),
plot.margin = margin(5, 5, 5, 20))
y_df_mtcars <- annotate_ylab(mtcars, "mpg", "disp", "carb", "vs", "y")
ggplot(mtcars, aes(mpg, disp)) +
geom_point() +
geom_text(data = y_df_mtcars, aes(mpg, disp, label = label), angle = 90) +
facet_grid(carb ~ vs) +
coord_cartesian(xlim = range(mtcars$mpg), clip = "off") +
theme(axis.title.y = element_blank(),
plot.margin = margin(5, 5, 5, 20))
Created on 2021-11-24 by the reprex package (v2.0.1)
By using ggplot and faced_grid functions I'm trying to make a heatmap. I have a categorical y axis, and I want y axis labels to be left aligned. When I use theme(axis.text.y.left = element_text(hjust = 0)), each panels' labels are aligned independently. Here is the code:
#data
set.seed(1)
gruplar <- NA
for(i in 1:20) gruplar[i] <- paste(LETTERS[sample(c(1:20),sample(c(1:20),1),replace = T) ],
sep="",collapse = "")
gruplar <- cbind(gruplar,anagruplar=rep(1:4,each=5))
tarih <- data.frame(yil= rep(2014:2019,each=12) ,ay =rep_len(1:12, length.out = 72))
gruplar <- gruplar[rep(1:nrow(gruplar),each=nrow(tarih)),]
tarih <- tarih[rep_len(1:nrow(tarih),length.out = nrow(gruplar)),]
grouped <- cbind(tarih,gruplar)
grouped$value <- rnorm(nrow(grouped))
#plot
p <- ggplot(grouped,aes(ay,gruplar,fill=value))
p <- p + facet_grid(anagruplar~yil,scales = "free",
space = "free",switch = "y")
p <- p + theme_minimal(base_size = 14) +labs(x="",y="") +
theme(strip.placement = "outside",
strip.text.y = element_text(angle = 90))
p <- p + geom_raster(aes(fill = value), na.rm = T)
p + theme(axis.text.y.left = element_text(hjust = 0, size=14))
I know that by putting spaces and using a mono-space font I can solve the problem, but I have to use the font 'Calibri Light'.
Digging into grobs isn't my favourite hack, but it can serve its purpose here:
# generate plot
# (I used a smaller base_size because my computer screen is small)
p <- ggplot(grouped,aes(ay,gruplar,fill=value)) +
geom_raster(aes(fill = value),na.rm = T) +
facet_grid(anagruplar~yil,scales = "free",space = "free",switch = "y") +
labs(x="", y="") +
theme_minimal(base_size = 10) +
theme(strip.placement = "outside",
strip.text.y = element_text(angle = 90),
axis.text.y.left = element_text(hjust = 0, size=10))
# examine ggplot object: alignment is off
p
# convert to grob object: alignment is unchanged (i.e. still off)
gp <- ggplotGrob(p)
dev.off(); grid::grid.draw(gp)
# change viewport parameters for left axis grobs
for(i in which(grepl("axis-l", gp$layout$name))){
gp$grobs[[i]]$vp$x <- unit(0, "npc") # originally 1npc
gp$grobs[[i]]$vp$valid.just <- c(0, 0.5) # originally c(1, 0.5)
}
# re-examine grob object: alignment has been corrected
dev.off(); grid::grid.draw(gp)
I guess one option is to draw the labels on the right-hand side, and move that column in the gtable,
p <-ggplot(grouped,aes(ay,gruplar,fill=value)) +
facet_grid(anagruplar~yil,scales = "free",space = "free",switch = "y") +
geom_raster(aes(fill = value),na.rm = T) +
theme_minimal(base_size = 12) + labs(x="",y="") +
scale_y_discrete(position='right') +
theme(strip.placement = "outside", strip.text.y = element_text(angle = 90))+
theme(axis.text.y.left = element_text(hjust = 0,size=14))
g <- ggplotGrob(p)
id1 <- unique(g$layout[grepl("axis-l", g$layout$name),"l"])
id2 <- unique(g$layout[grepl("axis-r", g$layout$name),"l"])
g2 <- gridExtra::gtable_cbind(g[,seq(1,id1-1)],g[,id2], g[,seq(id1+1, id2-1)], g[,seq(id2+1, ncol(g))])
library(grid)
grid.newpage()
grid.draw(g2)
This seems like a bug in ggplot2, or at least what I consider an undesirable / unexpected behavior. You may have seen the approach suggested here, which uses string padding on a mono-space font to achieve the alignment.
This is pretty hacky, but if you need to achieve alignment using a particular font, you might replace the axis labels altogether with geom_text. I have a mostly-working solution, but it is ugly, in that each step seems to break something else!
library(ggplot2); library(dplyr)
# To add a blank facet before 2014, I convert to character
grouped$yil = as.character(grouped$yil)
# I add some rows for the dummy facet, in year "", to use for labels
grouped <- grouped %>%
bind_rows(grouped %>%
group_by(gruplar) %>%
slice(1) %>%
mutate(yil = "",
value = NA_real_) %>%
ungroup())
p <- ggplot(grouped,
aes(ay,gruplar,fill=value)) +
geom_raster(aes(fill = value),na.rm = T) +
scale_x_continuous(breaks = 4*0:3) +
facet_grid(anagruplar~yil,
scales = "free",space = "free",switch = "y") +
theme_minimal(base_size = 14) +
labs(x="",y="") +
theme(strip.placement = "outside",
strip.text.y = element_text(angle = 90),
axis.text.y.left = element_blank(),
panel.grid = element_blank()) +
geom_text(data = grouped %>%
filter(yil == ""),
aes(x = -40, y = gruplar, label = gruplar), hjust = 0) +
scale_fill_continuous(na.value = "white")
p
(The last problem with this plot that I can see is that it shows an orphaned "0" on the x axis of the dummy facet. Need another hack to get rid of that!)
Using this SO solution I created a facet with two "empty" plots, with the aim of combining with another group of facet_wrap plots, as shown below. The purpose is to have two y-axis labels for different unit measurements. How can I make the grid layout look like the top image, which produces the arrangement I want, but not the axis labels? This was accomplished with plot_grid with individual plots. My current output does not scale correctly and overlaps the other plots, as seen in the second image, but provides the axis labels.
I have example data below, just copy and run the code to input it.
library(ggplot2)
library(grid)
library(cowplot)
clipboard <- readClipboard()
test.data <- read.table(file = "clipboard", sep = ",", header=TRUE)
test.data1 <- test.data[1:24, ]
test.data2 <- test.data[25:32, ]
testplot1 <- ggplot(test.data1, aes(Station, value)) +
geom_point() +
labs(x = "Stations", y = "Scale A") +
theme(legend.position = "none", legend.title = element_blank()) +
facet_wrap( ~ constituent, ncol = 3, scales = "free_y")
testplot2 <- ggplot(test.data2, aes(Station, value)) +
geom_point() +
labs(x = "Stations", y = "Scale B") +
theme(legend.position = "none", legend.title = element_blank(), axis.title.y = element_text(hjust = 0.2)) +
facet_wrap( ~ constituent, ncol = 1, scales = "free_y")
blankplots <- ggplotGrob(testplot2)
rm_grobs <- blankplots$layout$name %in% c("panel-1-1", "panel-2-1", "strip-t-1-1", "strip-t-1-2")
blankplots$grobs[rm_grobs] <- NULL
blankplots$layout <- blankplots$layout[!rm_grobs, ]
grid.newpage()
emptygrids <- grid.draw(blankplots)
plot_grid(emptygrids, MPLOOplot1)
Example date is below:
Station,constituent,value
A1,A,1
B1,A,1
A1,B,2
B1,B,2
A1,C,3
B1,C,3
A1,D,4
B1,D,4
A1,E,5
B1,E,5
A1,F,6
B1,F,6
A1,G,7
B1,G,7
A1,H,8
B1,H,8
A1,I,9
B1,I,9
A1,J,10
B1,J,10
A1,K,11
B1,K,11
A1,L,1.4
B1,L,1.4
A1,Blank1,NA
B1,Blank1,NA
A1,Blank2,NA
B1,Blank2,NA
A1,XX,0.52
B1,XX,0.52
A1,YY,0.355
B1,YY,0.355
I'm not sure I understand exactly what you're trying to do, so let me know if this is what you had in mind. I wasn't sure what you wanted colour to be mapped to, so I just used constituent for this example.
library(gridExtra)
library(ggplot2)
library(dplyr)
library(cowplot)
theme_set(theme_classic())
testplot1 <- ggplot(test.data1, aes(Station, value, colour=constituent)) +
geom_point() +
labs(x = "Stations", y = "Scale A") +
theme(legend.title = element_blank()) +
facet_wrap( ~ constituent, ncol = 3, scales = "free_y") +
guides(colour=guide_legend(ncol=2))
testplot2 <- ggplot(test.data2 %>% filter(!grepl("Blank", constituent)),
aes(Station, value, colour=constituent)) +
geom_point() +
labs(x = "Stations", y = "Scale B") +
theme(legend.title = element_blank(),
axis.title.y = element_text(hjust = 0.2)) +
facet_wrap( ~ constituent, ncol = 1, scales = "free_y")
leg1 = get_legend(testplot1)
leg2 = get_legend(testplot2)
testplot1 = testplot1 + guides(colour=FALSE)
testplot2 = testplot2 + guides(colour=FALSE)
Now we lay out the plots and legends with grid.arrange. This requires some manual tweaking of the heights and widths.
grid.arrange(
arrangeGrob(
arrangeGrob(nullGrob(), leg2, leg1, nullGrob(), ncol=4, widths=c(1,4,4,1)),
testplot2, ncol=1, heights=c(4.2,5)
),
testplot1, ncol=2, widths=c(1.1,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
This question already has answers here:
Left align two graph edges (ggplot)
(9 answers)
Closed 9 years ago.
I am a newbie using ggplot2 and I'm trying to plot a scatter plot above a heatmap. Both plots have the same discrete x-axis.
This is the code I'm trying:
library(ggplot2)
library(grid)
library(reshape2)
#data for the scatterplot
df = data.frame(id1 = letters[1:10], C = abs(rnorm(10)))
#scatter plot
p1 <- ggplot(df, aes(x= id1, y = C)) +
geom_point(pch = 19) + theme_bw() +
scale_x_discrete(expand = c(0, 0), breaks = letters[1:10]) +
theme(legend.position = "none") + theme(axis.title.y = element_blank()) + theme(axis.title.x = element_blank())
#data for the heatmap
X = data.frame(matrix(rnorm(100), nrow = 10))
names(X) = month.name[1:10]
X = melt(cbind(id1 = letters[1:10], X))
#heatmap
p2 <- ggplot(X,
aes(x = id1, y = variable, fill = value))
p2 <- p2 + geom_tile()
p2 <- p2 + scale_fill_gradientn(colours = c("blue", "white" , "red"))
p2 <- p2 + theme(legend.position = "none") + theme(axis.title.y = element_blank()) + theme(axis.title.x = element_blank())
p2 <- p2 + scale_x_discrete(expand = c(0, 0), breaks = letters[1:10])
p2 <- p2 + scale_y_discrete(expand = c(0, 0))
layt <- grid.layout(nrow=2,ncol=1,heights=c(2/8,6/8),default.units=c('null','null'))
vplayout <- function(x,y) {viewport(layout.pos.row = x, layout.pos.col = y)}
grid.newpage()
pushViewport(viewport(layout=layt))
print(p1,vp=vplayout(1,1))
print(p2,vp = vplayout(2,1))
The problem is that the axis are not situated one above the other.
https://mail.google.com/mail/u/0/?ui=2&ik=81975edabc&view=att&th=13ece12a06a3cea2&attid=0.1&disp=emb&realattid=ii_13ece128398baede&zw&atsh=1
Is there any solution? It is possible to reshape the data and make something like facets?
Another option:
grid.draw(gtable:::rbind.gtable(ggplotGrob(p1),
ggplotGrob(p2), size='last'))
(ideally one would want size=max, but it has a bug preventing it to work).
There are a couple of tricks here. The first is that the tick marks get treated differently, even though you have the same discrete axis. When you do expand = c(0,0), on the scatterplot the tick is now aligned with the y axis, while on the heatmap it is in the centre of the category. My method of getting around that is to manually assign the expand value for the scatterplot so that there is a gap of of 1/2 a categorical value. Because there are 10 categorical values, in this case it is 0.05 ((1/10)/2). The points will now align with the centre of each category.
The other side of the problem is because the y labels are different sizes they throw out the rest of the alignment. The solution comes from this question, using ggplot_gtable and grid.arrange from the gridExtra package.
library(gridExtra)
#data for the scatterplot
df = data.frame(id1 = letters[1:10], C = abs(rnorm(10)))
#scatter plot
p1 <- ggplot(df, aes(x= id1, y = C)) +
geom_point(pch = 19) + theme_bw() +
# Change the expand values
scale_x_discrete(expand = c(0.05, 0.05), breaks = letters[1:10]) +
#scale_y_discrete(breaks = NULL) +
theme(legend.position = "none") + theme(axis.title.y = element_blank()) + theme(axis.title.x = element_blank())
p1
#data for the heatmap
X = data.frame(matrix(rnorm(100), nrow = 10))
names(X) = month.name[1:10]
X = melt(cbind(id1 = letters[1:10], X))
#heatmap
p2 <- ggplot(X,
aes(x = id1, y = variable, fill = value))
p2 <- p2 + geom_tile()
p2 <- p2 + scale_fill_gradientn(colours = c("blue", "white" , "red"))
p2 <- p2 + theme(legend.position = "none") + theme(axis.title.y = element_blank()) + theme(axis.title.x = element_blank())
p2 <- p2 + scale_x_discrete(expand = c(0, 0), breaks = letters[1:10])
p2 <- p2 + scale_y_discrete(expand = c(0, 0))
#Here's the gtable magic
gp1<- ggplot_gtable(ggplot_build(p1))
gp2<- ggplot_gtable(ggplot_build(p2))
#This identifies the maximum width
maxWidth = unit.pmax(gp1$widths[2:3], gp2$widths[2:3])
#Set each to the maximum width
gp1$widths[2:3] <- maxWidth
gp2$widths[2:3] <- maxWidth
#Put them together
grid.arrange(gp1, gp2)
EDIT - See #baptiste's answer for a more elegant method of alignment of the y axis