Plotly plot doesn't render within for loop of RMarkdown document - r

I'm trying to build a report dynamically that requires running a loop, and for each iteration printing some messages, tables, and a plot. I can get everything to work except for the plot.
example.rmd
```{r echo=FALSE, results='asis', fig.keep='all', message = FALSE, warning = FALSE}
library(knitr)
library(plotly)
for(i in 1:4){
foo <- iris[sample(nrow(iris), 20), ]
cat("\n")
cat("# Iteration", i, "\n")
# Print the first few lines
print(kable(head(foo)))
cat("\n")
# Plot Sepal.Width vs Petal.Length using ggplotly()
plt <- ggplot(foo, aes(x = Sepal.Width, y = Petal.Length))+geom_point()
# plot(plt) # <- this works
# plot(ggplotly(plt)) # <- this doesn't work
# ggplotly(plt) # <- this doesn't work
cat("\n")
}
```
How can I get the plotly plots to render in my report?

Following this post on github about basically the same issue, I was able to put together this very hacky solution. Would love to find a better method.
```{r echo=FALSE, results='asis', fig.keep='all', message = FALSE, warning = FALSE}
library(knitr)
library(plotly)
# Build list of outputs
output <- list()
for(i in 1:4){
foo <- iris[sample(nrow(iris), 20), ]
# Header for iteration
txt <- paste0("#Iteration ", i)
output[[length(output) + 1L]] <- txt
# Table of the first few lines
tbl <- kable(head(foo))
output[[length(output) + 1L]] <- tbl
# Plot
plt <- ggplotly(ggplot(foo, aes(x = Sepal.Width, y = Petal.Length))+geom_point())
output[[length(output) + 1L]] <- plt
}
# Render the outputs
for(j in 1:length(output)){
x <- output[[j]]
if(inherits(x, "character")){
cat("\n")
cat(x)
} else if(inherits(x, "knitr_kable")){
cat("\n")
print(x)
}
else {
# print the html piece of the htmlwidgets
cat("\n")
cat(htmltools::renderTags(as.widget(x))$html)
}
}
```
```{r echo=FALSE, messages=FALSE, warning=FALSE}
# Attach the Dependencies since they do not get included with renderTags(...)$html
deps <- lapply(
Filter(f = function(x){inherits(x,"htmlwidget")}, x = output),
function(hw){
htmltools::renderTags(hw)$dependencies
}
)
htmltools::attachDependencies(x = htmltools::tagList(), value = unlist(deps,recursive=FALSE))
```

Related

Create R markdown chunks WITH different chunk options AND markdown headings in a for loop

I would like to automatically generate R markdown chunks and headings within a for loop in R for a list of data.frames which varies length depending on input. These are then the input for plot generation. This R markdown document will then be knitted to a html_document using the package knitr.
I have attempted the solutions from several similar SO questions which can get me close, but is not what I actually need:
https://stackoverflow.com/a/44671368/10771401
https://stackoverflow.com/a/57034752/10771401
The problem is that I have 4 chunks that I'd like to repeat where each chunk has to have different chunk options (fig.width, fig.height) to correctly display the ggplot2 objects in them. So, I can't simply just supply a single set of options in the code chunk itself).
Here is an example of one of my failed attempts (NOTE: the second code block below also contains a comment indicating the chunk options which is required):
# Chunk data demo
data_list <- list(a=data.frame(), b=data.frame(), c=data.frame())
plot_1 <- list()
plot_2 <- list()
for (i in 1:length(data_list)) {
data_list[[i]] <- data.frame(x = 1:5, y = 1:5)
plot_1[[i]] <- ggplot(data_list[[i]], aes(x, y)) + geom_point()
plot_2[[i]] <- ggplot(data_list[[i]], aes(x, y)) + geom_point()
}
#```{r, results='asis'} (Start of R markdown chunk)
# Chunk generation (only 2 chunks are written for brevity)
for (i in 1:length(data_list)) {
# Section heading
cat("\n\n")
cat("##", names(data_list)[i])
# Chunk 1 ----
cat("\n\n")
cat("### Sub-section 1")
cat("\n\n")
cat("**Title 1**")
cat("\n\n")
cat('```{r, fig.width=10, fig.height=10}')
cat("\n\n")
print(plot_1[[i]])
cat("\n\n")
cat('```')
# Chunk 2 ----
cat("\n\n")
cat("### Sub-section 2")
cat("\n\n")
cat("**Title 2**")
cat("\n\n")
cat('```{r, fig.width=10, fig.height=3}') # NOTE: Change in option
cat("\n\n")
print(plot_2[[i]])
cat("\n\n")
cat('```')
cat("\n\n")
}
#``` (End of R markdown chunk)
EDIT: Removed the ggsave() calls, as not necessary to illustrate the problem which is apparent in the knitted document.
I see what you are trying to do, and I think the approach you are taking won't work. (But I guess you already knew that!) Here's why:
When you run the asis chunk, you are producing Markdown, not R Markdown. The fig.width and fig.height options need to be handled by knitr when it is processing R Markdown, but that only happens once. The next stage of processing which handles the Markdown is done by Pandoc, and it doesn't know anything about fig.width and fig.height.
So what you need to do is to produce a "child document", then get knitr to include it in your main document. Those are described here: https://bookdown.org/yihui/rmarkdown-cookbook/child-document.html . Basically they are just fragments of R Markdown to include in your main document. Here's a modification of your document that does this:
---
title: "Untitled"
author: "Duncan Murdoch"
date: "2023-02-12"
output:
html_document:
keep_md: true
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
## R Markdown
```{r}
library(ggplot2)
# Create the plots
data_list <- list(a=data.frame(), b=data.frame(), c=data.frame())
plot_1 <- list()
plot_2 <- list()
for (i in 1:length(data_list)) {
data_list[[i]] <- data.frame(x = 1:5, y = 1:5)
plot_1[[i]] <- ggplot(data_list[[i]], aes(x, y)) + geom_point()
plot_2[[i]] <- ggplot(data_list[[i]], aes(x, y)) + geom_point()
}
```
```{r echo = FALSE}
# Create a child document
child <- "child.Rmd"
cat("\n", file = child)
# Save typing file = child, append = TRUE every time
# by making a new function to call cat
cat2 <- function(...) cat(..., file = child, append = TRUE)
for (i in 1:length(data_list)) {
# Section heading
cat2("\n\n")
cat2("##", names(data_list)[i])
# Chunk 1 ----
cat2("\n\n")
cat2("### Sub-section 1")
cat2("\n\n")
cat2("**Title 1**")
cat2("\n\n")
cat2('```{r fig.width=10, fig.height=10}')
cat2("\n\n")
cat2("i = ", i, "\n")
cat2("print(plot_1[[i]])")
cat2("\n\n")
cat2('```')
# Chunk 2 ----
cat2("\n\n")
cat2("### Sub-section 2")
cat2("\n\n")
cat2("**Title 2**")
cat2("\n\n")
cat2("```{r fig.width=10, fig.height=3}")
cat2("\n\n")
cat2("print(plot_2[[i]])")
cat2("\n\n")
cat2('```')
cat2("\n\n")
}
```
```{r child="child.Rmd"}
```
You can also use the knitr::knit_expand() and pass it to the knitr::knit_child() function.
Edit:
---
output: html_document
---
```{r, echo=FALSE, results='asis'}
library(purrr)
library(ggplot2)
data_list <- list(a=data.frame(x = 1:5, y = 1:5), b=data.frame(x = 1:5, y = 1:10), c=data.frame(x = 1:5, y = 1:15))
data_plots_1 <- map(data_list, ~ggplot(.x, aes(x, y)) + geom_point())
data_plots_2 <- map(data_list, ~ggplot(.x, aes(x, y)) + geom_point(color = "red"))
fig_width <- c(7,5,5)
fig_height <- c(5,3,3)
data_names <- data_list %>% names()
```
```{r results='asis', echo=FALSE}
structure_text <- purrr::pmap_chr(list(data_names, fig_width, fig_height), \(data_names, fig_width, fig_height) {
knitr::knit_expand(text = c(
"## Title Plot 1 dataset {{data_names}}",
"",
"",
"```{r, fig.width= {{ fig_width }}, fig.height= {{ fig_height }}, echo =FALSE }",
"data_plots_1${{data_names}} ",
"```",
"",
"## Title Plot 2 dataset {{data_names}}",
"",
"",
"```{r, fig.width= {{ fig_width }}, fig.height= {{ fig_height }}, echo =FALSE }",
"data_plots_2${{data_names}} ",
"```",
""
))
})
res <- knitr::knit_child(text = structure_text, quiet = TRUE)
cat(res, sep = '\n')
Old
---
output: html_document
---
```{r, echo=FALSE, results='asis'}
library(purrr)
library(ggplot2)
data_list <- list(a=data.frame(x = 1:5, y = 1:5), b=data.frame(x = 1:5, y = 1:10))
data_names <- data_list %>% names()
fig_width <- c(7,5)
fig_height <- c(5,3)
```
```{r results='asis', echo=FALSE}
structure_text <- purrr::pmap_chr(list(data_names, fig_width, fig_height), \(data_names, fig_width, fig_height) {
knitr::knit_expand(text = c(
"## {{ data_names }}",
"",
"",
"```{r, fig.width= {{ fig_width }}, fig.height= {{ fig_height }}, echo =FALSE }",
"data_list${{data_names}} |>",
" ggplot(aes(x = x, y = y)) +",
" geom_point()",
"```",
""))
})
res <- knitr::knit_child(text = structure_text, quiet = TRUE)
cat(res, sep = '\n')

Dynamic creation of tabs in Rmarkdown does not work for ggplot while it does for plotly

I've been willing to dynamically create tab contents in rmarkdown.
I've created an in_tabs that seems to work for everything but ggplot plots.
The way it works is that it creates the Rmd code necessary to display nested lists in tabs.
The following reproducible example shows the issue:
---
title: "test"
output: html_document
---
```{r setup, include = FALSE}
library(ggplot2)
library(plotly)
l1 <- list(p1 = data.frame(x=1:10, y=1:10))
l2 <- list(p2 = data.frame(x=100:110, y=100:110))
gplot <- function(data) {
p <- ggplot(data) + aes(x=x, y=y) + geom_point() + geom_line()
return(p)
}
gplotly <- function(data) {
p <- ggplot(data) + aes(x=x, y=y) + geom_point() + geom_line()
return(ggplotly(p))
}
```
```{r, code, include = FALSE}
in_tabs <- function(l, labels = names(l), level, knit = TRUE, close_tabset = FALSE) {
if(is.null(labels)) {
stop("labels are NULL, it is required not to be so that the tabs have proper names")
}
names(l) <- labels
rmd_code <- lapply(seq_along(l), FUN = function(i) obj_to_rmd(l[[i]], name = names(l)[i], level = level + 1L))
if(isTRUE(getOption("knitr.in.progress"))) {
res <- knitr::knit(text = unlist(rmd_code), quiet = TRUE)
cat(res)
} else {
if(!knit) {
cat(unlist(rmd_code))
} else {
return(l)
}
}
if(close_tabset) {
cat(paste(get_section(level), "{.unlisted .unnumbered .toc-ignore .tabset}", "\n"))
}
}
get_section <- function(level) {
paste(rep("#", times = level), collapse = "")
}
get_tabset <- function(obj) {
ifelse(inherits(obj, "list"), "{.tabset}", "")
}
obj_to_rmd <- function(obj, parent_name = "l", name, level) {
section_code <- sprintf("%s %s %s\n", get_section(level), name, get_tabset(obj))
if(!inherits(obj, "list")) {
rmd_code <- c("```{r, echo = FALSE}\n",
sprintf("%s$`%s`\n", parent_name, name),
"```\n",
"\n")
} else {
rmd_code <- c("\n",
lapply(X = seq_along(obj),
FUN = function(i) obj_to_rmd(obj[[i]], sprintf("%s$`%s`", parent_name, name), names(obj)[i], level + 1L)))
}
return(c(section_code, rmd_code))
}
```
# plot 1 {.tabset}
```{r, plot-01, results = "asis"}
in_tabs(lapply(l1, FUN = gplot), labels = names(l1), level = 1L)
```
# plot 2 {.tabset}
```{r, plot-02, results = "asis"}
in_tabs(lapply(l2, FUN = gplot), labels = names(l2), level = 1L)
```
# plot 3 {.tabset}
```{r, plot-03, results = "asis"}
in_tabs(lapply(l1, FUN = gplotly), labels = names(l1), level = 1L)
```
# plot 4 {.tabset}
```{r, plot-04, results = "asis"}
in_tabs(lapply(l2, FUN = gplotly), labels = names(l2), level = 1L)
```
The output I get is:
You can see the issue that the first plot is actually identical the the second plot while it should not !!!
When using plotly (or anything else I have tested) it works as expected as shown on plots 3 and 4
Could you help me fix it, I am happy with testing for the class of the object obj_to_rmd receives.
PS: rmd code in_tabs generates can be seen by running in_tabs(..., knit = FALSE). For instance
in_tabs(lapply(l1, FUN = gplot), labels = names(l1), level = 1L, knit = FALSE)
## p1
```{r, echo = FALSE}
plot(l$`p1`)
```
As stefan mentioned, the issue is with the ggplot's id, since they somehow have the same code chunk, even though you named the chunks differently.I don't know the reason for this behaviour, but you can bypass it by setting
```{r, include=FALSE}
options(knitr.duplicate.label = "allow")
```
at the beginning of your document. That should do the trick. It will give different chunk names to each of your plots. You can verify that by removing results = "asis"from your ggplots to see that they no longer have the same id.
## ## p1
##
## <img src="test_files/figure-html/unnamed-chunk-2-1.png" width="672" />
## ## p2
##
## <img src="test_files/figure-html/unnamed-chunk-1-2-1.png" width="672" />
You can read more about allowing duplicated chunks at bookdown.org
I'm not 100% sure about all the details so you have to keep in mind that may answer involves some guessing.
When knitting the document knitr runs the ggplot2 code and saves the resulting plot as a png where the filename is the name of the chunk.
As far as I got it from inspecting the md file generated by knitr (by adding keep_md: true to the YAML) the issue with your code is, that "all" plots are saved under the same filename unnamed-chunk-1-1.png, i.e. both of your ggplot chunks look like this in the final md:
![](bar1_files/figure-html/unnamed-chunk-1-1.png)<!-- -->
This could also be seen by having a look the the figure-html folder which includes only one png.
Put differently your code basically works fine, but you are permanently overwriting the pngs so you end up with a document where only the last saved plot shows up. That's also the reason why your code works for ggplotly as in that case the HTML/JS code necessary to render the chart is directly added to the md file.
Under normal circumstances knitr ensures that all plots are saved under unique filenames. I can only guess why this fails in your case. My guess is that the issue is that you knit each chunk separately when calling knitr::knit(text = unlist(rmd_code), quiet = TRUE), i.e. each unnamed chunk gets the same name and each is ggplot is accordingly saved under the same filename.
Having said that, to achieve your desired result you could add a unique name to each of the dynamic code chunks so that each plot is saved under a unique filename.
As a quick solution I added an id argument to your in_tabs and obj_to_rmd functions. In case of in_tabs the id is simple the identifier of the chunk in your main document, while in case of obj_to_rmd I additionally add an identifier for the list element via id = paste(id, i, sep = "-"):
---
title: "test"
output:
html_document:
keep_md: true
---
```{r setup, include = FALSE}
library(ggplot2)
library(plotly)
d1 <- data.frame(x = 1:10, y = 1:10)
d2 <- data.frame(x = 100:110, y = 100:110)
l1 <- list(p1 = d1)
l2 <- list(p1 = d2, p2 = d1)
gplot <- function(data) {
ggplot(data) +
aes(x = x, y = y) +
geom_point() +
geom_line()
}
```
```{r, code, include = FALSE}
in_tabs <- function(l, labels = names(l), level, knit = TRUE, close_tabset = FALSE, id) {
if (is.null(labels)) {
stop("labels are NULL, it is required not to be so that the tabs have proper names")
}
names(l) <- labels
rmd_code <- lapply(seq_along(l), FUN = function(i) obj_to_rmd(l[[i]], name = names(l)[i], level = level + 1L, id = paste(id, i, sep = "-")))
if (isTRUE(getOption("knitr.in.progress"))) {
res <- knitr::knit(text = unlist(rmd_code), quiet = TRUE)
cat(res)
} else {
if (!knit) {
cat(unlist(rmd_code))
} else {
return(l)
}
}
if (close_tabset) {
cat(paste(get_section(level), "{.unlisted .unnumbered .toc-ignore .tabset}", "\n"))
}
}
get_section <- function(level) {
paste(rep("#", times = level), collapse = "")
}
get_tabset <- function(obj) {
if (inherits(obj, "list")) "{.tabset}" else ""
}
obj_to_rmd <- function(obj, parent_name = "l", name, level, id) {
section_code <- sprintf("%s %s %s\n", get_section(level), name, get_tabset(obj))
if (!inherits(obj, "list")) {
rmd_code <- c(
sprintf("```{r plot-%s, echo = FALSE}\n", id),
sprintf("%s$`%s`\n", parent_name, name),
"```\n",
"\n"
)
} else {
rmd_code <- c(
"\n",
lapply(
X = seq_along(obj),
FUN = function(i) obj_to_rmd(obj[[i]], sprintf("%s$`%s`", parent_name, name), names(obj)[i], level + 1L)
)
)
}
return(c(section_code, rmd_code))
}
```
# plot 1 {.tabset}
```{r, plot-01, results = "asis"}
p1 <- lapply(l1, FUN = gplot)
in_tabs(p1, labels = names(l1), level = 1L, id = 1)
```
# plot 2 {.tabset}
```{r, plot-02, results = "asis"}
p2 <- lapply(l2, FUN = gplot)
in_tabs(p2, labels = names(l2), level = 1L, id = 2)
```

How to parameterize the inline code, text, together with R code chunk in Rmarkdown

In my Rmarkdown report, most of sections have the same text, inline code and R code chunk. Is it possible to parameterize them? For example the below image, is it possible to use something like for loop to produce them instead of repeating similar code 3 times?
In main RMD file,
library(tidyverse)
dat <- tibble(
id = 1:3,
fruit = c("apple", "orange", "banana"),
sold = c(10, 20, 30)
)
res <- lapply(dat$id, function(x) {
knitr::knit_child(
'template.Rmd', envir = environment(), quiet = TRUE
)
})
cat(unlist(res), sep = '\n')
In template.RMD,
current_dat <- filter(dat, id == x)
# Section: `r current_dat$fruit`
current_dat %>%
ggplot(aes(x = fruit, y = sold)) + geom_col()
IMHO, the simplest wat to achieve this is to use results = 'asis' and cat() below is a minimal RMarkdown file.
---
title: "Minimal example"
---
```{R results = "asis"}
for (i in 1:3) {
x <- runif(10)
cat("# section", floor(i), "\n")
plot(x)
# line break
cat("\n\n")
}
```

dynamic tabsets with multiple plots r markdown

I managed to create a html document that creates dynamic tabsets based on a list of items. Adding one plot works fine on one tabset. How can I add now multiple plots on one tabset?
Hereby the code I started from but it only shows 1 plot per tabset when you knit the document to html output. obviously there is still something missing.
---
title: "R Notebook"
output:
html_document:
df_print: paged
editor_options:
chunk_output_type: inline
---
### header 1
```{r}
library(ggplot2)
df <- mtcars
pl_list <- list()
pl1 <- qplot(cyl, disp, data = df[1:12,])
pl2 <- qplot(mpg, cyl, data = df[13:20,])
pl3 <- qplot(mpg, cyl, data = df[21:30,])
pl4 <- qplot(mpg, cyl, data = df[1:12,])
pl_list[[1]] <- list(pl1, pl3, "one")
pl_list[[2]] <- list(pl2, pl4, "two")
```
### header {.tabset}
```{r, results = 'asis', echo = FALSE}
for (i in seq_along(pl_list)){
tmp <- pl_list[[i]]
cat("####", tmp[[3]], " \n")
print(tmp[1])
cat(" \n\n")
}
```
There are a couple of improvements you can do.
Create cat header function with arguments for text and level.
With it you don't need to call cat multiple times and it creates wanted number of # automatically.
catHeader <- function(text = "", level = 3) {
cat(paste0("\n\n",
paste(rep("#", level), collapse = ""),
" ", text, "\n"))
}
print plots using lapply.
Full code looks like this:
---
title: "R Notebook"
output:
html_document:
df_print: paged
editor_options:
chunk_output_type: inline
---
```{r, functions}
catHeader <- function(text = "", level = 3) {
cat(paste0("\n\n",
paste(rep("#", level), collapse = ""),
" ", text, "\n"))
}
```
### header 1
```{r}
library(ggplot2)
df <- mtcars
pl_list <- list()
pl1 <- qplot(cyl, disp, data = df[1:12,])
pl2 <- qplot(mpg, cyl, data = df[13:20,])
pl3 <- qplot(mpg, cyl, data = df[21:30,])
pl4 <- qplot(mpg, cyl, data = df[1:12,])
pl_list[[1]] <- list(pl1, pl3, "one")
pl_list[[2]] <- list(pl2, pl4, "two")
```
## header {.tabset}
```{r, results = "asis", echo = FALSE}
for(i in seq_along(pl_list)){
tmp <- pl_list[[i]]
# As you want to use tabset level here has to be lower than
# parent level (ie, parent is 2, so here you have to use 3)
catHeader(tmp[[3]], 3)
lapply(tmp[1:2], print)
}
```

Loops in Rmarkdown: How to make an in-text figure reference? Figure captions?

{r setup, include=FALSE, message=FALSE, results="hide"}
knitr::opts_chunk$set(echo = TRUE)
library(knitr)
library(kfigr)
library(dplyr)
library(png)
library(grid)
library(pander)
library(ggplot2)
Question
Loops in rmarkdown: in-text figure reference? figure captions?
Goal
Use a for loop to create sections with text, in-text results, and multiple figure references with associated figure captions in the figure list. The figure references/numbering should be seemless with figures numbered before and after these new sections.
Note: The figures referenced in the for loop are generated earlier in the text, saved as pngs, and then re-loaded. This might seem clunky for the purpose of this example, but the actual figs are maps and are slow to generate (I plan to comment out the loop that generates the figures once I have them how I want).
{r echo = FALSE, warnings=FALSE, message=FALSE, results="hide"}
Data: Each year we have a different number of strata, hence the need for a loop.
df <- rbind(
data.frame(strata = rep("A", 10), x = rnorm(10, mean= 10), y = rnorm(10, mean = 15),z = rnorm(10, mean = 20)),
data.frame(strata = rep("B", 10), x = rnorm(10, mean= 5), y = rnorm(10, mean = 10), z = rnorm(10, mean = 15)),
data.frame(strata = rep("C", 10), x = rnorm(10, mean= 15), y = rnorm(10, mean = 20), z = rnorm(10, mean = 10)))
first_plot: the figure that should appear in the list before for loop creates the sections by strata
first_plot <- ggplot(df, aes(x, fill=strata)) + geom_histogram()
last_plot: the figure that should appear in the list after the for loop creates the sections by strata
last_plot <- ggplot(df, aes(x = strata, y = z)) + geom_boxplot()
Figure generation (this is the part that will be commented out later in my version once I have the maps how I want)
strat <- unique(df$strata)
for (i in seq_along(strat)) {
sub <- df %>% filter(strata %in% strat[i])
fig1 <- ggplot(sub, aes(x = x, y = y)) + geom_point()
ggsave(fig1, file=paste0("fig1_", strat[i], ".png"))
fig2 <- ggplot(sub, aes(x = x, y = z)) + geom_point()
ggsave(fig2, file=paste0("fig2_", strat[i], ".png"))
}
Load the png's
df_figs <- list.files(pattern = "\\.png$")
for (i in df_figs){
name <- gsub("-",".",i)
name <- gsub(".png","",name)
i <- paste(".\\",i,sep="")
assign(name,readPNG(i))
}
Introduction section
Some introductory text in the report and a figure r figr('first_plot',TRUE, type='Figure').
```{r echo = FALSE, warnings=FALSE, message=FALSE, results = "asis"}
# Summary of results and image file names that will be references in text
results <- df %>%
group_by(strata) %>%
dplyr::summarise_each(funs(mean)) %>%
mutate(fig1 = paste0("fig1_", strata),
fig2 = paste0("fig2_", strata))
#Text template (each strata will have its own section)
template <- "# The %s stratum
The mean of *x* in %s stratum was %1.1f. Relationships between *x* and *y* and *x* and *z* can be found in `r figr('%s', TRUE, type='Figure')` and `r figr('%s', TRUE, type='Figure')`.
"
#Create markdown sections in for loop
for(i in seq(nrow(results))) {
current <- results[i, ]
cat(sprintf(template,
current$strata, current$strata,
current$x,
current$fig1, current$fig2))
}
#Also doesn't work:
template <- "# The %s stratum
The mean in %s stratum was %1.0f. Results can be found in "
template2 <- " and "
template3 <- ".
"
`figr('%s', TRUE, type='Figure')` and `figr('%s', TRUE, type='Figure')`."
#For loop
for(i in seq(nrow(results))) {
current <- results[i, ]
cat(sprintf(template,
current$strata, current$strata,
current$mean,
current$fig_1, current$fig_2))
print(paste0("`r figr(",paste0("'", current$fig1,"'"), TRUE, type='Figure'))
cat(sprintf(template2))
print(paste0("`r figr(",paste0("'", current$fig2,"'"), "TRUE, type='Figure'),`"))
cat(sprintf(template3))
}
```
Conclusion section
Some discussion text in the report and figure r figr('last_plot',TRUE, type='Figure').
Figures
*NOTE:* I don't know how to automate the looped portion of the list of figures here, so I've done it by hand.
```{r 'first_plot', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="The caption for the first figure."}
suppressMessages(print(first_plot))
```
```{r 'fig1_A', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="Caption text for fig1_A."}
grid.raster(fig1_A)
```
```{r 'fig2_A', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="Caption text for fig2_A."}
grid.raster(fig2_A)
```
```{r 'fig1_B', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="Caption text for fig1_B."}
grid.raster(fig1_B)
```
```{r 'fig2_B', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="Caption text for fig2_B."}
grid.raster(fig2_B)
```
```{r 'fig1_C', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="Caption text for fig1_C."}
grid.raster(fig1_C)
```
```{r 'fig2_C', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="Caption text for fig2_C."}
grid.raster(fig2_C)
```
```{r 'last_plot', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6, fig.cap="The caption for the last figure."}
suppressMessages(print(last_plot))
```
SOLUTIONS
Use knit_expand()
Use captioner instead of kfigr
This numbers your figures (or tables) in text and at the end of your report.
This script shows you how to create markdown paragraphs in for loops that have in-text references to figures.
It also shows you how to create custom figure captions in for loops while retaining the number order.
If you show how to do #4 and #5 using brew I will give you all the SO points.
Libraries
library(knitr)
library(dplyr)
library(png)
library(grid)
library(pander)
library(ggplot2)
library(devtools)
library(captioner)
Create a fig_nums() function using the captioner package
(https://github.com/adletaw/captioner/blob/master/vignettes/using_captioner.Rmd)
fig_nums <- captioner(prefix = "Figure")
Data
Each year we have a different number of strata, hence the need for a loop.
df <- rbind(
data.frame(strata = rep("A", 10), x = rnorm(10, mean= 10), y = rnorm(10, mean = 15), z = rnorm(10, mean = 20)),
data.frame(strata = rep("B", 10), x = rnorm(10, mean= 5), y = rnorm(10, mean = 10), z = rnorm(10, mean = 15)),
data.frame(strata = rep("C", 10), x = rnorm(10, mean= 15), y = rnorm(10, mean = 20), z = rnorm(10, mean = 10)))
first_plot: the figure that should appear in the list before for loop creates the sections by strata
first_plot <- ggplot(df, aes(x, fill=strata)) + geom_histogram()
fig_nums("first_plot", display = FALSE)
last_plot: the figure that should appear in the list after the for loop creates the sections by strata
last_plot <- ggplot(df, aes(x = strata, y = z)) + geom_boxplot()
Figure generation
Comment this section out once you have figs how you want. This step will not feel convoluted, unnatural, suboptimal, unnecessary, or like a very bad idea if you do a lot of mapping in R.
strat <- unique(df$strata)
for (i in seq_along(strat)) {
sub <- df %>% filter(strata %in% strat[i])
fig1 <- ggplot(sub, aes(x = x, y = y)) + geom_point()
ggsave(fig1, file=paste0("fig1_", strat[i], ".png"))
fig2 <- ggplot(sub, aes(x = x, y = z)) + geom_point()
ggsave(fig2, file=paste0("fig2_", strat[i], ".png"))
}
Load the png's
df_figs <- list.files(pattern = "\\.png$")
for (i in df_figs){
name <- gsub("-",".",i)
name <- gsub(".png","",name)
i <- paste(".\\",i,sep="")
assign(name,readPNG(i))
}
Introduction
Some introductory text in the report and a figure r fig_nums("first_plot", display="cite").
Results and image file names that will be referenced in text:
```{r echo = FALSE, warnings=FALSE, message=FALSE, results = "asis"}
results <- df %>%
group_by(strata) %>%
dplyr::summarise_each(funs(mean)) %>%
mutate(fig1 = paste0("fig1_", strata),
fig2 = paste0("fig2_", strata))
```
```{r run-numeric-md, warning=FALSE, include=FALSE}
#The text for the markdown sections in for loop... the knit_expand() is the work-horse here.
out = NULL
for (i in as.character(unique(results$strata))) {
out = c(out, knit_expand(text=c('#### The *{{i}}* strata',
'\n',
'The mean of *x* is ',
'{{paste(sprintf("%1.1f", results$x[results$strata==i]))}}', '({{fig_nums(results$fig1[results$strata==i],display="cite")}}).',
'\n'
)))
}
```
Creates section for each strata
`r paste(knit(text = out), collapse = '\n')`
Conclusion
Some discussion text in the report and figure r fig_nums("last_plot",display="cite").
List of Figures
`r fig_nums("first_plot",caption="Here is the caption for the first figure.")`
```{r 'first_plot', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6}
suppressMessages(print(first_plot))
```
```{r figcaps, include=FALSE}
caps = NULL
for (i in as.character(unique(results$strata))) {
caps = c(caps, knit_expand(
text=c({{fig_nums(results$fig1[results$strata==i], caption="Caption text for strata *{{i}}* goes here.")}},
'``` {r {{results$fig1[results$strata==i]}}, echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6}',
{{paste0('grid.raster(',results$fig1[results$strata==i],')')}},
'```',
'\n')))
}
#DON'T FORGET TO UNLIST!
src <- unlist(caps)
```
`r paste(knit(text = src),sep='\n')`
`r fig_nums("last_plot", caption="The caption for the last figure.")`
```{r 'last_plot', echo=FALSE, warning=FALSE, fig.width=6.5, fig.height=6}
suppressMessages(print(last_plot))
```

Resources