Vertically combing multiple bar plots - r

I'm trying to use to vertically group bar plots, sharing their x-axes.
I thought of using R's plotly's subplot for that but running into an issue I hope someone here may have a solution for.
Here are example data which have 28 groups where I'm creating a bar plot over 4 families in each group and then trying to vertically combine them using plotly::subplot:
set.seed(1)
df <- data.frame(group = paste0("G",unlist(lapply(1:28,function(i) rep(i,4)))),
family = paste0("F",rep(1:4,28)),
log2n = log2(as.integer(runif(4*28,0,30))+1),
stringsAsFactors = F)
Creating the list of bar plots:
library(plotly)
library(dplyr)
groups <- unique(df$group)
y.range <- c(0,max(df$log2n))
plot.list <- lapply(1:length(groups),function(g){
group.df <- dplyr::filter(df,group == groups[g])
plot_ly(x=group.df$family,y=group.df$log2n,type='bar',name=group.df$family,color=group.df$family,showlegend=(g==length(groups))) %>%
layout(yaxis=list(range=y.range))
})
If I try:
plotly::subplot(plot.list,shareX=T,nrows=length(plot.list))
I get:
So it seems like some sort of an overflow.
I gradually cut down on the number of plots in plot.list that I run subplot on and when reached 19 it seemed to stop 'overflowing':
plotly::subplot(plot.list[1:19],shareX=T,nrows=19)
Any idea if there's hope to get all 28 bar plots without overflowing?
Thanks a lot

I would generate the figure with ggplot and then convert it to plotly (or save it as a picture file) with proper size arguments.
library(plotly)
library(tidyverse)
g <- ggplot(df,
aes(x = family, y = log2n, fill = family)) +
geom_bar(stat = 'identity') +
facet_wrap(~group, ncol = 1) +
theme_minimal() +
theme(legend.position = "none")
ggsave(g, file = "temp.png", width = 4, height = 40)
ggplotly(g, width = 400, height = 4000)

Related

ggplot: align multiple faceted plots - facets all different sizes

I am trying to align multiple plots with facets. My problem is somewhat minor but irratating: I can make a plot so that the plot areas are aligned and the facets themselves are aligned, but the facet strips are not all the same width. If the labels of the facets are different lengths, then the facet strips are sized so that the text can fit within the facets. I am unable so far to find a way to make all facet strips the same width when aligning multiple plots.
Here is an example of the type of plots I want to align and my efforts to align them:
library(data.table)
library(ggplot2)
library(foreach)
library(stringr)
library(cowplot)
# example data to show how aligning faceted plots is not quite right
plotvars = c(paste0("plot1_var", 1:7), paste0("plot2_var",1:5), paste0("plot3_var",1:10))
data =
foreach(p=plotvars,.combine = "rbind") %do% {
d = data.table(plot = rep(str_extract(p,pattern = "plot[[:digit:]]"),2),
plot_variables = rep(p,2),
fill_categories = c("fill1","fill2"),
number = sample(1:1000, size = 2))
d[, facet_variables := ifelse(plot=="plot1",
rep(sample(paste0("facet",1:3),size=1),2),
ifelse(plot=="plot2",
rep(sample(paste0("facet_title",1:3),size=1),2),
ifelse(plot=="plot3",
rep(sample(paste0("facet_title_longer",1:3),size=1),2),
NA)))]
d
}
# function to make stacked barplots with facets + coord_flip
make_plot = function(data, plot_var) {
ggplot(data[plot==plot_var],
aes(x=plot_variables,
y=number,
fill=fill_categories))+
geom_bar(stat="identity")+
coord_flip()+
facet_grid(facet_variables ~ .,
space="free",
scales="free")+
theme(strip.text.y = element_text(angle=0),
legend.position = "none")
}
p1 = make_plot(data=data,plot_var="plot1")
p1
p2 = make_plot(data=data,plot_var="plot2")
p2
p3 = make_plot(data=data,plot_var = "plot3")
p3
# using 'cowplot::plot_grid' gives strange re-sizing of individual bars
cowplot::plot_grid(p1,p2,p3, ncol=1,nrow=3,align = "hv")
# try gtable_rbind version
g1=ggplotGrob(p1)
g2=ggplotGrob(p2)
g3=ggplotGrob(p3)
# this plot keeps the bar widths the correct size, but the facets are still incorrectly different widths.
ggdraw(gridExtra::gtable_rbind(g1,g2,g3))
How can I make the facet strips the same width across plots?
You can achieve something like this with a labeller function that inserts a second row of blank spaces of whatever length you want. Using mtcars...
#define a function to add a second line of spaces after a given label
#and a blank line before to maintain the centre vertical alignment
#you might need to play with the appropriate value to get the width right
widen <- function(x) paste(" \n", x, "\n", paste0(rep(" ", 20), collapse=""))
mtcars %>% ggplot(aes(x = mpg)) +
geom_histogram() +
facet_grid(cyl ~ ., labeller = labeller(cyl = widen)) +
coord_flip() +
theme(strip.text.y = element_text(angle = 0))
The facet strips are wrapped inside another table, and you need to adjust the widths there. The following seems to work.
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
g3 <- ggplotGrob(p3)
# g3 has the widest strips, so get the width from there and copy over
# to the other plots
stripwidth <- g3$grobs[[13]]$widths
g1$grobs[[13]]$widths <- stripwidth
g1$grobs[[14]]$widths <- stripwidth
g1$grobs[[15]]$widths <- stripwidth
g2$grobs[[13]]$widths <- stripwidth
g2$grobs[[14]]$widths <- stripwidth
g2$grobs[[15]]$widths <- stripwidth
ggdraw(gridExtra::gtable_rbind(g1,g2,g3))
Change this part
facet_grid(facet_variables ~ .,
space="free",
scales="free")+
to
facet_grid(facet_variables ~ .,
space="fixed", # This is the difference
scales="free")+

arbitrary number of plots for grid.arrange

I'm trying to plot an arbitrary number of bar plots with rmarkdown separated by 2 columns. In my example there will be 20 total plots so I was hoping to get 10 plots in each column, however, I can't seem to get this to work with grid.arrange
plot.categoric = function(df, feature){
df = data.frame(x=df[,feature])
plot.feature = ggplot(df, aes(x=x, fill = x)) +
geom_bar() +
geom_text(aes(label=scales::percent(..count../1460)), stat='count', vjust=-.4) +
labs(x=feature, fill=feature) +
ggtitle(paste0(length(df$x))) +
theme_minimal()
return(plot.feature)
}
plist = list()
for (i in 1:20){
plist = c(plist, list(plot.categoric(train, cat_features[i])))
}
args.list = c(plist, list(ncol=2))
do.call("grid.arrange", args.list)
When I knit this to html I'm getting the following output:
I was hoping I would get something along the lines of:
but even with this the figure sizes are still funky, I've tried playing with heights and widths but still no luck. Apologies if this is a long question
If you have all the ggplot objects in a list then you can easily build the two column graphic via gridExtra::grid.arrange. Here is a simple example that will put eight graphics into a 4x2 matrix.
library(ggplot2)
library(gridExtra)
# Build a set of plots
plots <-
lapply(unique(diamonds$clarity),
function(cl) {
ggplot(subset(diamonds, clarity %in% cl)) +
aes(x = carat, y = price, color = color) +
geom_point()
})
length(plots)
# [1] 8
grid.arrange(grobs = plots, ncol = 2)

Symmetrical histograms

I want to make a number of symmetrical histograms to show butterfly abundance through time. Here's a site that shows the form of the graphs I am trying to create: http://thebirdguide.com/pelagics/bar_chart.htm
For ease, I will use the iris dataset.
library(ggplot2)
g <- ggplot(iris, aes(Sepal.Width)) + geom_histogram(binwidth=.5)
g + coord_fixed(ratio = .003)
Essentially, I would like to mirror this histogram below the x-axis. Another way of thinking about the problem is to create a horizontal violin diagram with distinct bins. I've looked at the plotrix package and the ggplot2 documentation but don't find a solution in either place. I prefer to use ggplot2 but other solutions in base R, lattice or other packages will be fine.
Without your exact data, I can only provide an approximate coding solution, but it is a start for you (if you add more details, I'll be happy to help you tweak the plot). Here's the code:
library(ggplot2)
noSpp <- 3
nTime <- 10
d <- data.frame(
JulianDate = rep(1:nTime , times = noSpp),
sppAbundance = c(c(1:5, 5:1),
c(3:5, 5:1, 1:2),
c(5:1, 1:5)),
yDummy = 1,
sppName = rep(letters[1:noSpp], each = nTime))
ggplot(data = d, aes(x = JulianDate, y = yDummy, size = sppAbundance)) +
geom_line() + facet_grid( sppName ~ . ) + ylab("Species") +
xlab("Julian Date")
And here's the figure.

Moving table created by annotation_custom with geom_bar plot

I tried searching for answers but couldn't find anything.
I have have a plot and want to add a table within the plot itself. I can do it but the table ends up being right in the middle.
It is possible to relocate a table created by annotation_custom if the x-axis is discrete? If so, how?
Thank you!
For example, I want to relocate this table.
library(ggplot2)
library(gridExtra)
my.summary <- summary(chickwts$weight)
my.table <- data.frame(ids = names(my.summary), nums = as.numeric(my.summary))
ggplot(chickwts, aes(feed, weight)) +
geom_bar(stat = "identity") +
annotation_custom(tableGrob(my.table))
The custom annotation in ggplot2 can be rearragned inside the plotting area. This at least moves them out of the center. Maybe this solution is already sufficient for you. I'll try and tweak this. It should be possible to put this outside the plotting area as well.
library(ggplot2)
library(gridExtra)
my.summary <- summary(chickwts$weight)
my.table <- data.frame(ids = names(my.summary), nums = as.numeric(my.summary))
ggplot(chickwts, aes(feed, weight)) +
geom_bar(stat = "identity") +
annotation_custom(tableGrob(my.table), xmin=5,xmax=6,ymin=300,ymax=1300)
EDIT:
To place the table outside the plot, regardless of what the plot consists of, the grid package could be used:
library(ggplot2)
library(gridExtra)
library(grid)
# data
my.summary <- summary(chickwts$weight)
my.table <- data.frame(ids = names(my.summary), nums = as.numeric(my.summary))
# plot items
my.tGrob <- tableGrob(my.table)
plt <- ggplot(chickwts, aes(feed, weight)) +
geom_bar(stat = "identity")
# layout
vp.layout <- grid.layout(nrow=1, ncol=2, heights=unit(1, "null"),
widths=unit(c(1,9), c("null","line")) )
# start drawing
grid.newpage()
pushViewport(viewport(layout=vp.layout, name="layout"))
# plot
pushViewport(viewport(layout.pos.row=1, layout.pos.col=1, name="plot"))
print(plt, newpage=FALSE)
upViewport()
# table
pushViewport(viewport(layout.pos.row=1, layout.pos.col=2, name="table"))
grid.draw(my.tGrob)
upViewport()
#dev.off()

Keep all plot components same size in ggplot2 between two plots

I would like two separate plots. I am using them in different frames of a beamer presentation and I will add one line to the other (eventually, not in example below). Thus I do not want the presentation to "skip" ("jump" ?) from one slide to the next slide. I would like it to look like the line is being added naturally. The below code I believe shows the problem. It is subtle, but not how the plot area of the second plot is slightly larger than of the first plot. This happens because of the y axis label.
library(ggplot2)
dfr1 <- data.frame(
time = 1:10,
value = runif(10)
)
dfr2 <- data.frame(
time = 1:10,
value = runif(10, 1000, 1001)
)
p1 <- ggplot(dfr1, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2))
print(p1)
dev.new()
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(".")
print(p2)
I would prefer to not have a hackish solution such as setting the size of the axis label manually or adding spaces on the x-axis (see one reference below), because I will use this technique in several settings and the labels can change at any time (I like reproducibility so want a flexible solution).
I'm searched a lot and have found the following:
Specifying ggplot2 panel width
How can I make consistent-width plots in ggplot (with legends)?
https://groups.google.com/forum/#!topic/ggplot2/2MNoYtX8EEY
How can I add variable size y-axis labels in R with ggplot2 without changing the plot width?
They do not work for me, mainly because I need separate plots, so it is not a matter of aligning them virtically on one combined plot as in some of the above solutions.
haven't tried, but this might work,
gl <- lapply(list(p1,p2), ggplotGrob)
library(grid)
widths <- do.call(unit.pmax, lapply(gl, "[[", "widths"))
heights <- do.call(unit.pmax, lapply(gl, "[[", "heights"))
lg <- lapply(gl, function(g) {g$widths <- widths; g$heights <- heights; g})
grid.newpage()
grid.draw(lg[[1]])
grid.newpage()
grid.draw(lg[[2]])
How about using this for p2:
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() +
scale_y_continuous(breaks = NULL) +
scale_x_continuous(breaks = NULL) +
ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2)) +
theme(axis.title.y=element_text(color=NA))
This has the same label as p1, but the color is NA so it doesn't display. You could also use color="white".

Resources