I have a list of 45 ggplot objects that I'm arranging across multiple pages thanks to the marrangeGrob() function from gridExtra. I would like to show a same and unique legend on each pages.
I know how to extract the legend (g_legend), how to plot my ggplot without the legend. But I do not find a way to have a multipages thanks to marrangeGrob and a unique legend.
I used g_legend() to extract my legend
g_legend<-function(a.gplot){
g <- ggplotGrob(a.gplot + theme(legend.position = "right"))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
return(legend)}
In order to simplify, the reproductible data set is from diamonds, and let say I want to produce on one page p1 and p2 and on an other page p2 and p1.
df <- count(diamonds, cut)
p1 = ggplot(df, aes(x=cut, y=n, label=format(n, big.mark=","), fill=cut)) +
geom_bar(stat="identity") +
geom_text(aes(y=0.5*n), colour="white") +
coord_flip() +
theme(legend.position="bottom")
p2 = ggplot(diamonds %>% sample_n(1000), aes(x=carat, y=price, colour=cut)) +
geom_point()
leg = g_legend(p1)
I first try to plot the 4 graphs in one page
combined<-arrangeGrob(do.call("arrangeGrob",c(
lapply(list(p1,p2,p2,p1), function(x){ x + theme(legend.position="none")}),ncol=2)),
leg,
ncol = 2)
grid.newpage()
grid.draw(combined)
which works perfectly but when I try to do it with multipages
marrangeGrob(do.call("marrangeGrob",c(lapply(list(p1,p2,p2,p1), function(x){ x + theme(legend.position="none")}),
ncol=2,nrow=2)),
leg,
ncol = 2,nrow=1)
I obtained :
Error in $<-.data.frame(*tmp*, "wrapvp", value = list(x = 0.5, y = 0.5, : replacement has 17 rows, data has 5
Does anyone know a way to use marrangeGrob and obtain a unique legend on each multipages?
Related
The following sample or R code displays contour levels and the data points used in generating the contours.
n <- 10
x <- c(rnorm(n,-1,0.5), rnorm(n,1,0.5))
y <- c(rnorm(n,-1,1), rnorm(n,1,0.5))
df <- data.frame(x,y)
# categorise the points
df$cat <- sample(c(1,2), n, replace=T)
library(ggplot2)
p <- ggplot(df)
# for manual colouring of points, but not showing contours due to error
#p <- p + geom_point(aes(x=x,y=y,col=factor(cat)))
#cols <- c("1"="red", "2"="blue")
#p <- p + scale_color_manual(values=cols)
# this works fine except I am not controlling the colours
p <- p + geom_point(aes(x=x,y=y,col=cat))
p <- p + geom_density2d(aes(x=x,y=y,color=..level..))
print(p)
I am able to colour the points according to their binary category (see commented out code above) manually if I do not display the contours, but adding the contours results in a "Continuous value supplied to discrete scale" error.
Various attempts have failed.
The question: Is it possible to colour the points (according to category) and independently colour the contour levels (according to height)?
You can try
library(tidyverse)
df %>%
ggplot(aes(x=x,y=y)) +
stat_density_2d(aes(fill = ..level..), geom = "polygon") +
geom_point(aes(color=factor(cat)), size=5) +
theme_bw()
Or switch to points where fill is working like shape=21
df %>%
ggplot(aes(x=x,y=y)) +
geom_density2d(aes(color=..level..))+
geom_point(aes(fill=factor(cat)),color="black",shape=21, size=5) +
theme_bw() +
scale_fill_manual(values = c(2,4)) +
scale_color_continuous(low = "green", high = "orange")
or try to add scale_color_gradientn(colours = rainbow(10)) instead.
I am using ggplot2 for a multiplot. Althoug after a lot of tweaking, I still face
problems as:
Some free space gets plotted on each side (left/right) of each plot. I have marked this on the right side of each plot.
Plots are not aligned by the left side. This problem is clearly observed in the bottom plot
Y axis label is much far away from the plots. Can I reduce this separation?
Multiplot is:
I used the following R code for the same:
p1 <- ggplot(data = dplots[[1]],aes(timestamp,power/1000))+ geom_line()+
ylab("")+theme(axis.text.x=element_blank(),axis.title.x=element_blank(),axis.ticks.x=element_blank(),plot.margin = unit(c(-0.3,1,-0.3,1), "cm"))+labs(title="room1")
p2 <- ggplot(data = dplots[[2]],aes(timestamp,power/1000))+ geom_line()+
ylab("")+theme(axis.text.x=element_blank(),axis.title.x=element_blank(),axis.ticks.x=element_blank(),plot.margin = unit(c(-0.3,1,-0.3,1), "cm"))+ labs(title="room2")
p3 <- ggplot(data = dplots[[6]],aes(timestamp,power/1000))+ geom_line()+
ylab("")+theme(axis.text.x=element_blank(),axis.title.x=element_blank(),axis.ticks.x=element_blank(),plot.margin = unit(c(-0.3,1,-0.3,1), "cm"))+ labs(title="room3")
p4 <- ggplot(data = dplots[[4]],aes(timestamp,power/1000))+ geom_line()+
ylab("")+theme(axis.text.x=element_blank(),axis.title.x=element_blank(),axis.ticks.x=element_blank(),plot.margin = unit(c(-0.3,1,-0.3,1), "cm"))+ labs(title="room4")
p5 <- ggplot(data = dplots[[5]],aes(timestamp,power/1000))+ geom_line()+
ylab("")+theme(axis.text.x=element_blank(),axis.title.x=element_blank(),axis.ticks.x=element_blank(),plot.margin = unit(c(-0.3,1,-0.3,1), "cm"))+ labs(title="room5")
p6 <- ggplot(data = dplots[[3]],aes(timestamp,power/1000))+ geom_line()+
ylab("")+theme(axis.title.x=element_blank(),axis.ticks.x=element_blank(),plot.margin = unit(c(-0.3,1,-0.3,1), "cm"))+ labs(title="Chiller") +
scale_x_datetime(labels= date_format("%d-%m-%y",tz ="UTC"),breaks = pretty_breaks(8))
grid.arrange(p1,p2,p3,p4,p5,p6,nrow=6,ncol=1,heights=c(0.15,0.15,0.15,0.15,0.15,0.15),left="Power (KW)")
The dataset (dplots) is stored at the link.
Probably the easiest solution is to combine the dataframes in the list in one dataset. With rbindlist from the data.table package you can also include id's for each dataframe:
library(data.table)
# bind the dataframes together into one datatable (which is an enhanced dataframe)
DT <- rbindlist(dplots, idcol = "id")
# give names to the id's
DT$id <- factor(DT$id, labels = c("room 1","room 2","room 3", "room 4","room 5","Chiller"))
library(ggplot2)
ggplot(DT, aes(x = timestamp, y = power)) +
geom_line() +
scale_x_datetime(expand = c(0,0)) +
facet_grid(id ~ ., scales="free_y") +
theme_bw()
this results in the following plot:
With your existing code, use cowplot package:
library(cowplot)
plot_grid(p1,p2,p3,p4,p5,p6,ncol=1,align = "v")
I wish to use ggplot2 objects/grobs/plots as axis labels.
Here is my toy example:
library(dplyr)
library(ggplot2)
# master plot
df <- data_frame(y = c("unchanging", "increasing", "decreasing"), x = c(20, 50, 30))
ggplot(df, aes(x, y)) + geom_point()
# fxn generates ggplot2 object specifying a line plot from two points
two_pt_line_plot <- function(y1, y2) {
df <- data_frame(y = c(y1, y2), x = c("from", "to"))
ggplot(df, aes(x,y, group = 1)) + geom_line(size = 4) +
xlab(NULL) + ylab(NULL) +
scale_x_discrete(expand=c(0,0)) +
scale_y_continuous(expand=c(0,0))
}
# make the three plot objects, name them appropriately.
grobs <- Map(two_pt_line_plot, c(.5,0,1), c(.5, 1, 0))
names(grobs) <- df$y
grobs
#> $unchanging
#> $increasing
#> $decreasing
I want to programmatically generate this:
The only thing I can currently think of is that I somehow layer over the plots as facets where the theming as been hacked to the max to make it look like it belongs. But I haven't been able to do that yet and it seems like a very hack-y solution. I therefore thought I would throw it out there.
This question already has answers here:
Align multiple plots in ggplot2 when some have legends and others don't
(6 answers)
Closed 5 years ago.
I'm trying to use ggplot to draw a graph comparing the absolute values of two variables, and also show the ratio between them. Since the ratio is unitless and the values are not, I can't show them on the same y-axis, so I'd like to stack vertically as two separate graphs with aligned x-axes.
Here's what I've got so far:
library(ggplot2)
library(dplyr)
library(gridExtra)
# Prepare some sample data.
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control
# Plot absolute values
plot_values <- ggplot(results, aes(x=index)) +
geom_point(aes(y=value, color="value")) +
geom_point(aes(y=control, color="control"))
# Plot ratios between values
plot_ratios <- ggplot(results, aes(x=index, y=ratio)) +
geom_point()
# Arrange the two plots above each other
grid.arrange(plot_values, plot_ratios, ncol=1, nrow=2)
The big problem is that the legend on the right of the first plot makes it a different size. A minor problem is that I'd rather not show the x-axis name and tick marks on the top plot, to avoid clutter and make it clear that they share the same axis.
I've looked at this question and its answers:
Align plot areas in ggplot
Unfortunately, neither answer there works well for me. Faceting doesn't seem a good fit, since I want to have completely different y scales for my two graphs. Manipulating the dimensions returned by ggplot_gtable seems more promising, but I don't know how to get around the fact that the two graphs have a different number of cells. Naively copying that code doesn't seem to change the resulting graph dimensions for my case.
Here's another similar question:
The perils of aligning plots in ggplot
The question itself seems to suggest a good option, but rbind.gtable complains if the tables have different numbers of columns, which is the case here due to the legend. Perhaps there's a way to slot in an extra empty column in the second table? Or a way to suppress the legend in the first graph and then re-add it to the combined graph?
Here's a solution that doesn't require explicit use of grid graphics. It uses facets, and hides the legend entry for "ratio" (using a technique from https://stackoverflow.com/a/21802022).
library(reshape2)
results_long <- melt(results, id.vars="index")
results_long$facet <- ifelse(results_long$variable=="ratio", "ratio", "values")
results_long$facet <- factor(results_long$facet, levels=c("values", "ratio"))
ggplot(results_long, aes(x=index, y=value, colour=variable)) +
geom_point() +
facet_grid(facet ~ ., scales="free_y") +
scale_colour_manual(breaks=c("control","value"),
values=c("#1B9E77", "#D95F02", "#7570B3")) +
theme(legend.justification=c(0,1), legend.position=c(0,1)) +
guides(colour=guide_legend(title=NULL)) +
theme(axis.title.y = element_blank())
Try this:
library(ggplot2)
library(gtable)
library(gridExtra)
AlignPlots <- function(...) {
LegendWidth <- function(x) x$grobs[[8]]$grobs[[1]]$widths[[4]]
plots.grobs <- lapply(list(...), ggplotGrob)
max.widths <- do.call(unit.pmax, lapply(plots.grobs, "[[", "widths"))
plots.grobs.eq.widths <- lapply(plots.grobs, function(x) {
x$widths <- max.widths
x
})
legends.widths <- lapply(plots.grobs, LegendWidth)
max.legends.width <- do.call(max, legends.widths)
plots.grobs.eq.widths.aligned <- lapply(plots.grobs.eq.widths, function(x) {
if (is.gtable(x$grobs[[8]])) {
x$grobs[[8]] <- gtable_add_cols(x$grobs[[8]],
unit(abs(diff(c(LegendWidth(x),
max.legends.width))),
"mm"))
}
x
})
plots.grobs.eq.widths.aligned
}
df <- data.frame(x = c(1:5, 1:5),
y = c(1:5, seq.int(5,1)),
type = factor(c(rep_len("t1", 5), rep_len("t2", 5))))
p1.1 <- ggplot(diamonds, aes(clarity, fill = cut)) + geom_bar()
p1.2 <- ggplot(df, aes(x = x, y = y, colour = type)) + geom_line()
plots1 <- AlignPlots(p1.1, p1.2)
do.call(grid.arrange, plots1)
p2.1 <- ggplot(diamonds, aes(clarity, fill = cut)) + geom_bar()
p2.2 <- ggplot(df, aes(x = x, y = y)) + geom_line()
plots2 <- AlignPlots(p2.1, p2.2)
do.call(grid.arrange, plots2)
Produces this:
// Based on multiple baptiste's answers
Encouraged by baptiste's comment, here's what I did in the end:
library(ggplot2)
library(dplyr)
library(gridExtra)
# Prepare some sample data.
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control
# Plot ratios between values
plot_ratios <- ggplot(results, aes(x=index, y=ratio)) +
geom_point()
# Plot absolute values
remove_x_axis =
theme(
axis.ticks.x = element_blank(),
axis.text.x = element_blank(),
axis.title.x = element_blank())
plot_values <- ggplot(results, aes(x=index)) +
geom_point(aes(y=value, color="value")) +
geom_point(aes(y=control, color="control")) +
remove_x_axis
# Arrange the two plots above each other
grob_ratios <- ggplotGrob(plot_ratios)
grob_values <- ggplotGrob(plot_values)
legend_column <- 5
legend_width <- grob_values$widths[legend_column]
grob_ratios <- gtable_add_cols(grob_ratios, legend_width, legend_column-1)
grob_combined <- gtable:::rbind_gtable(grob_values, grob_ratios, "first")
grob_combined <- gtable_add_rows(
grob_combined,unit(-1.2,"cm"), pos=nrow(grob_values))
grid.draw(grob_combined)
(I later realised I didn't even need to extract the legend width, since the size="first" argument to rbind tells it just to have that one override the other.)
It feels a bit messy, but it is exactly the layout I was hoping for.
An alternative & quite easy solution is as follows:
# loading needed packages
library(ggplot2)
library(dplyr)
library(tidyr)
# Prepare some sample data
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control
# reshape into long format
long <- results %>%
gather(variable, value, -index) %>%
mutate(facet = ifelse(variable=="ratio", "ratio", "values"))
long$facet <- factor(long$facet, levels=c("values", "ratio"))
# create the plot & remove facet labels with theme() elements
ggplot(long, aes(x=index, y=value, colour=variable)) +
geom_point() +
facet_grid(facet ~ ., scales="free_y") +
scale_colour_manual(breaks=c("control","value"), values=c("green", "red", "blue")) +
theme(axis.title.y=element_blank(), strip.text=element_blank(), strip.background=element_blank())
which gives:
Let's say we have a simple plot of the following kind.
library(ggplot2)
df = data.frame(y=c(0,1.1,2.3,3.1,2.9,5.8,6,7.4,8.2,9.1),x=seq(1,100, length.out=10))
ggplot(df,aes(x=x,y=y)) + geom_point()
x perfectly correlates with z. The relation is: Constant=x^2*z=1.23
therefore I could rewrite the data.frame like this:
df = cbind(df,1.23/df$x^2)
The question is:
How can I display both variables xand zone the x-axis? It could be one at the bottom and one at the top of the graph or both at the bottom.
Here's a dangerous attempt. Previous version with a log-scale was just wrong.
library(ggplot2)
df = data.frame(y=c(0,1.1,2.3,3.1,2.9,5.8,6,7.4,8.2,9.1),
x=seq(1,100, length.out=10))
df$z = 1.23/df$x^2
## let's at least remove the gridlines
p1 <- ggplot(df,aes(x=x,y=y)) + geom_point() +
scale_x_continuous(expand=c(0,0)) +
theme(panel.grid.major=element_blank(),
panel.grid.minor = element_blank())
## make sure both plots have expand = c(0,0)
## otherwise data and top-axis won't necessarily be aligned...
p2 <- ggplot(df,aes(x=z,y=y)) + geom_point() +
scale_x_continuous(expand=c(0,0))
library(gtable)
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
tmp <- gtable_filter(g2, pattern="axis-b")
## ugly tricks to extract and reshape the axis
axis <- tmp[["grobs"]][[1]][["children"]][["axis"]] # corrupt the children
axis$layout <- axis$layout[2:1,]
axis$grobs[[1]][["y"]] <- axis$grobs[[1]][["y"]] - unit(1,"npc") + unit(0.15,"cm")
## back to "normality"
g1 <- gtable_add_rows(g1, sum(tmp$heights), 2)
gtableAddGrobs <- gtable_add_grob # alias, making sure #!hadley doesn't see this
g1 <- gtableAddGrobs(g1,
grobs=list(gtable_filter(g2, pattern="xlab"),axis),
t=c(1,3), l=4)
grid.newpage()
grid.draw(g1)
A both-on-the-bottom approach can be done with the excellent cowplot library.
library(ggplot2)
library(cowplot)
data <- data.frame(temp_c=runif(100, min=-5, max=30), outcome=runif(100))
plot <- ggplot(data) +
geom_point(aes(x=temp_c, y=outcome)) +
theme_classic() +
labs(x='Temperature (Celsius)')
x2plot <- ggplot(data) +
geom_point(aes(x=temp_c, y=outcome)) +
theme_classic() +
scale_x_continuous(label=function(x){round(x*(9/5) + 32)}) +
labs(x='Temperature (Fahrenehit)')
x <- get_x_axis(x2plot)
xl <- get_plot_component(x2plot, "xlab-b")
plot_grid(plot, ggdraw(x), ggdraw(xl), align='v', axis='rl', ncol=1,
rel_heights=c(0.8, 0.05, 0.05))