I use markdown documents for my analyses. I create lots of plots and use
knitr::opts_chunk$set(dev= c("png", "svg", "pdf")
and rmarkdown::render(... , clean = FALSE)
to obtain png ( for use with google slides) , svg (for use with powerpoint) and pdf (to be included in manual latex reports). However, I would now like to combine plots from different analyses into figure panels while being able to change plot sizes and aspect ratios without rerunning all the analyses each time.
One way to achieve this is to save the ggplots in .rds
files using saveRDS(ggplot2::last_plot(), "figure_1.rds")
in the analysis notebooks and library(patchwork); readRDS("figure_1.rds") / readRDS("figure_2.rds")
in a separate script generating the figure panels. This could partially be automated using a hook:
example_analysis.Rmd
```{r setup}
knitr::opts_chunk$set(dev= c("png", "svg", "pdf")
knitr::knit_hooks$set(hook_save_plot_as_rds = function(before, options, envir, name) {
if(before) return() # run only after chunk
if(length(knitr:::get_plot_files())==0) return() # only run if
saveRDS(ggplot2::last_plot(), knitr::fig_chunk(knitr::opts_chunk$get("label"), ext = "rds"))
})
```
Here we do some heavy analysis
```{r sepal-plot}
ggplot(iris, aes(Sepal.Width, Sepal.Length)) + geom_point()
```
Here we do some more heavy analysis
```{r petal-plot}
ggplot(iris, aes(Petal.Width, Petal.Length)) + geom_point()
```
Somewhere else:
library(patchwork)
get_figure <- function(name) readRDS(paste0("example_analysis_files/figure-html/", name, "-1.rds"))
get_figure("petal-plot") / get_figure("sepal-plot") + plot_annotation(tag_levels="A")
But that only works for the last ggplot of each chunk. Is there a way that works for all plots of chunk? Is there maybe a hidden device="rds"
?
The solution is actually quite simple: the right place to do this is not an extra device or some hooks, but the print function. knitr provides the knit_print
S3 generic that gives control over how R objects are printed by knitr. One can simply add a knit_print.ggplot
that also saves the plot.
I knew yihui must have made this possible.
Full example:
example_analysis.Rmd
---
title: "example_analysis"
output:
html_document:
keep_md: yes
---
```{r setup}
local({ # to keep global environment uncluttered
counter <- NA
previous_label <- NA
print_and_save.ggplot <- function(x, ...) {
ret <- print(x, ...)
current_label <- knitr::opts_current$get("label")
if(isTRUE(previous_label==current_label)) {
counter <<- counter+1 # keep track of plot number
} else { # reset plot number for each new chunk
previous_label <<- current_label
counter <<- 1
}
dir.create(knitr::opts_current$get("fig.path"), recursive = TRUE, showWarnings = FALSE)
saveRDS(ret, knitr::fig_path(suffix = "rds", number = counter))
invisible(ret)
}
library(knitr)
registerS3method("knit_print", "ggplot", print_and_save.ggplot)
})
library(ggplot2)
```
Here we do some heavy analysis
```{r sepal-plot}
ggplot(iris, aes(Sepal.Width, Sepal.Length, fill= Species)) + geom_point()
ggplot(iris, aes(Sepal.Length, fill= Species)) + geom_histogram() + coord_flip()
```
Here we do some more heavy analysis
```{r petal-plot}
ggplot(iris, aes(Petal.Width, Petal.Length, fill= Species)) + geom_point()
ggplot(iris, aes(Petal.Length, fill= Species)) + geom_histogram() + coord_flip()
```
somewhere else:
library(patchwork)
get_figure <- function(name, idx = 1) readRDS(paste0("example_analysis_files/figure-html/", name, "-", idx,".rds"))
(get_figure("petal-plot") + get_figure("petal-plot", 2)) / (get_figure("sepal-plot") + get_figure("sepal-plot", 2)) + plot_annotation(tag_levels="A")
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.