saving each modified facet in ggplot2 - r

I try to save each Species data in iris data set to .png file using for loop. But before, that I would like to modify facet strip thickness as I needed to do in my real data plotting process.
However, when I attempted to write each facet
the following code below it just giving me the empty plots for each of these Species.
Here is my attempt,
library(ggplot2)
plot_list = list()
for (i in unique(iris$Species)) {
p = ggplot(iris[iris$Species == i, ], aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(size=3, aes(colour=Species))+
facet_wrap(~Species)
#this part to modify facet_wrap strips
g1 = ggplotGrob(p)
pos = c(unique(subset(g1$layout, grepl("panel", g1$layout$name), select = t)))
for(i in pos) g1$heights[i-1] = unit(0.4,"cm")
grobs = which(grepl("strip", g1$layout$name))
for(i in grobs) g1$grobs[[i]]$heights <- unit(1, "npc")
grid.newpage()
grid.draw(g1)
plot_list[[i]] = g1
}
#finally write the modified graphs to file
for (i in 1:3) {
file_name = paste("iris_plot_", i, ".png", sep="")
tiff(file_name)
print(plot_list[[i]])
dev.off()
}
Currently this code is producing the empty graphs and do not know why! Any help will be appreciated!

You don't need to modify strip height using ggplotGrob. Setting the relevant parameter in ggplot's theme() would do:
p1 = ggplot(iris[iris$Species == "setosa",],
aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
facet_wrap(~Species)
p2 = p1 + theme(strip.text.x = element_text(margin = margin(t = 10, b = 10)))
# note: default margin for top & bottom is 5.5
gridExtra::grid.arrange(p1, p2, ncol = 2)
As for the rest, you may wish to check the length of plot_list after the first loop. You initially assigned i to take on the unique values of iris$Species, then tried to use it as index for the list of plots. The first three elements of plot_list did not contain plots.
The following would work in this example. You probably need to make some modifications for the actual use case:
plot_list = list()
loop.list <- unique(iris$Species)
for (i in seq_along(loop.list)) {
p = ggplot(iris[iris$Species == loop.list[i], ],
aes(x = Sepal.Length, y=Sepal.Width)) +
geom_point(size = 3, aes(colour = Species))+
facet_wrap(~Species) +
theme(strip.text.x = element_text(margin = margin(t = 11, b = 11)))
plot_list[[i]] <- ggplotGrob(p)
}
for (i in 1:3) {
file_name = paste("iris_plot_", i, ".png", sep="")
tiff(file_name)
grid.draw(plot_list[[i]])
dev.off()
}

Related

R: ggplot - Different Title for each object in a plot list

currently I am trying to making a plot list with ggplot from a list of data frames (94 time series). Then I want to export the plots to a PDF. This, so far, was successful using following code:
plot.list = lapply(HR_clean, function(x) {
y = length(x)
z = data.frame("HR" = x, "Time" = rep(1:y, 1))
ggplot(z, aes(x = Time, y = HR)) +
theme_bw() +
geom_line(linetype = "solid") +
ggtitle("Plot Title")
})
ggsave(
filename = "plots2.pdf",
plot = marrangeGrob(plot.list, nrow=1, ncol=1),
width = 15, height = 9
)
However, I also want that the main title of each plot is equal name of the corresponding list object. Perhaps anyone knows a smart solution for this problem.
Best,
Johnson
I couldn't test this (since you are not providing example data) but this should work using purrr's imap():
plot.list <- purrr::imap(HR_clean, function(x, name) {
y <- length(x)
z <- data.frame("HR" = x, "Time" = rep(1:y, 1))
ggplot(z, aes(x = Time, y = HR)) +
theme_bw() +
geom_line(linetype = "solid") +
ggtitle(name)
})

Save grid.arrange output in loop and print to word (ReporteRs) or PDF files

I have checked the solution listed in this link:
Saving grid.arrange() plot to file
but cannot apply it to my problem, the solution is not clear.
I have a loop which creates four plots for each i, which are then arranged in a grid.arrange:
for (i in 1:n)){
p1<- ggplot(predictors, aes(x = x1, y = x2)) + geom_point()
p2<- ggplot(temp) + geom_histogram(aes(x=value)) +
facet_grid(. ~ variable, scales = "free_x")
p3<- ggplot(predictors_trans) + geom_point(aes(x = trans.x1, y = trans.x2))
p4<- ggplot(temp) + geom_histogram(aes(x=value), data = temp) +
facet_grid(. ~ variable, scales = "free_x")
###arrange plots in grid:
plot_list[[i]] = (grid.arrange(p1, p2, p3, p4))
###write grid to doc via ReporteRs package
mydoc2 = addPlot( doc = mydoc2, fun = print, x = plot_list[[i]],
vector.graphic = TRUE, par.properties = parCenter(), width = 6, heigth =
###save all images to directory as well
ggsave(filename=paste("myplot",i,".jpg",sep=""), plot_list[[i]])
}
the plots are generated and saved, and but the word document generated is empty after it mydoc2 is written to it:
writeDoc( mydoc2, "why224.docx")
browseURL( "why224.docx" )
I also tried writing just the images to a PDF, which turns out empty:
pdf("plots61.pdf")
for (i in 1:n) {
print((plot_list[[i]]))
}
dev.off()
if I remove x=plot_list[[i]] and set x=grid.arrange(p1,p2,p3,p4) in the addPlot command, I get a word document with a blank image inside:
Does anyone have any solutions as to how to print grid.arrange object or ggsave results of a loop to a document (word or pdf)? thanks.
With ggsave use arrangeGrob. This loop saves grids to a separate pdf for each iteration:
library(ggplot2)
library(gridExtra)
for(i in 1:4) {
p1 <- qplot(1, 1, geom = "point")
p2 <- qplot(1, 1, geom = "point")
p3 <- qplot(1, 1, geom = "point")
p4 <- qplot(1, 1, geom = "point")
g <- arrangeGrob(p1, p2, p3, p4)
ggsave(file = paste0("myplot", i, ".pdf"), g)
}

Adding legend for list of ggplots created in loop

I am creating a list of plots (without legend) by using a loop in ggplot2. Then I created a legend separately and trying to print a combined plots by using grid.arrange and grobs function. It creates the combined plot but without the legend. Could anyone please help to solve the problem?
I am attaching my code here:
df1<-data.frame(x=1:10,y1=rnorm(10),y2=rnorm(10),y3=rnorm(10),y4=rnorm(10),y5=rnorm(10))
df2 <- melt(df1,id.vars="x")
plot.list = list()
for (i in 1:3){
p <- ggplot(df2, aes(x=x, y=value)) +
geom_line(aes(colour=variable, group=variable))+
theme(legend.position='none')
plot.list[[i]] = p
}
temp_legend <- ggplot(df2, aes(x=x, y=value)) +
geom_line(aes(colour=variable, group=variable)) +
scale_color_manual("",labels = c("Observed","3d","5d","7d","10d"), values = c("black", "limegreen","blue","tan1","red3")) +
theme(legend.direction = "horizontal",
legend.position = "bottom")
library(gridExtra)
# create get_legend function
get_legend<-function(myggplot){
tmp <- ggplot_gtable(ggplot_build(myggplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)
}
# Extract legend using get_legend function
legend <- get_legend(temp_legend)
# png export
png("Output.png", width = 5, height = 6.35, units = 'in', res = 300)
grid.arrange(grobs=plot.list,legend,
ncol=1, nrow = 4,
widths = 6, heights = c(2,2,2,0.35))
dev.off()
grid.arrange(grobs=c(plot.list,list(legend)),
ncol=1, heights = c(2,2,2,0.35))
or simply
grid.arrange(grobs=plot.list, ncol=1, bottom = legend)

for loop issue in ggplot2

I would like to output multiple graphs from ggplot2. I found very nice examples but still could not find what I want to achieve.
r-saving-multiple-ggplots-using-a-for-loop
by using similar example, I only want to output three different graphs for each species so far I am outputting same three combined graph.
library(dplyr)
fill <- iris%>%
distinct(Species,.keep_all=TRUE)
plot_list = list()
for (i in 1:length(unique(iris$Species))) {
p = ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(size=3, aes(colour=Species))
geom_rect(data=fill,aes(fill = Petal.Width),xmin = -Inf,xmax = Inf,ymin = -Inf,ymax = Inf,alpha = 0.5)+ ## added later to OP
plot_list[[i]] = p
}
# Save plots to tiff. Makes a separate file for each plot.
for (i in 1:3) {
file_name = paste("iris_plot_", i, ".tiff", sep="")
tiff(file_name)
print(plot_list[[i]])
dev.off()
}
What I am missing?
You are not filtering the dataset inside the for-loop, and so the same graph is repeated three times without change.
Note the changes here:
plot_list = list()
for (i in unique(iris$Species)) {
p = ggplot(iris[iris$Species == i, ], aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(size=3, aes(colour=Species))
plot_list[[i]] = p
}
Addressing the OP update, this works for me:
fill <- iris %>%
distinct(Species, .keep_all=TRUE)
for (i in unique(iris$Species)) {
p = ggplot(iris[iris$Species == i, ], aes(x=Sepal.Length, y=Sepal.Width)) +
geom_rect(data = fill, aes(fill = Petal.Width),xmin = -Inf,xmax = Inf,ymin = -Inf,ymax = Inf,alpha = 0.5) +
geom_point(size = 3, aes(colour = Species))
plot_list[[i]] = p
}

Customizing plots within a function (ggplot2)

I've written a function that generates a survival plot to my liking using ggplot2. I would like the plot to be customizable, but because I am making a panel of three plots, I can't simply return the plot object to add additional customizations.
I've successfully managed to accomplish what I want using character strings (see the function at the end of the question), but I was wondering if there was a better way to do this; using the character string seems kind of foreign.
For example, the function currently allows me to do this:
require(survival)
fit <- survfit(Surv(time, status) ~ x, data=aml)
ggSurvGraph(fit, times=seq(0, 60, by=12), offset.scale=1, xlim=c(0, 60),
gg_expr="ylab('Percentage Survived') + xlab('Time Elapsed') +
scale_y_continuous(breaks=seq(0, 1.0, by=.25), labels=seq(0, 100, by=25))")
ggexpr gets added to the plot by
if (!missing(gg_expr)) .plot <- eval(parse(text=paste(".plot + ", gg_expr, sep="")))
But it seems to me it would be more natural to pass an expression in gg_expr rather than a character string. Such as this:
require(survival)
fit <- survfit(Surv(time, status) ~ x, data=aml)
ggSurvGraph(fit, times=seq(0, 60, by=12), offset.scale=1, xlim=c(0, 60),
gg_expr=ylab('Percentage Survived') + xlab('Time Elapsed') +
scale_y_continuous(breaks=seq(0, 1.0, by=.25), labels=seq(0, 100, by=25)))
Is there a better way to approach this? (a copy of what the plot looks like follows the function code)
ggSurvGraph <- function(object, times, cum.inc=FALSE, conf.bar=TRUE,
offset.scale=1, n.risk=FALSE, n.event=FALSE,
xlim, gg_expr){
require(stringr)
require(plyr)
require(ggplot2)
require(gridExtra)
require(survival)
#**************************************************************
#*** Parameter checking
error.count <- 0
error.msg <- NULL
#*** 'object' should be either a 'survfit' object or a 'data.frame'
if (!(any(class(object) %in% c("survfit","data.frame")))){
error.count <- error.count + 1
error.msg <- c(error.msg, str_c(error.count, ": \'object\' must be either a survfit object or a data frame", sep=""))
}
#*** When 'object' is a data frame, it must have the columns in 'req.col'
#*** This is a feature that was added so that we could make survival graphs with PROC LIFETEST output
req.col <- c("time","surv","lower","upper","n.risk","n.event")
if ("data.frame" %in% class(object) && !any(req.col %in% names(object))){
miss.col <- str_c("\'", req.col[!req.col %in% names(object)], "\'", sep="", collapse=", ")
error.count <- error.count + 1
error.msg <- c(error.msg, str_c(error.count, ": data frame \'object\' is missing columns ", miss.col, sep=""))
}
#*** Stop the function if any parameter checks failed
if (error.count){
stop(str_c(error.msg, collapse="\n"))
}
#********************************************************************
#*** Prepare the data for plotting
#*** Create data frame from survfit object
if ("survfit" %in% class(object)) survData <- createSurvivalFrame(object) else survData <- object
if (is.null(survData$strata)) survData$strata <- factor(1)
if (cum.inc) survData <- transform(survData,
surv = 1-surv,
lower = 1-lower,
upper = 1-upper)
survData <- ddply(survData,
"strata",
transform,
cum.evt = cumsum(n.event))
#*** Generate offset values
if(nlevels(survData$strata)>1){
offset <- seq.int(-1*ceiling(nlevels(survData$strata)/2),ceiling(nlevels(survData$strata)/2),length.out=nlevels(survData$strata)+1)
offset <- offset[offset!=0]
offset <- offset[order(abs(offset))] * offset.scale
}
else offset <- 0
offset <- data.frame(strata = levels(survData$strata), offset = offset)
survData <- merge(survData, offset, by="strata")
#*************************************************************
#* Limit to 'times' argument
extractSurvTimes <- function(df, reportTime=times){
.out <- df[sapply(reportTime, function(t) max(which(df$time <= t))), ]
.out$reportTime <- reportTime
return(.out)
}
survData <- transform(survData, reportTime = time)
survTimes <- if (missing(times)) survData
else do.call("rbind", lapply(levels(survData$strata), function(x) extractSurvTimes(subset(survData, strata==x))))
if (missing(xlim)) xlim <- c(0, max(survData$time, na.rm=TRUE))
#*************************************************************
#*** Create Plot
#*** Creates a blank plot for a spacer between survival plot and risk/event data
blank.pic <- ggplot(survData, aes(time, surv)) +
geom_blank() + theme_bw() +
theme(axis.text.x = element_blank(), axis.text.y = element_blank(),
axis.title.x = element_blank(), axis.title.y = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(), panel.border = element_blank())
#*** Create the survival plot
if (nlevels(survData$strata) > 1){
.plot <- ggplot(survData, aes(x=time, y=surv, colour=strata)) + geom_step()
}
else{
.plot <- ggplot(survData, aes(x=time, y=surv)) + geom_step()
}
.plot <- .plot + scale_x_continuous(limits = xlim)
#*** Add Confidence bars
if (conf.bar){
.plot <- .plot +
geom_segment(data=survTimes, aes(x=reportTime + offset, xend=reportTime + offset, y=lower, yend=upper))
}
if (!missing(gg_expr)) .plot <- eval(parse(text=paste(".plot + ", gg_expr, sep="")))
riskTable <- survTimes
riskTable <- melt(riskTable[, c("reportTime", "strata", "n.risk", "cum.evt")],
c("reportTime", "strata"))
riskTable <- transform(riskTable,
y.pos = ifelse(variable %in% "n.risk", 1, 0))
.risk <- ggplot(survData, aes(x=time, y=surv)) +
geom_text(data=riskTable, aes(x=reportTime, y=rev(variable), label=value), size=3.5, hjust=0) +
theme_bw() +
# scale_y_discrete(breaks = as.character(levels(riskTable$strata)),
# labels = levels(riskTable$variable)) +
theme(axis.text.x = element_blank(),
axis.title.x = element_blank(), axis.title.y = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(), panel.border = element_blank()) +
scale_x_continuous(limits = xlim) +
scale_y_discrete(labels=c("N Event", "N at Risk"))
if (nlevels(riskTable$strata) > 1) .risk <- .risk + facet_wrap(~ strata, ncol=1)
grid.arrange(.plot + theme(plot.margin = unit(c(1,1,0,.5), "lines"), legend.position="bottom"),
blank.pic + theme(plot.margin = unit(c(0,0,0,0), "lines")),
.risk + theme(plot.margin = unit(c(0,1,0,0), "lines")),
clip = FALSE, nrow = 3,
ncol = 1, heights = unit(c(.70, .04, .35),c("null", "null", "null")))
}
I don't understand your question, but I have a feeling the list() syntax might help,
p = qplot(1:10, 1:10)
p + list(ylab("label"),
scale_x_continuous(),
geom_line())
Abstract
This answer allows you to add ggplot objects to the return value of your function in standard ggplot form. Here, we add coord_cartesian to the first graph of a function that returns two graphs (yours would return a graph and a table, but same idea):
my_plots() + coord_cartesian(ylim=c(0, 5))
Also, note you could add any ggplot object. We just chose coord_cartesian because it was convenient. In your case, you would modify ggSurvGraph to operate like my_plots, which should be pretty simple.
Details
The strategy relies on the function not using grid.arrange, but rather, on having the print method for the object your function returns use grid.arrange. I made up a function that makes a barplot and a scatter plot, but I think it illustrates the point well.
library(gridExtra)
library(ggplot2)
my_plots <- function() {
df <- data.frame(x=1:10, y=(1:10)/10)
gg1 <- ggplot(df) + geom_point(aes(x=x, y=y))
gg2 <- ggplot(df) + geom_bar(aes(x=x, y=y), stat="identity")
structure(list(gg1, gg2), class="myplots")
}
The key here is what I'm returning is a list of the ggplot objects, with a custom class myplots here. Then, I can just define a print method for that class, and have grid.arrange do it's thing:
print.myplots <- function(x, ...) {
do.call(grid.arrange, x)
}
my_plots() outputs the graphs through the print method (note the key point here is that you have two ggplot objects; I realize for yours one of them is a table, but the net result is the same):
my_plots()
And now, I can define a + method, which adds whatever the second operand is to the first value in my list (so in your case, this would only affect the graph, not the table):
`+.myplots` <- function(e1, e2) {
e1[[1]] <- e1[[1]] + e2
invisible(e1)
}
Now, we can use as illustrated in the abstract (notice how the Y axis changed for the first graph):
my_plots() + coord_cartesian(ylim=c(0, 5))

Resources