In my dataset, I have 60 groups that I want to analyze in put into an HTML report using R Markdown. Because I want to apply the same analysis to each group, I am hoping that there is a way I can dynamically generate the code blocks/analysis.
Simply, I want to avoid replicating the block 60 times.
I came across this this question which uses children in knitr. I have attempted to replicate this with the iris dataset. In my example below, all I wanted to do was generate three H4 titles, one for each species.
It's worth noting that I am not married to this approach, it just appears to be related to what I am looking to do.
Here are the files I used:
parent.RMD file. This would be my "master" report.
Automate Chunks of Analysis in R Markdown
========================================================
```{r setup, echo=FALSE}
library(knitr)
```
```{r run-numeric-md, include=FALSE}
out = NULL
for (i in as.character(unique(iris$Species))) {
out = c(out, knit_child('child.Rmd'))
}
```
And here is child.Rmd.
#### Species = `r [i]`
Try knit_expand():
Automate Chunks of Analysis in R Markdown
========================================================
```{r setup, echo=FALSE}
library(knitr)
```
```{r run-numeric-md, include=FALSE}
out = NULL
for (i in as.character(unique(iris$Species))) {
out = c(out, knit_expand(text='#### Species = {{i}}'))
}
```
`r paste(knit(text = out), collapse = '\n')`
You can also create a template file like 'child.rmd' and put this in your for loop so you don't have to put a complicated analysis in quotes:
out = c(out, knit_expand('template.rmd'))
Then have your 'template.rmd' be:
#### Species = {{i}}
Taking #sam's solution, I made the following generic function. Say you have a data frame called grfDf with DiagrammeR graph objects in the column graph. The following is all you need to plot all the graphs in Rmd : r require(DiagrammeR); renderHtmlWidgetList(grfDf$graph, render_graph). See the code for caveats.
```
require(knitr)
#' Render a list of htmlWidgets using various tricks
#'
#' #param widgetList A list of htmlWidget objects to be rendered
#' #param renderFunction The function to render individual widgets. It can be either a name
#' of the rendering function, e.g., "render_graph" in DiagrammeR, or the actual function to
#' be passed to this call.
#' #return The knitted string. This is to be included in the output by using `r renderHtmlWidgetList(...)`;
#' #details This is a collection of various tricks. See the URL citations in the code.
#' Note that this code does alliterate global variables starting with "renderHtmlWidgetList_".
#' You may want to delete them using rm(list = ls(pattern="renderHtmlWidgetList_*")).
#' #examples Inlcude the following in the Rmd directly
#' `r require(DiagrammeR); renderHtmlWidgetList(grfDf$graph, render_graph)`
#'
#' #export
renderHtmlWidgetList <- function(widgetList, renderFunction){
# error checking
stopifnot(is.list(widgetList))
# handles if the renderFunction is actually a function
# http://stackoverflow.com/questions/10520772/in-r-how-to-get-an-objects-name-after-it-is-sent-to-a-function
if(is.function(renderFunction)) {
# convert back to string, because we need to knit it later
renderFunction <- deparse(substitute(renderFunction))
}
stopifnot(is.character(renderFunction) & length(renderFunction)==1)
stopifnot(exists(renderFunction, mode = "function"))
# inject global vars; make sure we have a unique global var name
gVarName<- paste0("renderHtmlWidgetList_", sample(1:10000, 1))
while (exists(gVarName)) {
gVarName<- paste0("renderHtmlWidgetList_", sample(1:10000, 1))
}
# assigning widgetList to a global temp var
# http://stackoverflow.com/questions/5510966/create-a-variable-name-with-paste-in-r
assign(gVarName, widgetList, envir = .GlobalEnv)
# solution from https://gist.github.com/ReportMort/9ccb544a337fd1778179
out <- NULL
knitPrefix <- "\n```{r results='asis', cache=FALSE, echo=FALSE}\n\n"
knitSuffix <- "\n\n```"
for (i in 1:length(widgetList)) {
knit_expanded <- paste0(knitPrefix, renderFunction, "(", gVarName, "[[", i, "]])")
out = c(out, knit_expanded)
}
#invisible(out)
paste(knitr::knit(text = out), collapse = '\n')
}
```
Related
This is what Im doing to generate a markdown so that all the things should be in one place.
How can i put these output into a datatable form which are more readable and easier to search.The list which is made are of different length. Each list has a series of table under it.
If there a way to convert these differing length list to data table format that would be really helpful
The table looks like this
## Prepare for analyses
```{r,warning=FALSE,message=FALSE}
set.seed(1234)
library(europepmc)
library(tidypmc)
library(tidyverse)
#library(dplyr)
```
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
```
##Cytarabine cytogenetically normal aml adult clinical trial Randomized Controlled Trial. 828 records found, showing 10
```{r,include=FALSE}
b <-epmc_search(query = 'cytarabine cytogenetically normal aml adult clinical trial Randomized Controlled Trial OPEN_ACCESS:Y',limit = 10)
pmcids <- b$pmcid[b$isOpenAccess=="Y"]
docs <- map(pmcids, epmc_ftxt)
my_tables <- map(docs, pmc_table)
```
```{r}
names(my_tables) <- pmcids
```
The code chunk input and output is then displayed as follows:
```{r basicconsole}
source("flat.R")
L1 <- flattenlist(my_tables)
l.f <- Filter(function(a) any(!is.na(a)), L1)
l.f
#tibble:::print.tbl_df(head(df))
#n <- paste0("Valporic_", names(l.f), ".txt")
for (i in 1:length(l.f)) {
write.table(l.f[i], sep = "\t",row.names = FALSE,col.names = TRUE,file=paste0(names(l.f)[i], ".txt"))
}
UPDATE
I have manged to covert those tibble into dataframe
using this solution
##Outout
```{r}
abc <- mapply(cbind, l.f)
abc
But when it is rendered in the markdown the column formatting is gone. Now i have now dataframe inside list.
But still im not sure how to put that into a data table
**UPDATE 2.0 **
The better approach is to read those saved output as list of files into data table and then use it as markdown but so far it is taking only one ID only. My code.
tbl_fread <-
list.files(pattern = "*.txt") %>%
map_df(~fread(.))
knitr::kable(head(tbl_fread), "pipe")
Is it possible to put these files as such.
if a list of file are from one PMCID then those would be all in one column such as if PMCID one has 3 output then all of them should be one the same row. Then the next PMCID in the second one etc etc.
UPDATE new
I have managed to align the output into more readable format. But It seems that by default all the files assigned to multiple columns which would be the case given that im reading all the files together since my idea of using the list to data table didn't work.
If i can push or stack each unique PMCID over one another instead of all in one after another that would be. Good
knitr::kable(tbl_fread, align = "lccrr")
This may be something you can adapt for R Markdown. I'm not sure what the rationale is to save and load the tables. Instead, you could obtain the tables and show in html directly.
As you are using HTML, make sure to have results='asis' in your chunk. You can use a for loop and seq_along to show each table. You can include information in your table caption, such as the PMCID as well as table number.
---
title: "test13121"
author: "Ben"
date: "1/31/2021"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
# Libraries
```{r}
library(tidypmc)
library(tidyverse)
library(europepmc)
library(kableExtra)
```
# Get Articles
```{r, echo = FALSE}
b <-epmc_search(query = 'cytarabine aml OPEN_ACCESS:Y',limit = 6)
pmcids <- b$pmcid[b$isOpenAccess=="Y"]
docs <- map(pmcids, epmc_ftxt)
my_tables <- map(docs, pmc_table)
names(my_tables) <- pmcids
```
# Show Tables
```{r, echo=F, results='asis'}
for (i in seq_along(my_tables)) {
for (j in seq_along(my_tables[[i]])) {
print(kable(x = my_tables[[i]][[j]], caption = paste0(names(my_tables)[i], ": Table ", j)))
}
}
```
I am working to stick two PNGs side by side and then convert the concatenated object to a PDF using the code below. Now how can I write all the PDF objects as pages in a single file? I tried to save all the objects in a list and then pass the list to image_write(), but it did not work.
library(magick)
vec_out <- list()
for(i in 1:length(all_stims)){
stim <- all_stims[i]
img1 <- image_read(file.path(figures_folder, "across_cluster_heatmaps", paste0("bendall_",stim,".png")))
img2 <- image_read(file.path(figures_folder, "across_cluster_heatmaps", paste0("farmer_", stim,".png")))
imgs <- c(img1, img2)
imgs <- image_append(imgs)
imgs_pdf <- image_convert(imgs)
vec_out[[i]] <- imgs_pdf
}
image_write(vec_out, path = file.path(figures_folder, "test.pdf"), format = "pdf")
Any suggestions would be helpful. Thanks.
Couldn't you write a knitr-document instead, or as rmarkdown-file?
I could not run your code, since it's not reproducible.
A mini example in rmarkdown:
The following code produces 3 plots in a for-loop.
By choosing result = 'asis' as option and by inserting
cat("\n\n\\pagebreak\n") in the for-loop, every output is printed on a separate page.
---
title: "Test page break between two figures"
output: pdf_document
---
```{r, echo=FALSE, results='asis'}
for (i in 1:3) {
print(plot(1:i, rnorm(i)))
cat("\n\n\\pagebreak\n")
}
```
I suggest doing something as mentioned above, instead of creating in a first step all pdf-files separately and glue them together in a second step.
I'm after a way to render an Rmd document (that contains references to various "child" files) to a self-contained R Notebook without these dependencies.
At the moment, the .Rmd code chunks are located throughout a number of .R, .py and .sql files and are referenced in the report using
```{r extraction, include=FALSE, cache=FALSE}
knitr::read_chunk("myscript.R")
```
followed by
```{r chunk_from_myscript}
```
as documented here.
I've done this to avoid code duplication and to allow for running the source files separately however these code chunks are only executable in the report via a call to knit or render (when read_chunk is run and the code chunk is available).
Is there a way to spin-off an Rmd (prior to knitting) with
just these chunks populated?
This function
rmarkdown::render("report.Rmd", clean = FALSE)
almost gets there as it leaves the markdown files behind whilst removing extraction and populating chunk_from_myscript however as these files are straight markdown, the chunks are no longer executable and the chunk options are missing. It obviously also doesn't include chunks where eval=TRUE, echo=FALSE which would be needed to run the resulting notebook.
I've also looked at knitr::spin however this would mean disseminating the contents of the report to every source file and isn't terribly ideal.
Reprex
report.Rmd
---
title: 'Report'
---
```{r read_chunks, include=FALSE, cache=FALSE}
knitr::read_chunk("myscript.R")
```
Some documentation
```{r chunk_from_myscript}
```
Some more documentation
```{r chunk_two_from_myscript, eval=TRUE, echo=FALSE}
```
myscript.R
#' # MyScript
#'
#' This is a valid R source file which is formatted
#' using the `knitr::spin` style comments and code
#' chunks.
#' The file's code can be used in large .Rmd reports by
#' extracting the various chunks using `knitr::read_chunk` or
#' it can be spun into its own small commented .Rmd report
#' using `knitr::spin`
# ---- chunk_from_myscript
sessionInfo()
#' This is the second chunk
# ---- chunk_two_from_myscript
1 + 1
Desired Output
notebook.Rmd
---
title: 'Report'
---
Some documentation
```{r chunk_from_myscript}
sessionInfo()
```
Some more documentation
```{r chunk_two_from_myscript, eval=TRUE, echo=FALSE}
1 + 1
```
Working through your reprex I now better understand the issue you are trying to solve. You can knit into an output.Rmd to merge your report and scripts into a single markdown file.
Instead of using knitr::read_chunk, I've read in with knitr::spin to cat the asis output into another .Rmd file. Also note the params$final flag to allow rendering the final document when set as TRUE or allowing the knit to an intermediate .Rmd as FALSE by default.
report.Rmd
---
title: "Report"
params:
final: false
---
```{r load_chunk, include=FALSE}
chunk <- knitr::spin(text = readLines("myscript.R"), report = FALSE, knit = params$final)
```
Some documentation
```{r print_chunk, results='asis', echo=FALSE}
cat(chunk, sep = "\n")
```
to produce the intermediate file:
rmarkdown::render("report.Rmd", "output.Rmd")
output.Rmd
---
title: "Report"
---
Some documentation
```{r chunk_from_myscript, echo=TRUE}
sessionInfo()
```
With the secondary output.Rmd, you could continue with my original response below to render to html_notebook so that the document may be shared without needing to regenerate but still containing the source R markdown file.
To render the final document from report.Rmd you can use:
rmarkdown::render("report.Rmd", params = list(final = TRUE))
Original response
You need to include additional arguments to your render statement.
rmarkdown::render(
input = "output.Rmd",
output_format = "html_notebook",
output_file = "output.nb.html"
)
When you open the .nb.html file in RStudio the embedded .Rmd will be viewable in the editing pane.
Since neither knitr::knit nor rmarkdown::render seem suited to rendering to R markdown, I've managed to somewhat work around this by dynamically inserting the chunk text into each empty chunk and writing that to a new file:
library(magrittr)
library(stringr)
# Find the line numbers of every empty code chunk
get_empty_chunk_line_nums <- function(file_text){
# Create an Nx2 matrix where the rows correspond
# to code chunks and the columns are start/end line nums
mat <- file_text %>%
grep(pattern = "^```") %>%
matrix(ncol = 2, byrow = TRUE)
# Return the chunk line numbers where the end line number
# immediately follows the starting line (ie. chunk is empty)
empty_chunks <- mat[,1] + 1 == mat[,2]
mat[empty_chunks, 1]
}
# Substitute each empty code chunk with the code from `read_chunk`
replace_chunk_code <- function(this_chunk_num) {
this_chunk <- file_text[this_chunk_num]
# Extract the chunk alias
chunk_name <- stringr::str_match(this_chunk, "^```\\{\\w+ (\\w+)")[2]
# Replace the closing "```" with "<chunk code>\n```"
chunk_code <- paste0(knitr:::knit_code$get(chunk_name), collapse = "\n")
file_text[this_chunk_num + 1] %<>% {paste(chunk_code, ., sep = "\n")}
file_text
}
render_to_rmd <- function(input_file, output_file, source_files) {
lapply(source_files, knitr::read_chunk)
file_text <- readLines(input_file)
empty_chunks <- get_empty_chunk_line_nums(file_text)
for (chunk_num in empty_chunks){
file_text <- replace_chunk_code(file_text, chunk_num)
}
writeLines(file_text, output_file)
}
source_files <- c("myscript.R")
render_to_rmd("report.Rmd", "output.Rmd", source_files)
This has the added benefits of preserving chunk options and working
with Python and SQL chunks too since there is no requirement to evaluate
any chunks in this step.
Long story short, I'm trying to use knitr to dynamically create separate tabsets and render a plot within each tabset. Below is a description of my failed attempts. I would appreciate a solution, but I suspect I'm just not understanding the fundamental way in which knitr renders output. So if you could point me to resources or provide some conceptual guidance, that would also be appreciated.
Dynamically creating the tabsets themselves is easy. Here is my .R file
#' ---
#' title:
#' author:
#' date:
#' output:
#' html_document
#' ---
#' # {.tabset}
#+ results='asis', echo=FALSE
for(i in 1:10){
cat('##',i,' \n')
}
When I call rmarkdown::render() on this .R file, I get 10 empty tabsets (see below). I'm not really sure what cat does or why the \n is necessary (this is where some conceptual explanation would be appreciated), but it works.
Now I add in a boxplot() call to the for-loop and expect to get the same boxplot on each tabset.
#' ---
#' title:
#' author:
#' date:
#' output:
#' html_document
#' ---
#' # {.tabset}
#+ results='asis', echo=FALSE
for(i in 1:10){
cat('##',i,' \n')
boxplot(iris$Sepal.Length~iris$Species)
}
But instead, the first tab comes up empty and the second tab is this vomit of output.
If I add another cat() with two "\n" characters, then all the tabs appear, but only the second tab is populated with the chart. The other tabs are empty.
#' ---
#' title:
#' author:
#' date:
#' output:
#' html_document
#' ---
#' # {.tabset}
#+ results='asis', echo=FALSE
for(i in 1:10){
cat('##',i,' \n')
boxplot(iris$Sepal.Length~iris$Species)
cat(' \n \n')
}
So I found a solution, but still do not know why it works. Would appreciate any feedback. Adding plot.new() before each plot() works.
#' ---
#' title:
#' author:
#' date:
#' output:
#' html_document
#' ---
#' # {.tabset}
#+ results='asis', echo=FALSE
for(i in 1:10){
cat('##',i,' \n')
plot.new()
boxplot(iris$Sepal.Length~iris$Species)
cat(' \n \n')
}
I'm not sure, but based on these patterns, I'd say that boxplot, like most of R's native plotting functions, 'overplot' each other. So, rmarkdown::render just sees one figure being generated. By calling plot.new you're telling R to 'finalize' the current plot and then start writing plot output to a new plot. This results in ten different plots.
A tip that I find useful and you might find useful as well: when debugging/troubleshooting/figuring out the dynamics of rmarkdown::render and R Markdown, it can help to also inspect the 'raw' Markdown output. You can tell rmarkdown::render to keep this file around by including keep_md: TRUE in the YAML header (see the **Advance http://rmarkdown.rstudio.com/html_document_format.html for examples and more information).
I'm familiar with R markdown "parameters".
However, say I want to generate the same report (same chart, same table) but for 5 different regions.
Is there a way to do this elegantly in a loop or lapply or do I need to make several sections. So in pseudo code I want to do something like:
for(i in 1:5):
Bunch of text
table[i]
plot[i]
Instead of
bunch of text
table[1]
plot[1]
bunch of text
table[2]
plot[2]
...
Put another way, I want to functionalize a "section" of the report, and then I can call
for(i in 1:5):
makeReport(i)
And it will go in, put in the text, figures, etc associated with index i.
You have to call print explicitly if inside for loop:
```{r}
for(i in 1:2) {
print(summary(cars[,-i]))
plot(cars[,-i])
}
```
or
```{r}
makeReport <- function(i) {
print(summary(cars[,-i]))
plot(cars[,-i])
}
for(i in 1:2) {
makeReport(i)
}
```
Update
As Stéphane Laurent already demonstrated in Dynamic number of calls to a chunk with knitr
you can define a child .rmd:
test_section.rmd
Header: `r i`-th cars
```{r}
print(summary(cars[,-i]))
plot(cars[,-i])
```
and in the main rmd file concatenate the results:
```{r runall, include=FALSE}
out <- NULL
for (i in 1:2) {
out <- c(out, knitr::knit_child('test_section.rmd'))
}
```
`r paste(out, collapse = '\n')`