Creating R, Python, Stata, and Julia tutorial worksheets (with and without solutions) using Quarto

[This article was first published on R | Dr Tom Palmer, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Introduction

I regularly need to produce exercises/tutorials for my students. One fantastic feature of R Markdown is that it allows me to create one R Markdown document which can be rendered to both the question document and the solutions document. This is achieved by toggling knitr chunk options such as eval, echo, and include, and using asis chunks to include the text for the solutions. I wrote a little package, knitexercise to help with this.

The toggling of the knitr chunk options can be parameterised making it possible to have an R script which contains the code to conveniently produce both questions and solutions documents. An example R Markdown file, exercise.Rmd, might look like the following.

---
title: "`r params$title`"
output: html_document
params:
  solutions: TRUE
  title: "Example exercise: Solutions"
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(include = params$solutions)
```

1. This is question 1. Which might have some R code you always want to show.

   ```{r, include=TRUE}
   # example code for the question
   ```

   ```{asis}
   Paragraph text for the solution can be kept in the document in an `asis` chunk.
   And solution R code in an `r` chunk.
   Both of these will use the `include` value from the `setup` chunk.
   ```
    
   ```{r}
   # example code for the solution
   ```

2. This is question 2 ...

And the rendering script might look like this.

rmarkdown::render("exercise.Rmd",
  output_file = "exercise-questions",
  params = list(solutions = FALSE, title = "Example exercise: Questions")
)

rmarkdown::render("exercise.Rmd",
  output_file = "exercise-solutions"
)

Inline code

Inline code has been a feature of R Markdown for a while. Yihui Xie and Christophe Dervieux have used it to pull off some fantastic tricks. My favourite trick is using it to programmatically write out R Markdown/Markdown code within an R Markdown document when a certain condition is met.

`r if (knitr::is_latex_output()) "...some_Markdown_to_include_when_rendering_to_pdf..."`

The problem

R Markdown is incredibly flexible because we can include R objects as document and chunk options. When translating this to a pure Quarto version we can do this in a document using in the knitr engine using the ! expr ... YAML tag literal. As far as I am aware, this is not yet possible for any other Quarto engine. Despite this my aim was to see whether I could achieve parameterised conditional content inclusion/exclusion in a Quarto document using other engines.

Programmatically including conditional content in Quarto documents

R: knitr and Quarto

I have already shown the R Markdown approach above.

For Quarto using the knitr engine, in a blog post Nicola Rennie used inline code to write out Quarto’s conditional content classes.

---
format: html
params:
  hide_answers: true
---

```{r}
#| include: false
# An R code chunk, so inline code is not first in document.
```

`r if (params$hide_answers) "::: {.content-hidden}"`

Text and code for answers.

`r if (params$hide_answers) ":::"`

Note that if you use the curly braces around the r to write you inline code then you need to enclose the output string in the I() function as follows.

`{r} if (params$hide_answers) I("::: {.content-hidden}")`

Then we can have a shell script to render our questions and solutions documents as follows.

quarto render exercise-r.qmd -o exercise-r-questions.html
quarto render exercise-r.qmd -P hide_answers:false -o exercise-r-solutions.html

Python

I realised I could adapt Nicola Rennie’s approach for other engines. For the jupyter: python3 engine we can do so as follows, the following code uses Quarto’s parameters feature.

---
format: html
jupyter: python3
---

```{python}
#| include: false
#| tags: [parameters]
hide_answers = True
```

```{python}
#| include: false
from IPython.display import Markdown
```

`{python} Markdown("::: {.content-hidden}") if hide_answers else Markdown(" ")`

```{python}
print("Hidden in questions")
```

`{python} Markdown(":::") if hide_answers else Markdown(" ")`

The shell script to render our questions and solutions documents is as follows.

quarto render exercise-python.qmd -o exercise-python-questions.html
quarto render exercise-python.qmd -P hide_answers:False -o exercise-python-solutions.html

The only problem with this is that currently the Papermill output cell about the injected parameters is printed in the document.

Screenshot of the injected parameters cell in a Quarto document.

In Jupyter this can apparently be suppressed by passing the --report-mode flag, but I couldn’t work out how to do that in Quarto. I believe it will be possible to suppress this output in future versions of Quarto (I’m using the current latest release version of Quarto 1.6.40).

Stata

The Stata case involved two additional tricks. First, the nbstata Jupyter kernel allows inline code, however this must be a display command, so we cannot write the if statement within the inline code. I found I can overcome this by saving the different strings in scalars (because the inline code can’t use local macros) at the top of the document as follows. Second, I didn’t try but I suspect the nbstata kernel doesn’t support parameters, and so I achieved the toggling of the code using an environmental variable, e.g. HIDE_ANSWERS_STATA.

---
format: html
jupyter: nbstata
---

```{stata}
*| include: false
local hide_answers : env HIDE_ANSWERS_STATA
if (`hide_answers') {
    scalar hide_answers_open = "::: {.content-hidden}"
    scalar hide_answers_close = ":::"     
}
else {
    scalar hide_answers_open = " "
    scalar hide_answers_close = " "
}
```

`{stata} scalar(hide_answers_open)`

```{stata}
display "Hidden in questions"
```

`{stata} scalar(hide_answers_close)`

The shell script to render the documents is then as follows, here we define the environment variable before the call to quarto.

HIDE_ANSWERS_STATA=1 quarto render exercise-stata.qmd -o exercise-stata-questions.html
HIDE_ANSWERS_STATA=0 quarto render exercise-stata.qmd -o exercise-stata-solutions.html

Julia

For the native Julia engine I found that Quarto’s parameterisation worked and that I could avoid the inclusion of the injected parameters cell output by leaving the chunk with the parameters tag empty.

---
format: html
engine: julia
---

```{julia}
#| tags: [parameters]
```

```{julia}
#| include: false
using Markdown
```

`{julia} hide_answers ? md"::: {.content-hidden}" : md""`

```{julia}
println("Hidden in questions")
```

`{julia} hide_answers ? md":::" : md""`

The shell script to render the documents is then as follows.

quarto render exercise-julia.qmd -P hide_answers:true -o exercise-julia-questions.html
quarto render exercise-julia.qmd -P hide_answers:false -o exercise-julia-solutions.html

Problems with environment variables

I found that passing environment variables, to the jupyter: python3 and engine: julia is unreliable/broken. Admittedly, I was not using Quarto in project mode with an _environment file, but honestly it doesn’t feel to me I should need to do that for a single document.

The problem I found was that after a first render the value of the environment variable seems to be cached within the Quarto output document and I couldn’t change it on subsequent renders. I also found that using the dotenv package to access was broken in the same way.

For engine: julia I also found that passing environment variables is unreliable and like for the jupyter: python3 engine I experienced environment variable values being stuck after the first render. However, using the Julia DotEnv package did seem to be reliable.

Summary

I have shown how to programmatically include conditional content for several Quarto engines (knitr, jupyter: python3, jupyter: nbstata, and engine: julia) using parameters or environment variables to toggle inline code to write Quarto Markdown in the Quarto documents. I use this to write exercise/tutorial documents in which a single Quarto document is used to output both the questions and solutions documents.

To leave a comment for the author, please follow the link and comment on their blog: R | Dr Tom Palmer.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)