Yihui Xie

One Little Thing: Reusing Code Chunks and Chunk Options with knitr

Yihui Xie / 2021-05-28

Most software users must have heard of the DRY principle (i.e., “Don’t Repeat Yourself”). If you repeat a piece of code or text for multiple times in a document, it will be a pain to maintain these copies. Ideally, you only maintain a single copy, and reuse it programmatically in other places.

For knitr documents, you can freely reuse code chunks. We have introduced three methods in the R Markdown Cookbook (Section 14.1). These methods teach you how to reuse the code of a code chunk in other code chunks. As you know, a code chunk consists of two parts: the code, and the chunk options. What we did not cover in the book was how to reuse chunk options.

Preset options in knitr::opts_template

Jared Lander is a very early adopter of knitr and uses it extensively. I was not surprised to know that he was not aware of knitr::opts_template. This feature of setting chunk options from an “option template” was introduced in 2012, and documented in the book “Dynamic Documents with R and knitr,” which was first published in 2013 (my first and only non-free book).

The idea is to save users substantial typing effort by storing a group of chunk options in the object knitr::opts_template with an ID. Then use the chunk option opts.label to refer to the ID and apply the pre-saved options to the current code chunk. For example,

  fig.large = list(fig.width = 10, fig.height = 6, dev = 'svg'),
  fig.small = list(fig.width = 5, fig.height = 3, dev = 'png'),
  no.message = list(message = FALSE, warning = FALSE)

Then you can (re)use these chunk options via the opts.label option, e.g.,

```{r, opts.label='fig.large'}

The above chunk is equivalent to:

```{r, fig.width=10, fig.height=6, dev='svg'}

Similarly, you can reuse other groups of options:

```{r, opts.label='fig.small'}

You can use a vector of labels, too:

```{r, opts.label=c('fig.large', 'no.message')}

You can reuse these chunk options for as many times as you like.

Reuse chunk options from another code chunk

Sometimes you may want to just borrow chunk options from an existing code chunk, instead of setting them in knitr::opts_template in advance. Before knitr v1.32, this was not possible. Thanks to Atsushi, this has finally become possible. The usage is very straightforward: you still use opts.label but its value can be the labels of existing code chunks. For example,

```{r, chunk-a, fig.width=10, fig.height=6, dev='svg'}

Reuse chunk options from chunk-a:

```{r, chunk-b, opts.label='chunk-a'}

Reuse both chunk options and chunk content from another chunk

It is common to reuse the full code chunk (both code and options) for demo purposes. For example, in this blog post, the penguin plot appeared four times, but they are actually from the same code chunk. We did not copy but reused the code chunk three times. Each time we changed one or more chunk options locally after importing them from the first code chunk, to demonstrate the effect of the changed option. An example should explain this idea more clearly. First, we define the “template” chunk, with the label chunk-a:

```{r, chunk-a, fig.cap="A template figure.", dev='svg'}

To fully reuse a code chunk, you can use the chunk option ref.label (note that it is ref.label, not opts.label) and pass a chunk label wrapped in I() to it, e.g.,

```{r, chunk-b, ref.label=I('chunk-a')}

That means both the content and options of chunk-a will be applied to chunk-b. Without the I() trick, only the code content of chunk-a will be applied to chunk-b. If you feel I() is black magic, you can use opts.label = TRUE instead—ref.label = I('foo') is just a shortcut of ref.label = 'foo' and opts.label = TRUE (opts.label = TRUE means inheriting chunk options). The marker I() often indicates that the value is “special.”

With ref.label, you can still add more local options to a chunk header. For example, we can define the alt text for the figure via the fig.alt option:

```{r, chunk-c, ref.label=I('chunk-a'), fig.alt='Yeah, alt text!'}

Note that local chunk options have higher precedence, meaning that a local option will override a reused option. For example, we have specified dev = 'svg' in chunk-a, and we can override it in another chunk while inheriting the rest of chunk options:

```{r, chunk-d, ref.label=I('chunk-a'), dev='png'}

Then for this chunk-d, its dev option will be png instead of svg.

Final words

With all these methods, it should be more flexible to author and program a knitr document. They also manifest the spirit of Literate Programming (LP)—you should be able to program a document. This aspect of LP is not commonly seen in tools that claim to be based on LP.

Anyway, LP aside, the above methods can certainly help you stick more tightly to the DRY principle. Once again, I’d like to thank Atsushi for the contribution. These are the features that I had wished to exist and wanted to use for several years. I’m really glad that they have finally arrived.