I tried to generate multiple grid plots with ggplot2. So I would like to generate distribution plot with additional boxplot below x-axis and that for different groups and variables like that:
CODE: I tried to do that with the following code :
library(ggplot2)
require(grid)
x=rbind(data.frame(D1=rnorm(1000),Name="titi",ID=c(1:1000)),
data.frame(D1=rnorm(1000)+1,Name="toto",ID=c(1:1000)))
space=1
suite=1
p1=ggplot(x, aes(x=D1, color=Name, fill=Name)) +
geom_histogram(aes(y=..density..),alpha=0.35,color=adjustcolor("white",0),position="identity",binwidth = 0.05)+
geom_density(alpha=.2,size=1)+
theme_minimal()+
labs(x=NULL,y="Density")+
theme(legend.position = "top",
legend.title = element_blank())+
scale_fill_manual(values=c("gray30","royalblue1"))+
scale_color_manual(values=c("gray30","royalblue1"))
p2=ggplot(x, aes(x=factor(Name), y=D1,fill=factor(Name),color=factor(Name)))+
geom_boxplot(alpha=0.2)+
theme_minimal()+
coord_flip()+
labs(x=NULL,y=NULL)+
theme(legend.position = "none",
axis.text.y = element_blank(),
axis.text.x = element_blank(),
panel.grid.minor.x = element_blank(),
panel.grid.major.x = element_blank(),
panel.grid.minor.y = element_blank(),
panel.grid.major.y = element_blank())+
scale_fill_manual(values=c("gray30","royalblue1"))+
scale_color_manual(values=c("gray30","royalblue1"))
grid.newpage()
pushViewport(viewport(layout=grid.layout(5,1)))
define_region <- function(row, col){
viewport(layout.pos.row = row, layout.pos.col = col)
}
print(p1, vp=define_region(1:4,1))
print(p2, vp=define_region(5,1))
RESULT:
QUESTION: During my search I observed that scale between density distribution plot and boxplot are not the same (problem 1). I haven't found solution to plot these two graph in grid (I'm lost).
With the cowplot package this becomes a bit easier. However, we should properly set the x-axis range to ensure they are the same for both plots. This is because the density plots are naturally a bit wider than pure data plots, and the axis for p1 will therefore be a bit wider. When the axes are fixed we can arrange and align them (axis text and margins will no longer matter).
library(cowplot)
comb <- plot_grid(
p1 + xlim(-5, 5),
p2 + ylim(-5, 5), # use ylim for p2 because of coord_flip()
align = 'v', rel_heights = c(4, 1), nrow = 2
)
Similarly we can arrange multiples of the combination plots:
plot_grid(comb, comb, comb, comb)
Related
I just found this plot in Factfulness (book by Hans Rosling and his children). I find the aestetics of the split quite appealing.
While it's possible to make something similar using geom_rect(), it's a quite different look. Another approach would be to use cowplot or patchwork but quite tricky. Here's as far as I got trying to replicate the top part with
gapminder %>%
filter(year==1997, gdpPercap<16000) %>%
ggplot(aes(gdpPercap, y=lifeExp, size=pop)) +
geom_point(alpha=0.5)+
scale_x_log10()+
ggthemes::theme_base()+
theme(legend.position = "none",
plot.background = element_blank(),
plot.margin = unit(c(0.5, 0, 0, 0), "cm")) -> P1
gapminder %>%
filter(year==1997, gdpPercap>16000) %>%
ggplot(aes(gdpPercap, y=lifeExp, size=pop)) +
geom_point(alpha=0.5)+
scale_x_log10()+
ggthemes::theme_base()+
theme(legend.position = "none",
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
plot.background = element_blank(),
plot.margin = unit(c(0.5, 0.5, 0, 0), "cm"),
axis.title.x = element_blank()) -> P2
cowplot::plot_grid(P1, P2, rel_widths = c(2,1), labels = NULL,
align = "h")
I think al the rest of the text and highlights are possible with existing packages. I am wondering what's the way to get a common x axis (the right side should display the ticks according to the ). Ideally, the x axis title would be centered but that might be too much to ask. I can also move it inside as text.
There are problems with axes, as you can see in the plot with y ticks. I wonder if facets would be a better approach. I am also not sure if the point sizes is wrongly calculated because I filter the data first.
Here is a solution using facets. You can solve the x-axis breaks problem by precomputing the breaks using the scale package's log10 break calculator. You could use a mutate() in the pipeline to make a new variable that splits the facets.
library(tidyverse)
library(gapminder)
breaks <- scales::log10_trans()$breaks(range(gapminder$gdpPercap), n = 6)
gapminder %>%
filter(year==1997) %>%
mutate(facet = factor(ifelse(gdpPercap > 16000, "High", "Low"),
levels = c("Low", "High"))) %>%
ggplot(aes(gdpPercap, y=lifeExp, size=pop)) +
geom_point(alpha=0.5)+
scale_x_log10(breaks = breaks)+
ggthemes::theme_base()+
facet_grid(~ facet,
scales = "free_x", space = "free_x") +
ggtitle("My title") +
theme(legend.position = "none",
plot.title = element_text(hjust = 0.5),
plot.background = element_blank())
I'm generating a pie chart that I would like to incorporate into a Shiny page. The plot area (i.e.: the white space surrounding the plot) will be fluid. I'd like the plot itself (the grey area and the pie chart) to align to the left-hand side of the plot area, instead of appearing centered. Any ideas?
ggplot(treeData, aes(x = "", fill = ClassName)) +
ggtitle("Species distribution") +
geom_bar(width = 1) +
coord_polar("y", start=0) +
xlab("") + ylab("") +
theme(
axis.text.x=element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(),
plot.margin=unit(c(25,0,0,0), "pt")) +
scale_fill_manual(values= c("#ffff00", "#008080", "#00ff00"))
The more detailed explanation for why this answer works is provided here. In brief, you need to place the plot into a grid that can expand as you resize the enclosing image.
library(ggplot2)
library(grid)
library(gtable)
# some test data
animals <- as.data.frame(
table(Species =
c(rep("Moose", sample(1:100, 1)),
rep("Frog", sample(1:100, 1)),
rep("Dragonfly", sample(1:100, 1))
)))
# make the pie chart
g <- ggplot(animals, aes(x = "", y = Freq, fill = Species)) +
geom_bar(width = 1, stat = "identity") +
coord_polar("y", start=0) +
theme(
axis.text.x=element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(),
plot.margin=unit(c(25,0,0,0), "pt"))
# set the desired width and height of the
# pie chart (need to play around to find
# numbers that work
plotwidth = unit(6.1, "inch")
plotheight = unit(5, "inch")
# place into matrix that can expand
grob <- ggplotGrob(g)
mat <- matrix(list(grob, nullGrob(), nullGrob(), nullGrob()), nrow = 2)
widths <- unit(c(1, 1), "null")
widths[1] <- plotwidth
heights <- unit(c(1, 1), "null")
heights[1] <- plotheight
gm <- gtable_matrix(NULL, mat, widths, heights)
grid.newpage()
grid.draw(gm)
You can try to change the margins by adding
theme(plot.margin = unit(c(5.5, 100, 5.5, -100), "points"))
or use the cowplot function
cowplot::plot_grid(your_plot, NULL, ncol=2, rel_widths = c(2,1))
This is a follow up of Question How to fit custom long annotations geom_text inside plot area for a Donuts plot?. See the accepted answer, the resulting plot understandably has extra blank area on the top and on the bottom. How can I get rid of those extra blank areas? I looked at theme aspect.ratio but this is not what I intend though it does the job but distorts the plot. I'm after cropping the plot from a square to a landscape form.
How can I do that?
UPDATE This is a self contained example of my use-case:
library(ggplot2); library(dplyr); library(stringr)
df <- data.frame(group = c("Cars", "Trucks", "Motorbikes"),n = c(25, 25, 50),
label2=c("Cars are blah blah blah", "Trucks some of the best in town", "Motorbikes are great if you ..."))
df$ymax = cumsum(df$n)
df$ymin = cumsum(df$n)-df$n
df$ypos = df$ymin+df$n/2
df$hjust = c(0,0,1)
ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)), #change width to adjust width of annotations
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
expand_limits(x = c(2, 4)) + #change x-axis range limits here
# no change to theme
theme(axis.title=element_blank(),axis.text=element_blank(),
panel.background = element_rect(fill = "white", colour = "grey50"),
panel.grid=element_blank(),
axis.ticks.length=unit(0,"cm"),axis.ticks.margin=unit(0,"cm"),
legend.position="none",panel.spacing=unit(0,"lines"),
plot.margin=unit(c(0,0,0,0),"lines"),complete=TRUE) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar("y", start=0) + scale_x_discrete()
And this is the result I'd like to find an answer to fix those annotated resulting blank spaces:
This is a multi-part solution to answer this and the other related question you've posted.
First, for changing the margins in a single graph, #Keith_H was on the right track; using plot.margin inside theme() is a convenient way. However, as mentioned, this alone won't solve the issue if the goal is to combine multiple plots, as in the case of the other question linked above.
To do that you'll need a combination of plot.margin and a specific plotting order within arrangeGrob(). You'll need a specific order because plots get printed in the order you call them, and because of that, it will be easier to change the margins of plots that are layered behind other plots, instead of in front of plots. We can think of it like covering the plot margins we want to shrink by expanding the plot on top of the one we want to shrink. See the graphs below for illustration:
Before plot.margin setting:
#Main code for the 1st graph can be found in the original question.
After plot.margin setting:
#Main code for 2nd graph:
ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)),
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar(theta='y') +
expand_limits(x = c(2, 4)) +
guides(fill=guide_legend(override.aes=list(colour=NA))) +
theme(axis.line = element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
axis.text.y=element_blank(),
axis.text.x=element_blank(),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white"),
plot.margin = unit(c(-2, 0, -2, -.1), "cm"),
legend.position = "none") +
scale_x_discrete(limits=c(0, 1))
After combining plot.margin setting and arrangeGrob() reordering:
#Main code for 3rd graph:
p1 <- ggplot(mtcars,aes(x=1:nrow(mtcars),y=mpg)) + geom_point()
p2 <- ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)), #change width to adjust width of annotations
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar(theta='y') +
expand_limits(x = c(2, 4)) + #change x-axis range limits here
guides(fill=guide_legend(override.aes=list(colour=NA))) +
theme(axis.line = element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
axis.text.y=element_blank(),
axis.text.x=element_blank(),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white"),
plot.margin = unit(c(-2, 0, -2, -.1), "cm"),
legend.position = "none") +
scale_x_discrete(limits=c(0, 1))
final <- arrangeGrob(p2,p1,layout_matrix = rbind(c(1),c(2)),
widths=c(4),heights=c(2.5,4),respect=TRUE)
Note that in the final code, I reversed the order you had in the arrangeGrob from p1,p2 to p2,p1. I then adjusted the height of the first object plotted, which is the one we want to shrink. This adjustment allows the earlier plot.margin adjustment to take effect, and as that takes effect, the graph printed last in order, which is P1, will start to take the space of what was the margins of P2. If you make one of these adjustments with out the others, the solution won't work. Each of these steps are important to produce the end result above.
You can set the plot.margins to negative values for the top and bottom of the plot.
plot.margin=unit(c(-4,0,-4,0),"cm"),complete=TRUE)
edit: here is the output:
Is it possible to use a factor variable to facet a histogram below or beside a scatter plot in ggplot2 in R (such that the histograms are of the x- and y-components of the data)?
The reason I ask whether this could be done with a factor variable is because faceting seems to be more general than the available packages that address this issue, where, for example with faceting, facet labels can be turned on or off, and also faceting has a more standard appearance where publication may be a concern. (Faceting also by default preserves the use of the same axes).
So far I haven't been able to get this to work because it seems like all faceted data have to be of the same number of dimensions (e.g., the scatterplot data is 2D, the histogram data are 1D).
I am not sure if I fully understand the question since a histogram of factor variables doesn't quite make sense to me. Also, without sample data, I will just have to use mtcars. Something like this might help. I use grid.extra in addition to ggplot2 in order to make the plots have a custom grid arrangement.
library(gridExtra)
library(ggplot2)
s_plot <- ggplot(data = mtcars, aes(x = hp, y = mpg)) + geom_point()
h1 <- ggplot(data = mtcars, aes(x = hp)) + geom_histogram()
h2 <- ggplot(data = mtcars, aes(x = mpg)) + geom_histogram()
grid.arrange(s_plot, h1, h2, layout_matrix = cbind(c(1, 1), c(2, 3)))
Note that in the layout_matrix argument in grid.arrange, I use cbind(c(1,1), c(2, 3)) because I want the first plot to be in a column all by itself and then I want the other two plots to occupy individual rows in the second column of the grid.
Consider the use of geom_rug.
ggplot(mtcars, aes(wt, mpg)) +
geom_point() + geom_rug()
Nick and Brian,
Thanks for your help with the code. I asked around and was able to get the set-up I was looking for. Basically it goes like this, as shown below. (Hopefully this might be useful to you and others in the future, as I think this is a common type of graph):
rm(list = ls())
library(ggplot2)
library(gridExtra)
df <- data.frame(
x = rnorm(100),
y = rnorm(100)
)
xrange <- range(pretty(df$x))
yrange <- range(pretty(df$y))
p.left <- ggplot(df, aes(y)) +
geom_histogram() +
lims(x = yrange) +
coord_flip() +
theme_light() +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
panel.grid.major.x = element_blank(),
plot.margin = unit(c(1, 0.05, 0.05, 1), "lines")
)
p.blank <- ggplot() +
theme_void() +
theme(plot.margin = unit(rep(0, 4), "lines"))
p.main <- ggplot(df, aes(x, y)) +
geom_point() +
lims(x = xrange, y = yrange) +
theme_light() +
theme(
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
plot.margin = unit(c(1, 1, 0.05, 0.05), "lines")
)
p.bottom <- ggplot(df, aes(x)) +
geom_histogram() +
lims(x = xrange) +
theme_light() +
theme(
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
panel.grid.major.y = element_blank(),
plot.margin = unit(c(0.05, 1, 1, 0.05), "lines")
)
lm <- matrix(1:4, nrow = 2)
grid.arrange(
p.left, p.blank, p.main, p.bottom,
layout_matrix = lm,
widths = c(1, 5),
heights = c(5, 1),
padding = unit(0.1, "line")
)
I am trying to create an arrangement of three scatterplots with shared axes and marginal histograms. This seems like it should be simple, but it's giving me fits. I have tried approaches with gridExtra and gtable, both of which have gotten the general arrangement like I want but with alignments and plot sizes that were off.
There are many other posts related to this question and I have experimented with the answers given to many of them, especially answers from #baptiste here and here. The latter's approach to controlling width might be the key to the alignments, but I haven't understood it well enough to adapt it to my problem.
A minimal working example follows, preceded by its result. The result has quite a few problems:
The bottom right plot is bigger than the other two. This has been an issue with all approaches, including grid.arrange(), with which I placed a blank rectangle grob to balance the scatter plots.
The sizes of the maringal histograms are way off and not what I would expect from the code, which is that their narrow dimension would be about 1/4 the size of the scatter plots' width.
The spacing of the plots is too wide.
None of the marginal histograms align correctly with the scatter plots.
The marginal histogram on the right (c) is so narrow that it doesn't show up at all in the png, yet it does show up (though still too narrow) in the RStudio plot viewer, though only when "zoomed".
Bonus points for an answer that will align the plots' axes correctly even if the width of the axes labels varies between plots as such would be much more adaptable for future problems.
library(ggplot2)
library(grid)
library(gtable)
data <- data.frame(a = rnorm(100, 30, 3), b = rnorm(100, 40, 5), c=rnorm(100, 50, 3))
b_a.scatter <- ggplot(data, aes(x=a, y=b)) + geom_point() + coord_equal(ratio=1, xlim=c(0,100), ylim=c(0,100)) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
plot.margin = unit(c(1,0,-0.5,1), "cm")
)
c_a.scatter <- ggplot(data, aes(x=a, y=c)) + geom_point() + coord_equal(ratio=1, xlim=c(0,100), ylim=c(0,100)) +
theme(plot.margin = unit(c(-0.5,0,0.5,1), "cm"))
c_b.scatter <- ggplot(data, aes(x=b, y=c)) + geom_point() + coord_equal(ratio=1, xlim=c(0,100), ylim=c(0,100)) +
theme(
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = unit(c(-0.5,0,0.5,0), "cm")
)
a.hist <- ggplot(data, aes(x=a)) + geom_histogram() + coord_equal(xlim=c(0,100), ratio=1/4) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = unit(c(0,0,1,1), "cm")
)
b.hist <- ggplot(data, aes(x=b)) + geom_histogram() + coord_equal(xlim=c(0,100), ratio=1/4) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = unit(c(0,0,1,0), "cm")
)
c.hist <- ggplot(data, aes(x=c)) + geom_histogram() + coord_flip(xlim=c(0,100)) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
plot.margin = unit(c(0,1,0,0), "cm")
)
blankPanel <- grid.rect(gp=gpar(col="white"))
gt <- gtable(widths = unit(rep(1,9), "null"),
heights = unit(rep(1,9), "null"),
respect=T)
gl <- list(ggplotGrob(b_a.scatter),
ggplotGrob(c_a.scatter), ggplotGrob(c_b.scatter), ggplotGrob(c.hist),
ggplotGrob(a.hist), ggplotGrob(b.hist))
gt <- gtable_add_grob(gt, gl,
l=c(1,1,5,9,1,5),
r=c(4,4,8,9,4,8),
t=c(1,5,5,5,9,9),
b=c(4,8,8,8,9,9))
grid.newpage()
png('multiplot.png')
grid.draw(gt)
dev.off()
Here's one approach: combine graphs column by column using rbind, then cbind the three columns. Dummy gtables are provided for the empty cells, they only contain layout information.
library(ggplot2)
library(gtable)
d <- data.frame(a = rnorm(100, 30, 3),
b = rnorm(100, 40, 5),
c=rnorm(100, 50, 3))
theme_set(theme_bw() +
theme(plot.background=element_rect(colour="red",size = 2)))
## define simpler plots
a <- ggplot(d, aes(x=a, y=b)) + geom_point() + xlim(0,100)+ ylim(0,90)
b <- ggplot(d, aes(x=a, y=c)) + geom_point() + xlim(0,100) + ylim(0,90)
c <- ggplot(d, aes(x=b, y=c)) + geom_point() + xlim(0,100) + ylim(0,90)
ah <- ggplot(d, aes(x=a)) + geom_histogram() +
xlim(0,100)+ ylim(0,2000000)
bh <- ggplot(d, aes(x=b)) + geom_histogram() +
xlim(0,100) + ylim(0,2000000)
ch <- ggplot(d, aes(x=c)) + geom_histogram() +
coord_flip(xlim=c(0,200000))
pl <- lapply(list(a,b,c,ah,bh,ch), ggplotGrob)
## function to create a dummy table (no grobs, zero size) of the right dim for (r/c)bind
dummy_gtable <- function(g){
gtable(widths=unit(rep(0,ncol(g)), 'null'), heights=unit(rep(0, nrow(g)), 'null'))
}
left <- rbind(pl[[1]],pl[[2]],pl[[4]])
middle <- rbind(dummy_gtable(pl[[1]]),pl[[3]],pl[[5]])
right <- rbind(dummy_gtable(pl[[1]]),pl[[6]], dummy_gtable(pl[[5]]))
grid.newpage()
grid.draw(cbind(left, middle, right))
Note that I'm using cbind and rbind from my experimental fork of gtable, because the released version does not use unit.pmax as unit comparison for widths and heights. Custom functions could be borrowed from another question to keep using the stable gtable version.