Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using R/exams in bookdown document (especially for HTML output)

I have created a "book" using bookdown. I would love to be able to add interactive quizzes, without needing shiny etc.

Is it possible using R/exams (http://www.R-exams.org/) with bookdown? I'm mainly interested in the HTML output; PDF output a bonus but hardly essential. The web page offers promise:

Based on (potentially) dynamic exercise templates large numbers of personalized exams/quizzes/tests can be created for various systems: [...] and the possibility to create custom output (in PDF, HTML, Docx, ...).

Exercise types include multiple-choice or single-choice questions, numeric or text answers, or combinations of these. Formatting can be done either in Markdown or LaTeX with the possibility to generate dynamic content using R, e.g., random numbers, graphics, data sets, or shuffled text blocks.

It sounds great. Does anyone know if it is possible to use exams with bookdown (even if just some features)?

If it is possible: how? Any pointers?

If it is not possible: does anyone know a way to do something similar?

like image 886
Pete Avatar asked Dec 10 '25 17:12

Pete


1 Answers

2024 update: exams2forms

There is now a dedicated R/exams interface in the package exams2forms which originated in this discussion and addresses exactly the question above. The discussion below is still helpful for understanding how to roll your own custom interface. But for including interactive exercises in bookdown or quarto, it is probably easiest to employ exams2forms. A tutorial is available at: https://www.R-exams.org/tutorials/exams2forms/


2020 original: General remarks

R/exams is indeed extensible leveraging its building blocks is relatively easy. The workhorse function underlying all the exams2xyz() interfaces is called xexams(). It proceeds in four steps:

  1. sweave: The exercise files are copied to a temporary directory and then run through R, by default using xweave() which provides a unified convenience interface to utils::Sweave() (for Rnw files) and knitr::knit() (for Rmd files).
  2. read: The resulting weaved files are read into R, by default using read_exercise(). For each exercise this yields a list of question, questionlist, solution, solutionlist, metainfo, and supplements. All elements are always there but may be empty, e.g., when there is no solution environment provided in the exercise or when there are no supplementary files.
  3. transform: By default this is empty but can be used to transform the exercise list elements above to a desired format, e.g., HTML.
  4. write: By default this is empty, but can be used to write out results for each of the n replications of the exam.

Embedding exercise texts in Markdown

When you write your exercises in R/Markdown (Rmd) files you can easily run them through xexams() to get some randomized version of them. As an example, let's consider the numeric (num) and single-choice (schoice) version of the derivative exercise, see: deriv, deriv2. Using 1 as the random seed, the numeric exercise has the following question along with the correct solution and tolerance:

set.seed(1)
d1 <- xexams("deriv.Rmd")[[1]][[1]]
d1$question
## [1] "What is the derivative of $f(x) = x^{2} e^{2.3 x}$, evaluated at $x = 0.56$?"
d1$metainfo$solution
## [1] 6.68
d1$metainfo$tolerance
## [1] 0.01

The reason for the [[1]][[1]] index is that this is from the first (and only) exam, the first (and only) exercise. If you generate, say, xexams(..., n = 3) then the first index could be in 1, 2, 3. Similarly, you could inlude more than one exercise if you want.

The single-choice version has

set.seed(1)
d2 <- xexams("deriv2.Rmd")[[1]][[1]]
d2$question
## [1] "What is the derivative of $f(x) = x^{2} e^{2.3 x}$, evaluated at $x = 0.66$?"
## [2] ""                                                                            
d2$questionlist
## [1] "$8.01$"  "$14.09$" "$10.59$" "$15.35$" "$6.02$" 
d2$metainfo$solution
## [1] FALSE FALSE  TRUE FALSE FALSE

Both of these would be very easy to integrate as static text into any R/Markdown document.

Embedding exercise texts in webex

To turn the static text into a dynamic element in HTML, e.g., a text field where readers could enter a number which is then compared with the reference value from the solution, it is possible to employ Javascript for example. One lightweight R-based framework for generating such output is the webex package by Dale Barr and Lisa DeBruine.

In webex you can create fill-in-the-blank interactions via fitb() for numeric solutions with an optional tolerance (num in R/exams) or for character solutions (string in R/exams). Also, you can create drop-down menu interactions via mcq() for single-choice questions (schoice in R/exams). (Note: The jargon regarding choice questions is not unified: What R/exams calls single-choice is also referred to as multiple-choice. In this case multiple-answer is often used for what R/exams calls multiple-choice.)

At the moment, webex does not support radio buttons as an alternative to drop-down menus. Also, check-boxes for multiple-choice (aka multiple-answer) questions is not available.

Below, I illustrate how to embed simple schoice, num, and string questions in webex. For more elaborate examples with supplementary files, see the comments below. Also, cloze would also be doable but take some more work.

---
title: "Web Exercises with R/exams & webex"
output: webex::webex_default
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(echo = TRUE)
library("webex")
library("exams")
```

`r style_widgets("#DF536B", "#61D04F")`

## `schoice`

```{r swisscapital, echo = FALSE, results = "asis"}
x <- xexams("swisscapital.Rmd")[[1]][[1]]
names(x$questionlist) <- ifelse(x$metainfo$solution, "answer", "")

x <- c(
  x$question,
  "",
  mcq(x$questionlist),
  "",
  hide("Correct solution"),
  "",
  x$solution,
  "",
  paste("*", x$solutionlist),
  "",
  unhide()
)
writeLines(x)
```

## `num`

```{r deriv, echo = FALSE, results = "asis"}
x <- xexams("deriv.Rmd")[[1]][[1]]

x <- c(
  x$question,
  "",
  fitb(x$metainfo$solution, tol = x$metainfo$tol,
    width = min(100, max(20, nchar(x$metainfo$solution)))),
  "",
  hide("Correct solution"),
  "",
  x$solution,
  "",
  unhide()
)
writeLines(x)
```

## `string`

```{r function, echo = FALSE, results = "asis"}
x <- xexams("function.Rmd")[[1]][[1]]

x <- c(
  x$question,
  "",
  fitb(x$metainfo$solution, width = min(100, max(20, nchar(x$metainfo$solution)))),
  "",
  hide("Correct solution"),
  "",
  x$solution,
  "",
  unhide()
)
writeLines(x)
```

Rendering this with rmarkdown::render() gives you a file like shown in the screenshot below. When embedding this in bookdown you need to make sure to embed the webex.css and webex.js from the package.

webex

Further variations

Some extra work is involved when processing exercises that contain images such as boxplots. The default in xexams() is set up for PDF output but the driver$sweave can be tweaked to produce PNG output. In either case, the supplements is then a vector of file paths to the supplementary files:

set.seed(1)
b1 <- xexams("boxplots.Rmd", driver = list(sweave = list(png = TRUE)))[[1]][[1]]
b1$question
## [1] "In the following figure the distributions of a variable"             
## [2] "given by two samples (A and B) are represented by parallel boxplots."
## [3] "Which of the following statements are correct?  _(Comment: The"      
## [4] "statements are either about correct or clearly wrong.)_"             
## [5] "\\"                                                                  
## [6] "![](boxplot-1.png)"                                                  
## [7] ""                                                                    
b1$supplements
##                                                      boxplot-1.png 
## "/tmp/RtmpA07Hau/file11d77d212e69bf/exam1/exercise1/boxplot-1.png" 
## attr(,"dir")
## [1] "/tmp/RtmpA07Hau/file11d77d212e69bf/exam1/exercise1"

Additionally, you can set up a transform driver that converts the R/Markdown already to HTML (rather than having bookdown doing this later on). Here I'm selecting pandoc as the converter, using MathJax for the rendering of mathematical content (like bookdown does as well). Using base64 = TRUE instead of the FALSE below would embed the supplementary PNG image directly in the HTML code using a Base 64 encoding.

set.seed(1)
htmltrafo <- make_exercise_transform_html(converter = "pandoc-mathjax", base64 = FALSE)
b2 <- xexams("boxplots.Rmd", driver = list(sweave = list(png = TRUE), transform = htmltrafo))[[1]][[1]]
b2$question
## [1] "<p>In the following figure the distributions of a variable given by two samples (A and B) are represented by parallel boxplots. Which of the following statements are correct? <em>(Comment: The statements are either about correct or clearly wrong.)</em><br />"
## [2] "<img src=\"boxplot-1.png\" /></p>"                                         
like image 196
Achim Zeileis Avatar answered Dec 13 '25 09:12

Achim Zeileis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!