Modify chunk options via knit_hooks - r

I want to define a custom chunk hook to certain r-markdown chunks, which override certain chunk options (include and eval, basically).
Use case is that I add this chunk option to a chunk, which will hide code and output per default (include = FALSE) and evaluates the chunk only if a file exists eval = <some logic>. This works if I hardcode it (cf. to chunk export_disp_mpg_by_hand but not via a custom chunk option.
But regardless of how I try it, the options are not considered.
Code
---
title: "Chunks Options"
author: "Me"
output: html_document
---
```{r setup, include = FALSE, message = FALSE}
library(ggplot2)
library(knitr)
library(here)
image_path <- "figs"
if (!dir.exists(here(image_path))) {
dir.create(here(image_path))
}
knit_hooks$set(export_fig = function(before, options, envir) {
if (before) {
fn <- gsub("^export_", "", options$label)
options$include <- FALSE
options$eval <- !file.exists(here(image_path, paste0(fn, ".png")))
### Does not work either
## opts_current$set(include = FALSE,
## eval = !file.exists(here(image_path, paste0(fn, ".png"))))
}
options
})
```
# Plot
```{r make_plot}
(pl <- ggplot(mtcars, aes(disp, mpg)) +
geom_point())
```
```{r export_disp_mpg, export_fig = TRUE}
ggsave(here(image_path, "disp_mpg.png"), pl)
```
```{r export_disp_mpg_by_hand, include = FALSE, eval = !file.exists(here(image_path, "disp_mpg.png"))}
ggsave(here(image_path, "disp_mpg.png"), pl + geom_smooth(method = "lm"))
```
Expected Behaviour
Chunk export_disp_mpg should never be shown in the rendered output and the the file it creates should only be created if it is not existing.

So close, I found the solution. In case anybody is interested, I leave the question here with the follwong working answer.
Instead of using knit_hooks, one should use opts_hooks:
```{r setup, include = FALSE}
opts_hooks$set(export_fig = function(options) {
fn <- gsub("^export_", "", options$label)
options$include <- FALSE
options$eval <- !file.exists(here(image_path, paste0(fn, ".png")))
options
})
```

Related

knitr::include_graphics: safe way to read from the project directory?

I am trying to make a RMarkdown report using bookdown::html_document2 to create numbered Fig. 1, Fig. 2, ... across the whole document. Here, I am using both the R-generated and the external figures. I have found that using include_graphics() will help to generate a proper Fig. X numbers, also including in numbering the external figures.
To get the script to work, I am declaring the root.dir = rprojroot::find_rstudio_root_file('C:/myRproject')) while my external figures are located within C:/myRproject/inImg. But in this case, R cannot find my external images anymore? Why is this and how can I properly claim the paths for my R Markdown input, and for external figures? Thank you!
Example:
---
title: "My awesome title"
author: "me"
date: "`r Sys.Date()`"
output:
bookdown::html_document2:
toc: true
toc_depth: 3
knit: (function(input, ...) {
rmarkdown::render(
input,
output_dir = "../outReports",
output_file = file.path("../outReports", glue::glue('test_{Sys.Date()}'
)))
})
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, tidy = TRUE, tidy.opts = list(comment = FALSE), message = F, warning = F)
knitr::opts_chunk$set(fig.width=6, fig.height=3)
library(knitr)
library(png)
```
```{css, echo=FALSE}
p.caption {
font-size: 0.8em;
}
```
```{r setup-root}
knitr::opts_knit$set(root.dir = rprojroot::find_rstudio_root_file('C:/myRproject'))
```
```{r read-libs, eval = TRUE, echo=FALSE, include = FALSE, warning = FALSE, error = FALSE }
# rm(list=ls())
getwd()
#### Source paths and functions -----------------------------------------------
source('myPaths.R') # already this one I can't find within the directory?
# Read pictures as part of teh R chunks
library(knitr)
library(png)
# Read Input data -------------------------------------------------------------------
#getwd()
load(file = "outData/dat1.Rdata")
```
## Including Plots
You can also embed plots, for example:
```{r, out.width = "50%", fig.cap = 'Add fig from internet'}
include_graphics("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/MC_Drei-Finger-Faultier.jpg/330px-MC_Drei-Finger-Faultier.jpg")
```
```{r add-extern-plot2, fig.cap = 'my numbered caption'}
# All defaults
img1_path <- "C:/myRproject/inImg/my_extern_fig.png"
img1 <- readPNG(img1_path, native = TRUE, info = TRUE)
attr(img1, "info")
include_graphics(img1_path)
```

Create code snippets for various languages in rmardown programmatically

I try to create Code snippet programmatically through a provided Parameter but Keep the target programming language dynamic.
What i tried:
Following https://stackoverflow.com/a/64855295/8538074
i know i could use opts <- knitr::opts_chunk$get()
which will include an engine opts$engine which could be tried
to bet set to "SQL".
I guess that sthg like that should work because of:
https://github.com/yihui/knitr-examples/blob/master/115-engine-sql.md
https://github.com/yihui/knitr-examples/blob/master/115-engine-sql.Rmd
(but i would need to render it from code since i handover the corresponding code string via the params of the rmarkdown file)
My best try:
---
title: "xx"
output: html_document
params:
code: list(language = "SQL", code_string = "SELECt * FROM tbl LIMIT 15")
---
```{r setup, include=FALSE}
hook <- knitr::hooks_html()$source
opts <- knitr::opts_chunk$get()
language <- params$code$code$language
opts$engine <- language
code_string <- params$code$code_string
cat(hook(code_string, options = opts))
```
Based on MartinĀ“s comment:
---
title: "xx"
output: html_document
params:
code: list(language = "SQL", code_string = "SELECT * FROM tbl LIMIT 15")
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
```{r, results = 'asis', echo = F}
chunks <- eval(parse(text = params$code))
hook <- knitr::hooks_html()$source
opts <- knitr::opts_chunk$get()
opts$highlight <- FALSE
code_string <- chunks$code_string
cat(hook(code_string, options = opts))
```

Create code snippets by a loop in rmardown

Similar to how to create a loop that includes both a code chunk and text with knitr in R i try to get text and a Code snippet created by a Loop.
Something along this:
---
title: Sample
output: html_document
params:
test_data: list("x <- 2", "x <- 4")
---
for(nr in 1:3){
cat(paste0("## Heading ", nr))
```{r, results='asis', eval = FALSE, echo = TRUE}
params$test_data[[nr]]
```
}
Expected Output would be:
What i tried:
I tried to follow: https://stackoverflow.com/a/36381976/8538074. But printing "```" did not work for me.
You can make use of knitr hooks. Take the following MRE:
---
title: "Untitled"
output: html_document
params:
test_data: c("x <- 2", "x <- 4")
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
```{r, results = 'asis', echo = F}
hook <- knitr::hooks_html()$source
opts <- knitr::opts_chunk$get()
chunks <- eval(parse(text = params$test_data))
for(nr in seq_along(chunks)){
cat(paste0("## Heading ", nr, "\n"))
cat(hook(chunks[nr], options = opts))
}
```
We get the default source hook and also the default chunk options. Then we get the test data, which is supplied as a string. Therefore we parse and evaluate that string.
In the loop we simply call the source hook on each element of the test data. Here is the result:

Replace variables with their corresponding values in source code chunk

I'd like to show parameter values and not params$... in R Markdown output. For example, the first code chunk below displays params$file in the output, but I'd like to replace that with samples.txt. I tried adding a second chunk with message, but that outputs a white code chunk and I'd like a gray background like all other R code blocks.
---
output: html_document
params:
file: samples.txt
---
```{r read, message=FALSE, collapse=TRUE, comment=""}
x <- read_tsv(params$file)
x
```
This just needs a gray background
```{r print, echo=2, collapse=TRUE, comment=""}
message('x <- read_tsv("', params$file, '")')
x
```
You could modify the source hook. A solution tailored to your need follows immediately. For a more general approach, that replaces all elements in params, scroll down.
---
output:
pdf_document: default
html_document: default
params:
file: samples.txt
---
```{r, include=FALSE}
library(knitr)
library(stringr)
default_source_hook <- knit_hooks$get('source')
knit_hooks$set(source = function(x, options) {
x <- str_replace_all(x, pattern = 'params\\$file', paste0("'",params$file,"'"))
default_source_hook(x, options)
})
```
```{r print, echo=T, comment="", eval = T}
print(params$file)
```
First we save the default hook that would be used depending on the output file type (render_html or render_latex etc.). Then we change the source hook: we replace all occurrences of params$file with its value and then throw the source code back into the default hook we saved before.
In this case this results in:
This magic works, because we only modify the source code that will be printed, not the one being evaluated!
Update: A more general Approach
I played a bit with your example and created a more general hook. It should replace all elements of the form params$... in your code chunks. It even checks for the type of value and adds quotes if it is a character value.
Check the following MRE:
---
output:
pdf_document: default
html_document: default
params:
file: samples.csv
age: 28
awesome: true
34: badname
_x: badname
---
```{r, include=FALSE}
library(knitr)
library(gsubfn)
default_source_hook <- knit_hooks$get('source')
knit_hooks$set(source = function(x, options) {
x <- gsubfn(x = x, pattern = "params\\$`?([\\w_]+)`?", function(y) {
y <- get(y, params)
ifelse(is.character(y), paste0("'", y, "'"), y)
})
default_source_hook(x, options)
})
```
```{r print, echo=T, comment="", eval = T}
file <- params$file
age <- params$age
awsm <- params$awesome
# dont name your variables like that! works though...
badexmpls <- c(params$`34`, params$`_x`)
```
We make use of gsubfn(). This function allows us to use a function for the replacement attribute (not possible in common gsub). This function takes on the elements found, but, thanks to regex only the part after the $. So in this chunk, y equals file, age and awesome.

Creating dynamic tabs in Rmarkdown

In Rmarkdown, it's possible to create tabs, for example:
---
output: html_document
---
# Tabs {.tabset}
## Tab 1
foo
## Tab 2
bar
I'm wondering if it's possible to create an arbitrary number of tags? How can I create a tab programatically?
The following code is a poor attempt to do this, but it results in a heading instead of a tab.
---
output: html_document
---
# Tabs {.tabset}
```{r echo=FALSE}
shiny::tags$h2("Tab 1")
```
foo
## Tab 2
bar
Solution
Thanks to #GGamba for providing a great solution. I needed to go one step further and be able to add tabs as part of a loop, so I needed to make two changes. First of all, I used this code to dynamically add tabs (the only difference here is that I force the evaluation of hrefCode inside the timeout because otherwise all timeouts called together will use the same value)
(function(hrefCode){setTimeout(function(){
var tabContent = document.createElement('div');
var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
tabContent.setAttribute('id', 'tab-' + hrefCode);
tabContent.setAttribute('class', 'tab-pane')
tabContent.innerHTML = '", gsub('\n', '', Panel, fixed = TRUE), "';
tabContainerTarget.appendChild(tabContent);
}, 100);
})(hrefCode);
Secondly, to add tabs in a loop, you can do something like this:
tabsToAdd <- list("tab3" = "hello", "tab4" = "world")
shiny::tagList(lapply(names(tabsToAdd), function(x) {
addToTabset(title = x, tabsetId = 'tbSet1',
tabPanel(x, tabsToAdd[[x]]))
}))
There is also a simple rmarkdown solution to this problem that does not require shiny and/or custom javascript. Does not work for all kinds of R output (see below):
## Tabbed Example {.tabset}
```{r, results = 'asis'}
for (nm in unique(iris$Species)){
cat("### ", nm, "\n")
cat(knitr::knit_print(plot(iris[iris$Species == nm, ])))
cat("\n")
}
```
A more involved method, that first creates a list of raw Rmarkdown code as a list of character vectors, which are then evaluated in a separate (inline) code chunk with knitr::knit(). This works for all kinds of output, not just base plots.
## Tabbed Example ggplot {.tabset}
```{r}
library(ggplot2)
template <- c(
"### {{nm}}\n",
"```{r, echo = FALSE}\n",
"ggplot(iris[iris$Species == '{{nm}}', ], aes(x = Sepal.Length, y = Sepal.Width)) + geom_point()\n",
"```\n",
"\n"
)
plots <- lapply(
unique(iris$Species),
function(nm) knitr::knit_expand(text = template)
)
```
`r knitr::knit(text = unlist(plots))`
As far as I know what you are trying to do is not possible in rmarkdown (but I'd love to stand corrected). But of course we can implement a function to do just that.
I based my answer on this answer by #KRohde, so all the credits goes to him. I just adapted it to work in a simpler markdown document.
The answer is mostly build with JS rather than R, but as the markdown is mostly an HTML I feel JS is a better tool.
Here is the code:
---
output: html_document
---
```{r echo=FALSE, results='asis'}
library(shiny)
addToTabset <- function(title, tabsetId, Panel) {
tags$script(HTML(paste0("
/* Getting the right tabsetPanel */
var tabsetTarget = document.getElementById('", tabsetId, "');
/* Creating 6-digit tab ID and check, whether it was already assigned. */
hrefCode = Math.floor(Math.random()*100000);
/* Creating node in the navigation bar */
var navNode = document.createElement('li');
var linkNode = document.createElement('a');
linkNode.appendChild(document.createTextNode('", title, "'));
linkNode.setAttribute('data-toggle', 'tab');
linkNode.setAttribute('data-value', '", title, "');
linkNode.setAttribute('href', '#tab-' + hrefCode);
navNode.appendChild(linkNode);
tabsetTarget.appendChild(navNode);
setTimeout(function(){
var tabContent = document.createElement('div');
var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
tabContent.setAttribute('id', 'tab-' + hrefCode);
tabContent.setAttribute('class', 'tab-pane')
tabContent.innerHTML = '", gsub('\n', '', Panel, fixed = T), "';
tabContainerTarget.appendChild(tabContent);
}, 100);
")
))
}
```
The code above should stay in a 'setup chunk', as it define an R function to call a JS function that mostly just add the right things to the DOM.
It can then be used when needed, passing the tabPanel title, the 'target' tabset and the normal tabPanel function.
```{r results='asis', echo=FALSE}
shiny::tabsetPanel(id = 'tbSet1',
shiny::tabPanel('Tab 1', 'foo'),
shiny::tabPanel('Tab 2', 'bar')
)
```
```{r results='asis', echo=FALSE}
addToTabset(title = 'Tab 3',
tabsetId = 'tbSet1',
tabPanel(
h1('This is a title'),
actionButton('btn',label = 'Clicky button'),
radioButtons('asd', LETTERS[1:5], LETTERS[1:5])) )
```
This also appears to be a good way to also deal with the problem of dynamic tabsets with plotly graphics at https://github.com/ropensci/plotly/issues/273
I created the plots first and saved them in a list which I then extract and display with a simple code in the template variable below. This saves me from having to enclose the large code in quotes in the template file.
However, if I try this trick a second time in the same document, it complains with a "duplicate chunk label "unnamed-chunk-1"... error and will not compile.
This appears to be "fixable" by specifying options(knitr.duplicate.label = "allow") but are there any "consequences" to allowing duplicate labels that I need to be aware of? I read https://bookdown.org/yihui/rmarkdown-cookbook/duplicate-label.html and I think I'm ok but is there a better way than allowing duplicate chunk labels?
---
title: "Tabsets for plotly graphs"
output:
html_document:
number_sections: yes
toc: yes
toc_depth: 4
---
## Tabbed Set 1 {.tabset}
```{r}
options(knitr.duplicate.label = "allow")
library(plotly)
plotlist <- plyr::dlply(iris, "Species", function(iris.part){
plot_ly(data=iris.part, x = ~Sepal.Length, y = ~Sepal.Width)
})
template <- c(
"### First {{nm}}\n",
"```{r, echo = FALSE}\n",
"plotlist[[{{nm}}]] \n",
"```\n",
"\n"
)
plots <- lapply(1:length(plotlist), function(nm) {knitr::knit_expand(text = template)})
```
`r knitr::knit(text = unlist(plots))`
## Tabbed set 2 {.tabset}
```{r}
library(plotly)
template <- c(
"### Second {{nm}}\n",
"```{r, echo = FALSE}\n",
"plotlist[[{{nm}}]] \n",
"```\n",
"\n"
)
plots <- lapply(1:length(plotlist), function(nm) {knitr::knit_expand(text = template)})
```
`r knitr::knit(text = unlist(plots))`

Resources