A common scenario in my day-to-day data analysis is that I have many objects that are typically data frames, and for each of them I want to create a new section in my Quarto/R Markdown document with their respective summary statistics. The set of operations to apply are usually the same, just the data is different. A manual way of doing this would be copy-pasting the same chunk of code for generating the summaries, and only change the variable name. For instance:
This becomes a pain when they are just so many objects and I have to
change the name one by one. This post introduces a programmatic way of
automating this task using knit_child
from the knitr
rendering
engine.
Using the knitr
Engine
The knitr
engine has built-in supports for dynamic, parameterized
documents. For R Markdown, you can pass data to the document using the
params
argument in rmarkdown::render()
, learn more at
https://bookdown.org/yihui/rmarkdown/parameterized-reports.html. This
is intended for rendering one big parameterized .Rmd
document.
When it comes to inserting sub-contents into either .qmd
or .Rmd
files, knitr:::knit_child
is the function you need. Similar to
params
in rmarkdown::render
, you can pass a environment object to
knitr:::knit_child
using the envir
argument. The returned value of
knit_child
is simply the character string that can be directly
embedded into a document, it’s also used in conjunction with the chunk
option output = 'asis'
(Quarto) or results = 'asis'
(R Markdown),
knit_child
accepts both an inline string text
or a file path file
.
Below we use the inline string to generate a summary of the iris
data
frame.
All these combined, we could divide the job into a main document
main.Rmd
and a template document _template.Rmd
. In the main
document, we collect all the data, loop through them and insert them
into the template document using knitr:::knit_child
. Then we can use
the asis
output option to include the template document into the main
document.
This will also work for Quarto documents as long as we are using the knitr engine.
The example main and template documents are shown below:
In main.Rmd
In _template.Rmd
I’ve defined a wrapper function render_child()
that uses
knit_child()
under the hood. The key is you can pass arbitrary values
using the envir
argument. Whatever you need in the template document,
you can pass them in as a named list and convert them into an
environment object using rlang::env()
.
When you run the main document, it will generate a list of random data frames, generate a child document for each using the template document, and inserts the result back to the main document. The result is shown below:
A Note on knitr::knit_expand
R Markdown
Cookbook
also introduces a function called knitr::knit_expand()
that can be
used to insert contents into a template content. An example is shown
below:
Since you can also pass a file
argument to knit_expand
, I was
tempted to use this function with knit_child
when I started to write
this post. For example
Then you can access iris
in the template document like so
But this will not work. knitr::knit_expand
simply finds all the
placeholders marked by { }
, interpolates them and then pass the text
to the knitting functions. This mechanism works fine you only need to
insert simple primitives, such as strings and numbers, but not if you
want to pass data.frames or other complex objects in general. Then the
knitting functions will see a code chunk like this
which is not valid R syntax.
Without knitr
It you are using Quarto with a different engine than knitr
, I found no
similar construct as knitr::knit_child
. Although Quarto offers a
native variables
mechanism for reading values from configuration files, it’s intended for
static data that should be shared across multiple documents. It also
suffers the same problem as knitr::knit_expand
that you are limited to
whatever data structure that is supported by YAML.