Using R/Markdown fails inside learnr question - r

Motivation: I want to write an interface that uses questions from the R package exams in learnr questions/quizzes. In R/exams each question is either an R/Markdown (Rmd) or R/LaTeX (Rnw) file with a certain structure specifying question, solution, and further meta-information. The questions can contain R code to make them dynamic, e.g., sampling numbers or certain text building blocks etc. Hence, the workflow is that first the questions are run through knitr::knit or utils::Sweave and then embedded in a suitable output format.
Problem: When I rmarkdown::run("learnr+rexams.Rmd") a learnr tutorial that dynamically produces a question or quiz from an Rmd exercise I get the error:
Error in if (grepl(not_valid_char_regex, label)) { :
argument is of length zero
The code for a simple reproducible example learnr+rexams.Rmd is included below.
The reason for the error appears to be that learnr runs a function verify_tutorial_chunk_label() that tries to assure the the learnr R chunk labels are well formatted. However, confusion is caused by the chunks that are run by the R/exams package, unnecessarily leading to the error above.
Workarounds: I can disable the verify_tutorial_chunk_label() in the learnr namespace and then everything works well. Or I can use Rnw instead of Rmd exercises and then learnr does not conflict with Sweave(). Also, when I run my code outside of a learnr tutorial it works fine.
Question: Can I do anything less invasive to make exams cooperate with learnr? For example, setting some appropriate knitr options or something like that?
Example: This is the source for the minimal learnr tutorial learnr+rexams.Rmd that replicates the problem. Note that everything is very much simplified and only works for certain R/exams exercises, here using the function exercise template that ships with R/exams.
---
title: "learnr & R/exams"
output: learnr::tutorial
runtime: shiny_prerendered
---
```{r exams2learnr, include = FALSE}
exams2learnr <- function(file) {
x <- exams::xexams(file)[[1]][[1]]
x <- list(text = x$question, type = "learnr_text",
learnr::answer(x$metainfo$solution, correct = TRUE))
do.call(learnr::question, x)
}
## assignInNamespace("verify_tutorial_chunk_label", function() return(), ns = "learnr")
```
```{r rfunctions, echo = FALSE, message = FALSE}
exams2learnr("function.Rmd")
```
Running this tutorial (as noted above) replicates the error. To avoid it I can either uncomment the assignInNamespace() call or alternatively replace "function.Rmd" by "function.Rnw".

The problem is that by the time learnr::question() is called, knitr is no longer able to find the chunk label for the chunk where exams2learnr() was called. You can get around this by setting the current chunk label before calling do.call(learnr_question, x):
exams2learnr <- function(file, label = knitr::opts_current$get("label")) {
force(label)
x <- exams::xexams(file)[[1]][[1]]
x <- list(
text = x$question,
type = "learnr_text",
learnr::answer(x$metainfo$solution, correct = TRUE)
)
knitr::opts_current$set(label = label)
do.call(learnr::question, x)
}
This also lets you set the label dynamically if you want, which becomes the ID of the question in learnr.

Related

How to Customize figure LaTeX with knitr/rmarkdown

I want to create a footer within the float for a figure created with ggplot2 in an rmarkdown document that is generating a .pdf file via LaTeX.
My question: Is there a way within rmarkdown/knitr to add more LaTeX commands within the figure environment?
Specifically, I'd like to find a way to insert custom LaTeX using either the floatrow or caption* macro as described in https://tex.stackexchange.com/questions/56529/note-below-figure within the figure environment.
When I looked at the chunk options (https://yihui.org/knitr/options/#plots), something like out.extra seems close to what I want, but that is used as an extra option to \includegraphics while I want access to put extra LaTeX within the figure environment, outside of any other LaTeX command.
The solution to your question is perhaps quite similar to this one. However, I believe yours is a bit more general, so I'll try to be a bit more general as well...
As far as I know, there's no simple solution to add extra LaTeX code within the figure environment. What you can do is update the knit (or output) hook (i.e. the LaTeX code output generated by the figure chunk).
The source code for the LaTeX figure output hook can be found here (hook_plot_tex). The output generated can be found starting at line 159. Here we can see how the output is structured and we're able to modify it before it reaches the latex engine.
However, we only want to modify it for relevant figure chunks, not all. This is where Martin Schmelzer's answer comes in handy. We can create a new chunk option which allows for control over when it is activated. As an example enabling the use of caption* and floatrow we can define the following knit hook
defOut <- knitr::knit_hooks$get("plot")
knitr::knit_hooks$set(plot = function(x, options) {
#reuse the default knit_hook which will be updated further down
x <- defOut(x, options)
#Make sure the modifications only take place when we enable the customplot option
if(!is.null(options$customplot)) {
x <- gsub("caption", "caption*", x) #(1)
inter <- sprintf("\\floatfoot{%s}\\end{figure}", options$customplot[1]) #(2)
x <- gsub("\\end{figure}", inter, x, fixed=T) #(3)
}
return(x)
})
What we're doing here is (1) replacing the \caption command with \caption*, (2) defining the custom floatfoot text input, (3) replacing \end{figure} with \floatfoot{custom text here}\end{figure} such that floatfoot is inside the figure environment.
As you can probably tell, sky's the limit for what you can add/replace in the figure environment. Just make sure it is added inside the environment and is in the apropriate location. See the example below how the chunk option is used to enable floatfoot and caption*. (You can also split the customplot option into e.g. starcaption and floatfoot by simply dividing up the !is.null(options$customplot) condition. This should allow for better control)
Working example:
---
header-includes:
- \usepackage[capposition=top]{floatrow}
- \usepackage{caption}
output: pdf_document
---
```{r, echo = F}
library(ggplot2)
defOut <- knitr::knit_hooks$get("plot")
knitr::knit_hooks$set(plot = function(x, options) {
x <- defOut(x, options)
if(!is.null(options$customplot)) {
x <- gsub("caption", "caption*", x)
inter <- sprintf("\\floatfoot{%s}\\end{figure}", options$customplot[1])
x <- gsub("\\end{figure}", inter, x, fixed=T)
}
return(x)
})
```
```{r echo = F, fig.cap = "Custom LaTeX hook chunk figure", fig.align="center", customplot = list("This is float text using floatfoot and floatrow")}
ggplot(data = iris, aes(x=Sepal.Length, y=Sepal.Width))+
geom_point()
```
PS
The example above requires the fig.align option to be enabled. Should be fairly easy to fix, but I didn't have the time to look into it.
#henrik_ibsen gave the answer that got me here. I made some modifications to the code that I ended up using to make it work a bit more simply:
hook_plot_tex_footer <- function(x, options) {
x_out <- knitr:::hook_plot_tex(x, options)
if(!is.null(options$fig.footer)) {
inter <- sprintf("\\floatfoot{%s}\n\\end{figure}", options$fig.footer[1])
x_out <- gsub(x=x_out, pattern="\n\\end{figure}", replacement=inter, fixed=TRUE)
}
x_out
}
knitr::knit_hooks$set(plot=hook_plot_tex_footer)
Overall, this does the same thing. But, it uses knitr:::hook_plot_tex() instead of defOut() so that if rerun in the same session, it will still work. And, since it's going into a \floatfoot specifically, I named the option fig.footer. But, those changes are minor and the credit for the answer definitely goes to #henrik_ibsen.

kable all tables in markdown report [duplicate]

Using knitr and R Markdown, I can produce a tabularised output from a matrix using the following command:
```{r results='asis'}
kable(head(x))
```
However, I’m searching for a way to make the kable code implicit since I don’t want to clutter the echoed code with it. Essentially, I want this:
```{r table=TRUE}
head(x)
```
… to yield a formatted tabular (rather than the normal output='markdown') output.
I actually thought this must be pretty straightforward since it’s a pretty obvious requirement, but I cannot find any way to achieve this, either via the documentation or on the web.
My approach to create an output hook fails because once the data arrives at the hook, it’s already formatted and no longer the raw data. Even when specifying results='asis', the hook obtains the output as a character string and not as a matrix. Here’s what I’ve tried:
default_output_hook <- knit_hooks$get('output')
knit_hooks$set(output = function (x, options)
if (! is.null(options$table))
kable(x)
else
default_output_hook(x, options)
)
But like I said, this fails since x is not the original matrix but rather a character string, and it doesn’t matter which value for the results option I specify.
Nowadays one can set df_print in the YAML header:
---
output:
html_document:
df_print: kable
---
```{r}
head(iris)
```
I think other answers are from a time when the following didn't work, but now we can just do :
```{r results='asis', render=pander::pander}
head(x)
```
Or set this for all chunks in the setup chunk, for instance :
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, render=pander::pander)
```
Lacking a better solution I’m currently re-parsing the character string representation that I receive in the hook. I’m posting it here since it kind of works. However, parsing a data frame’s string representation is never perfect. I haven’t tried the following with anything but my own data and I fully expect it to break on some common use-cases.
reparse <- function (data, comment, ...) {
# Remove leading comments
data <- gsub(sprintf('(^|\n)%s ', comment), '\\1', data)
# Read into data frame
read.table(text = data, header = TRUE, ...)
}
default_output_hook <- knit_hooks$get('output')
knit_hooks$set(output = function (x, options)
if (is.null(options$table))
default_output_hook(x, options)
else {
extra_opts <- if (is.list(options$table)) options$table else list()
paste(kable(do.call(reparse, c(x, options$comment, extra_opts))),
collapse = '\n')
}
)
This will break if the R markdown comment option is set to a character sequence containing a regular expression special char (e.g. *), because R doesn’t seem to have an obvious means of escaping a regular expression.
Here’s a usage example:
```{r table=TRUE}
data.frame(A=1:3, B=4:6)
```
You can pass extra arguments to the deparse function. This is necessary e.g. when the table contains NA values because read.table by default interprets them as strings:
```{r table=list(colClasses=c('numeric', 'numeric'))}
data.frame(A=c(1, 2, NA, 3), B=c(4:6, NA))
```
Far from perfect, but at least it works (for many cases).
Not exactly what you are looking for, but I am posting an answer here (that could not fit in a comment) as your described workflow is really similar to what my initial goal and use-case was when I started to work on my pander package. Although I really like the bunch of chunk options that are available in knitr, I wanted to have an engine that makes creating documents really easy, automatic and without any needed tweaks. I am aware of the fact that knitr hooks are really powerful, but I just wanted to set a few things in my Rprofile and let the literate programming tool its job without further trouble, that ended to be Pandoc.brew for me.
The main idea is to specify a few options (what markdown flavour are you using, what's your decimal mark, favorite colors for your charts etc), then simply write your report in a brew syntax without any chunk options, and the results of your code would be automatically transformed to markdown. Then convert that to pdf/docx/odt etc. with Pandoc.

Which knitr hooks are invoked when a DiagrammeR graph is rendered in rmarkdown?

I have an RMarkdown document where I use data.tree and DiagrammeR to generate models. I then display these using a set-up similar to the one used in How to include DiagrammeR/mermaid flowchart in a Rmarkdown file
For example:
```{r fig.cap="Structureel model bij enkelvoudige regressie.", fig.with=4, fig.height=1}
drawStructuralModel <- function(covariates, criterion) {
### Create tree
res <- Node$new(criterion);
for (covariate in covariates) {
res$AddChild(covariate);
}
### Set display settings
SetEdgeStyle(res, dir='back');
res <- ToDiagrammeRGraph(res, direction = "descend");
res <- add_global_graph_attrs(res, "layout", "dot", "graph");
res <- add_global_graph_attrs(res, "rankdir", "RL", "graph");
### Draw and return tree
render_graph(res);
}
drawStructuralModel("X", "Y");
```
So far so good. The caption text is added, which is what you'd expect.
Except :-)
Above, in the 'setup' knitr chunk, I used setFigCapNumbering from userfriendlyscience (see https://github.com/Matherion/userfriendlyscience/blob/master/R/setFigCapNumbering.R). This function uses knit_hooks$set to set a hook for plots, so that the captions are automatically numbered.
But this numbering isn't applied to the DiagrammeR output.
I guess that makes sense, since it's not actually a plot, rather an HTML widget or some SVG or so. I would still like it to be numbered as a figure, though.
But how do I find out which hook knitr invokes when DiagrammeR output is generated?
I could always resort to using the more generic 'automatic caption' function setCaptionNumbering (https://github.com/Matherion/userfriendlyscience/blob/master/R/setCaptionNumbering.R), and tell it to use the same counter option as the figure caption thingy uses. That would sidestep the issue, but I'd prefer to modify the appropriate knitr hook.
And since this problem (figuring out which hook knitr uses for output produced by a given function) probably occurs more often, I thought it would be worth opening an SA question.
Does anybody know how you can find this out?
knitr calls knit_print on the output produced by a given chunk. This then calls the appropriate method based on the class of the output. You can find out what methods are available by running methods("knit_print"), then see if any of those match the class of the output from DiagrammeR.
Looking at your example, I'm guessing the class of your output is "DiagrammeR" "htmlwidget", so knitr is calling knit_print.htmlwidget.
Digging into that source code, it calls htmltools::knit_print.html, which wraps the tags and then outputs asis. So to answer your question, it uses the default hooks for asis output in whatever output format you're using.

Source code from Rmd file within another Rmd

I'm attempting to make my code more modular: data loading and cleaning in one script, analysis in another, etc. If I were using R scripts, this would be a simple matter of calling source on data_setup.R inside analysis.R, but I'd like to document the decisions I'm making in an Rmarkdown document for both data setup and analysis. So I'm trying to write some sort of source_rmd function that will allow me to source the code from data_setup.Rmd into analysis.Rmd.
What I've tried so far:
The answer to How to source R Markdown file like `source('myfile.r')`? doesn't work if there are any repeated chunk names (a problem since the chunk named setup has special behavior in Rstudio's notebook handling). How to combine two RMarkdown (.Rmd) files into a single output? wants to combine entire documents, not just the code from one, and also requires unique chunk names. I've tried using knit_expand as recommended in Generate Dynamic R Markdown Blocks, but I have to name chunks with variables in double curly-braces, and I'd really like a way to make this easy for my colaborators to use as well. And using knit_child as recommended in How to nest knit calls to fix duplicate chunk label errors? still gives me duplicate label errors.
After some further searching, I've found a solution. There is a package option in knitr that can be set to change the behavior for handling duplicate chunks, appending a number after their label rather than failing with an error. See https://github.com/yihui/knitr/issues/957.
To set this option, use options(knitr.duplicate.label = 'allow').
For the sake of completeness, the full code for the function I've written is
source_rmd <- function(file, local = FALSE, ...){
options(knitr.duplicate.label = 'allow')
tempR <- tempfile(tmpdir = ".", fileext = ".R")
on.exit(unlink(tempR))
knitr::purl(file, output=tempR, quiet = TRUE)
envir <- globalenv()
source(tempR, local = envir, ...)
}

Use hooks to format table in output

Using knitr and R Markdown, I can produce a tabularised output from a matrix using the following command:
```{r results='asis'}
kable(head(x))
```
However, I’m searching for a way to make the kable code implicit since I don’t want to clutter the echoed code with it. Essentially, I want this:
```{r table=TRUE}
head(x)
```
… to yield a formatted tabular (rather than the normal output='markdown') output.
I actually thought this must be pretty straightforward since it’s a pretty obvious requirement, but I cannot find any way to achieve this, either via the documentation or on the web.
My approach to create an output hook fails because once the data arrives at the hook, it’s already formatted and no longer the raw data. Even when specifying results='asis', the hook obtains the output as a character string and not as a matrix. Here’s what I’ve tried:
default_output_hook <- knit_hooks$get('output')
knit_hooks$set(output = function (x, options)
if (! is.null(options$table))
kable(x)
else
default_output_hook(x, options)
)
But like I said, this fails since x is not the original matrix but rather a character string, and it doesn’t matter which value for the results option I specify.
Nowadays one can set df_print in the YAML header:
---
output:
html_document:
df_print: kable
---
```{r}
head(iris)
```
I think other answers are from a time when the following didn't work, but now we can just do :
```{r results='asis', render=pander::pander}
head(x)
```
Or set this for all chunks in the setup chunk, for instance :
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, render=pander::pander)
```
Lacking a better solution I’m currently re-parsing the character string representation that I receive in the hook. I’m posting it here since it kind of works. However, parsing a data frame’s string representation is never perfect. I haven’t tried the following with anything but my own data and I fully expect it to break on some common use-cases.
reparse <- function (data, comment, ...) {
# Remove leading comments
data <- gsub(sprintf('(^|\n)%s ', comment), '\\1', data)
# Read into data frame
read.table(text = data, header = TRUE, ...)
}
default_output_hook <- knit_hooks$get('output')
knit_hooks$set(output = function (x, options)
if (is.null(options$table))
default_output_hook(x, options)
else {
extra_opts <- if (is.list(options$table)) options$table else list()
paste(kable(do.call(reparse, c(x, options$comment, extra_opts))),
collapse = '\n')
}
)
This will break if the R markdown comment option is set to a character sequence containing a regular expression special char (e.g. *), because R doesn’t seem to have an obvious means of escaping a regular expression.
Here’s a usage example:
```{r table=TRUE}
data.frame(A=1:3, B=4:6)
```
You can pass extra arguments to the deparse function. This is necessary e.g. when the table contains NA values because read.table by default interprets them as strings:
```{r table=list(colClasses=c('numeric', 'numeric'))}
data.frame(A=c(1, 2, NA, 3), B=c(4:6, NA))
```
Far from perfect, but at least it works (for many cases).
Not exactly what you are looking for, but I am posting an answer here (that could not fit in a comment) as your described workflow is really similar to what my initial goal and use-case was when I started to work on my pander package. Although I really like the bunch of chunk options that are available in knitr, I wanted to have an engine that makes creating documents really easy, automatic and without any needed tweaks. I am aware of the fact that knitr hooks are really powerful, but I just wanted to set a few things in my Rprofile and let the literate programming tool its job without further trouble, that ended to be Pandoc.brew for me.
The main idea is to specify a few options (what markdown flavour are you using, what's your decimal mark, favorite colors for your charts etc), then simply write your report in a brew syntax without any chunk options, and the results of your code would be automatically transformed to markdown. Then convert that to pdf/docx/odt etc. with Pandoc.

Resources