ggsave cuts of part of the common legend created with ggarrange - r

I am trying to generate multiple plots from my data by using lapply and then arranging the resulting list with ggarrange. When I try to save the final figure with ggsave part of the legend text is cut off in the png.
First I define what I want to plot along with Plot titles and colors
main.overview <- list(
c("AA", "AA", "black"),
c("X5.HETE", "5-HETE", "red"),)
I then define a function to generate the plots.
plot.overview = function(data, mediator) {
analyte <- mediator[[1]]
name <- mediator[[2]]
color <- mediator[[3]]
ggplot(data = data, aes_string(x="Compound",y=analyte)) +
geom_boxplot(aes(fill=Compound)) +
labs(title=name) +
scale_fill_brewer(palette="Reds") +
theme_classic() +
theme(plot.title = element_text(hjust = 0.5, color = color),axis.title.x = element_blank(),axis.title.y = element_blank())}
Finally I call the function and arrange the plots into a figure
myplots <- lapply(main.overview, plot.overview, data=lm)
arrange <- ggarrange(plotlist = myplots, common.legend = TRUE, nrow=1, legend = "right")
figure <- annotate_figure(arrange, left = text_grob(expression(10^6~cells), rot=90))
ggsave("overview.png", dpi="print", device="png",plot=figure, height=10, width=30, units="cm")
In the final png however the common legend i put on the right is cut off.
EDIT:
I have figured out part of the problem, the problem only occurs on my desktop-pc and not on my laptop, so it might be a problem with additional packages or versions of the R libraries

Related

R ggarrange how to include upper scripts in the plot titles?

I am trying to add plot labels as titles in plots merged by ggarrange. My label contains parantheses and substricpts. I have found that using expression('My volume [m'^3*'/ha]') I can handle both, which works perfectly if placed as x or y label (ylab(expression('My volume [m'^3*'/ha]'))).
However, when using the same approach using ggarrange to combine different plots, and wishing to name them a) and b), the naming prints quotes as well:
How can I correctly write the expression, or using paste('..', '..') approach that quotes are not visible?
Dummy example to create several plots and plot them using ggarrange:
p1 <- ggplot(cars, aes(x=speed, y=dist)) + geom_point() + geom_smooth()
p2 <- p1
ggarrange(p1, p2,
nrow = 2, ncol = 1,
common.legend = TRUE,
legend="bottom",
labels=list(paste("a) ", 'My rate [%]'),
paste('b)', expression('My volume [m'^3*'/ha]'))), # How to change this???
align = c("hv"),
font.label = list(size = 10,
face = "plain",
color ="black"))
Wrong labeled output b:
Finally, I have found my answer based on this post. This works when replacing titles, but I bet it would work for ggarrange as well. Important it that it works with paranteses and a superscript.
p1 + ggtitle(expression(paste("a) ", "volume [m"^3, "/ha", "]")))

How to turn my legend horizontal as opposed to vertical with ggplot2?

I am struggling to understand why legend.horizontal is not rotating my legend axis so it isn't displaying vertically? Any help would be massively appreciated.
library(phyloseq)
library(ggplot2)
##phylum level
ps_tmp <- get_top_taxa(physeq_obj = ps.phyl, n = 10, relative = TRUE, discard_other = FALSE, other_label = "Other")
ps_tmp <- name_taxa(ps_tmp, label = "Unkown", species = T, other_label = "Other")
phyl <- fantaxtic_bar(ps_tmp, color_by = "phylum", label_by = "phylum",facet_by = "TREATMENT", other_label = "Other", order_alg = "as.is")
phyl + theme(legend.direction = "horizontal", legend.position = "bottom", )
Legends for discrete values don't have a formal direction per se and are positioned however ggplot2 decides it can best fit with your data. This is why things like legend.direction won't work here. I don't have the phyloseq package or access to your particular data, so I'll show you how this works and how you can mess with the legend using a reproducible example dataset.
library(ggplot2)
set.seed(8675309)
df <- data.frame(x=LETTERS[1:8], y=sample(1:100, 8))
p <- ggplot(df, aes(x, y, fill=x)) + geom_col()
p
By default, ggplot is putting our legend to the right and organizes it vertically as one column. Here's what happens when we move the legend to the bottom:
p + theme(legend.position="bottom")
Now ggplot thinks it's best to put that legend into 4 columns, 2 rows each. As u/Tech Commodities mentioned, you can use the guides() functions to specify how the legend looks. In this case, we will specify to have 2 columns instead of 4. We only need to supply the number of columns (or rows), and ggplot figures out the rest.
p + theme(legend.position="bottom") +
guides(fill=guide_legend(ncol=2))
So, to get a "horizontally-arranged" legend, you just need to specify that there should be only one row:
p + theme(legend.position="bottom") +
guides(fill=guide_legend(nrow=1))

R - how to allocate screen space to complex ggplot images

I am trying to write a script that produces four different plots in a single image. Specifically, I want to recreate this graphic as closely as possible:
My current script produces four plots similar to these but I cannot figure out how to allocate screen real-estate accordingly. I want to:
modify the height and width of the plots so that all four have uniform width, one is substantially taller than the others which have uniform height among them
define the position of the legends by coordinates so that I can use screen space effectively
modify the overall shape of my image explicitly as needed (maybe I will need it closer to square-shaped at some point)
GENERATE SOME DATA TO PLOT
pt_id = c(1:279) # DEFINE PATIENT IDs
smoke = rbinom(279,1,0.5) # DEFINE SMOKING STATUS
hpv = rbinom(279,1,0.3) # DEFINE HPV STATUS
data = data.frame(pt_id, smoke, hpv) # PRODUCE DATA FRAME
ADD ANATOMICAL SITE DATA
data$site = sample(1:4, 279, replace = T)
data$site[data$site == 1] = "Hypopharynx"
data$site[data$site == 2] = "Larynx"
data$site[data$site == 3] = "Oral Cavity"
data$site[data$site == 4] = "Oropharynx"
data$site_known = 1 # HACK TO FACILITATE PRODUCING BARPLOTS
ADD MUTATION FREQUENCY DATA
data$freq = sample(1:1000, 279, replace = F)
DEFINE BARPLOT
require(ggplot2)
require(gridExtra)
bar = ggplot(data, aes(x = pt_id, y = freq)) + geom_bar(stat = "identity") + theme(axis.title.x = element_blank(), axis.ticks.x = element_blank(), axis.text.x = element_blank()) + ylab("Number of Mutations")
# DEFINE BINARY PLOTS
smoke_status = ggplot(data, aes(x=pt_id, y=smoke, fill = "red")) + geom_bar(stat="identity") + theme(legend.position = "none", axis.title.x = element_blank(), axis.ticks.x = element_blank(), axis.text.x = element_blank()) + ylab("Smoking Status")
hpv_status = ggplot(data, aes(x=pt_id, y = hpv, fill = "red")) + geom_bar(stat="identity") + theme(legend.position = "none", axis.title.x = element_blank(), axis.ticks.x = element_blank(), axis.text.x = element_blank()) + ylab("HPV Status")
site_status = ggplot(data, aes(x=pt_id, y=site_known, fill = site)) + geom_bar(stat="identity")
PRODUCE FOUR GRAPHS TOGETHER
grid.arrange(bar, smoke_status, hpv_status, site_status, nrow = 4)
I suspect that the functions needed to accomplish these tasks are already included in ggplot2 and gridExtra but I have not been able to figure out how. Also, if any of my code is excessively verbose or there is a simpler, more-elegant way to do what I have already done - please feel free to comment on that as well.
Here are the steps to get the layout you describe:
1) Extract the legend as a separate grob ("graphical object"). We can then lay out the legend separately from the plots.
2) Left-align the edges of the four plots so that the left edges and the x-scales line up properly. The code to do that comes from this SO answer. That answer has a function to align an arbitrary number of plots, but I wasn't able to get that to work when I also wanted to change the proportional space allotted to each plot, so I ended up doing it the "long way" by adjusting each plot separately.
3) Lay out the plots and the legend using grid.arrange and arrangeGrob. The heights argument allocates different proportions of the total vertical space to each plot. We also use the widths argument to allocate horizontal space to the plots in one wide column and the legend in another narrow column.
4) Plot to a device in whatever size you desire. This is how you get a particular shape or aspect ratio.
library(gridExtra)
library(grid)
# Function to extract the legend from a ggplot graph as a separate grob
# Source: https://stackoverflow.com/a/12539820/496488
get_leg = function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
legend
}
# Get legend as a separate grob
leg = get_leg(site_status)
# Add a theme element to change the plot margins to remove white space between the plots
thm = theme(plot.margin=unit(c(0,0,-0.5,0),"lines"))
# Left-align the four plots
# Adapted from: https://stackoverflow.com/a/13295880/496488
gA <- ggplotGrob(bar + thm)
gB <- ggplotGrob(smoke_status + thm)
gC <- ggplotGrob(hpv_status + thm)
gD <- ggplotGrob(site_status + theme(plot.margin=unit(c(0,0,0,0), "lines")) +
guides(fill=FALSE))
maxWidth = grid::unit.pmax(gA$widths[2:5], gB$widths[2:5], gC$widths[2:5], gD$widths[2:5])
gA$widths[2:5] <- as.list(maxWidth)
gB$widths[2:5] <- as.list(maxWidth)
gC$widths[2:5] <- as.list(maxWidth)
gD$widths[2:5] <- as.list(maxWidth)
# Lay out plots and legend
p = grid.arrange(arrangeGrob(gA,gB,gC,gD, heights=c(0.5,0.15,0.15,0.21)),
leg, ncol=2, widths=c(0.8,0.2))
You can then determine the shape or aspect ratio of the final plot by setting the parameters of the output device. (You may have to adjust font sizes when you create the underlying plots in order to get the final layout to look the way you want it.) The plot pasted in below is a png saved directly from the RStudio graph window. Here's how you would save the plot as PDF file (but there are many other "devices" you can use (e.g., png, jpeg, etc.) to save in different formats):
pdf("myPlot.pdf", width=10, height=5)
p
dev.off()
You also asked about more efficient code. One thing you can do is create a list of plot elements that you use multiple times and then just add the name of the list object to each plot. For example:
my_gg = list(geom_bar(stat="identity", fill="red"),
theme(legend.position = "none",
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.x = element_blank()),
plot.margin = unit(c(0,0,-0.5,0), "lines"))
smoke_status = ggplot(data, aes(x=pt_id, y=smoke)) +
labs(y="Smoking Status") +
my_gg

Saving ggplots to a list in a for loop

I produce nine ggplots within a for loop and subsequently arrange those plots using grid.arrange:
plot_list <- list()
for(i in c(3:ncol(bilanz.vol))) {
histogram <- ggplot(data = bilanz.vol, aes(x = bilanz.vol[,i])) +
geom_histogram() +
scale_x_log10() +
ggtitle(paste(varnames[i]))
# ggsave(filename = paste("Graphs/", vars[i], ".png", sep = ""), width = 16, height = 12, units = "cm")
plot_list <- c(plot_list, list(histogram))
}
library(gridExtra)
png(filename = "Graphs/non-mfi.png", width = 1280, height = 960, units = "px")
do.call(grid.arrange, c(plot_list, list(ncol = 3)))
dev.off()
The code itself works fine and there are no errors. But for some reason I do not understand, the grid shows the same (last) histogram nine times. Still, each plot shows the correct title.
Interestingly, when I uncomment the ggsave line in the code above, each plot is saved correctly (separately) and shows the expected histogram.
Any ideas?
The reason is that ggplot does not evaluate the expression in the aes call before it is used (so I believe at least), it just sets up the plot and stores the data inside of it. In you case "the data" is the entire data frame bilanz.vol and since i = ncol(bilanz.vol) after the for loop completes the expression bilanz.vol[,i] will evaluate to the same thing for all plot objects.
To make it work you could do this, which makes sure all plot objects contains different data sets my.data.
my.data <- data.frame(x = bilanz.vol[,i])
histogram <- ggplot(data = my.data, aes(x = x)) +
geom_histogram() +
scale_x_log10() +
ggtitle(paste(varnames[i]))

gwidgets ggraphics cutting edge of ggplot

This may be something really obvious, but I am struggling to find a good resource explaining how to use features of gwidgets. With some help I have this script which creates checkboxes which alter a list of file names which are then used to create a plot of the checked files using ggplot. The problem is that the plot is getting cut off at the right edge and I have no idea how to fix this.
EDIT: I see some of you have been busy down-rating me, but now this should work if you run it with the file I provided. I have a suspicion that the problem arises from cairoDevice and the way ggraphics renders the plot.
read.table("foo.csv", header = TRUE, sep = ",", row.names=1)
ggplot(MeanFrameMelt, aes(x=variable, y=value, color=Legend, group=Legend))+
geom_line()+
theme(panel.background = element_rect(fill='NA', colour='black', size = 1),
legend.position = "none")+
ylab("Tag Density (mean coverage/bp)")+
xlab("Distance from reference side (bp)")+
scale_x_discrete(breaks=c("V1", "V200", "V400"), labels=c("-10000", "0", "10000"))+
GraphFiles <- FileNamesOrig
w <- gwindow("Tag Density Checkboxes", width = 1000)
g <- ggroup(container = w, horizontal = FALSE)
add(g, ggraphics())
lyt <- glayout(container = g, horizontal = FALSE)
print(p)
foo.cvs (this is the MeanFrameMelt)
EDIT 2:
This is what the graph looks like for me. I don't know what is going on, I am exporting the data.frame with this command:
write.table(MeanFrameMelt, file="test.cvs", sep=",", col.names=TRUE)
but then when I run it with the exported file I get exactly what agstudy got. The files are supposed to be identical.
EDIT 3:
Tested it with gput (thank you for the suggestion) and now its creating the correct plot:
New file
Use dget(file="test.txt")
I just reorganized your code, but I can't reproduce the problem. You have to call the plot actions inside a handelr to interact later with user(e.g zoom , mouse events). I show an example here.
First time you run you have the plot with an ugly axis. Then when you click in a region , the plot is refreshed and you have a nice axis.
## I define my plot
p <- ggplot(MeanFrameMelt, aes(x=variable, y=value, color=Legend, group=Legend))+
geom_line()+
theme(panel.background = element_rect(fill='NA', colour='black', size = 1),
legend.position = "none")+
ylab("Tag Density (mean coverage/bp)")+
xlab("Distance from reference side (bp)")
## init gwidgets
library(gWidgetsRGtk2)
w <- gwindow("Tag Density Checkboxes", width = 1000)
g <- ggroup(container = w, horizontal = FALSE)
gg <- ggraphics(container=g)
lyt <- glayout(container = g, horizontal = FALSE)
## I plot it the first time
print(p)
## I add a handler
ID <- addHandlerChanged(gg, handler=function(h,...) {
p <- p + scale_x_discrete(breaks=c("V1", "V200", "V400"),
labels=c("-1000", "0", "1000"))
print(p)
})
print(p)

Resources