bookdown + exams + webex
Compiling reproducible exercises for your book
It’s been three years since I’ve been using package bookdown
for compiling and distributing three different books in Amazon and the web. It helped me greatly in all my book projects and I’m always grateful to Yihui Xie for providing such a useful tool at the right time.
However, bookdown
offers no support for chapter exercises of any sort. While you can write exercises in plain RMarkdown, it is not a good solution for a long term project such as a technical book. When writing the latest edition of Analyzing Financial and Economical Data with R, I aimed for a work cycle where the 100 plus exercises and their solutions were reproducible and easier to maintain.
Meanwhile, package exams
provides a framework to produce exercises in a reproducible setup, making it possible to export the exercises to any given format such as pdf or html, or even e-learning platforms such as Moodle and Blackboard. I use exams
extensively in all my university classes and it works like a charm!
So, while writing afedR, I worked towards finding a way to bring the two technologies closer to each other, which is what I’ll report in this blog post. Here are the main advantages of this setup:
- The content of book exercises, their solution and explanation in a single location (no more fidling with different folders).
- Dynamic output for html, with buttons and solutions available at a single click.
- Exportable exercises for classes (see this blog post). You can export the same exercises to pdf or Moodle, for example.
bookdown
+ exams
+ webex
First and foremost, the main part of the hack is to realize that any exercises in a .Rmd file can be broken into a list using exams::xexams
. Let’s use an example from the book, with the first three exercises of chapter 01:
# example from book
afedR::copy_book_files(path_to_copy = tempdir())
## Copying data files files to /tmp/RtmpIs4EpM/afedR files/data
## 37 files copied
## Copying end-of-chapter (eoc) exercises with solutions to /tmp/RtmpIs4EpM/afedR files/eoc-exercises/
## 99 files copied
## Copying R code to /tmp/RtmpIs4EpM/afedR files/R-code
## 15 files copied
# temp folder with exercises
eoc_dir <- file.path(tempdir(), 'afedR files/eoc-exercises/')
# select exercises
my_exercises <- list.files(eoc_dir, pattern = '*.Rmd', full.names = TRUE)
my_exercises <- my_exercises[1:3]
# break it down
my_l <- exams::xexams(my_exercises)
# check it
dplyr::glimpse(my_l)
## List of 1
## $ exam1:List of 3
## ..$ exercise1:List of 6
## .. ..$ question : chr [1:3] "" "The R language was developed based on what other programming language?" ""
## .. ..$ questionlist: chr [1:5] "C++" "Python" "Julia" "Javascript" ...
## .. ..$ solution : chr [1:2] "" "Straight from the book, section **What is R**: \"R is a modern version of S, a programming language originally "| __truncated__
## .. ..$ solutionlist: NULL
## .. ..$ metainfo :List of 18
## .. ..$ supplements : Named chr(0)
## .. .. ..- attr(*, "names")= chr(0)
## .. .. ..- attr(*, "dir")= chr "/tmp/RtmpIs4EpM/file4e094f974499/exam1/exercise1"
## ..$ exercise2:List of 6
## .. ..$ question : chr [1:3] "" "What are the names of the two authors of R?" ""
## .. ..$ questionlist: chr [1:5] "Linus Torvalds and Richard Stallman" "John Chambers and Robert Engle" "Roger Federer and Rafael Nadal" "Guido van Rossum and Bjarne Stroustrup" ...
## .. ..$ solution : chr [1:3] "" "Straight from the book: \"... The base code of R was developed by two academics, **Ross Ihaka** and **Robert Ge"| __truncated__ ""
## .. ..$ solutionlist: NULL
## .. ..$ metainfo :List of 18
## .. ..$ supplements : Named chr(0)
## .. .. ..- attr(*, "names")= chr(0)
## .. .. ..- attr(*, "dir")= chr "/tmp/RtmpIs4EpM/file4e094f974499/exam1/exercise2"
## ..$ exercise3:List of 6
## .. ..$ question : chr [1:4] "" "Why is R special when comparing to other programming languages, such as Python, C++, javascript and others?" "" ""
## .. ..$ questionlist: chr [1:5] "It was designed for analyzing data and producing statistical output" "Easy to use" "Works on any plataform such as Windows, Unix, MacOS" "Makes it easy to write mobile apps" ...
## .. ..$ solution : chr [1:2] "" "Undoubtedly, the main differential of the R language is the ease with which data can be analyzed on the platfor"| __truncated__
## .. ..$ solutionlist: NULL
## .. ..$ metainfo :List of 18
## .. ..$ supplements : Named chr(0)
## .. .. ..- attr(*, "names")= chr(0)
## .. .. ..- attr(*, "dir")= chr "/tmp/RtmpIs4EpM/file4e094f974499/exam1/exercise3"
As an example, in this list you can see the main text of the question 01 in slot l_out$exam1$exercise1$question
:
my_l$exam1$exercise1$question
## [1] ""
## [2] "The R language was developed based on what other programming language?"
## [3] ""
And the solution at my_l$exam1$exercise1$solution
my_l$exam1$exercise1$solution
## [1] ""
## [2] "Straight from the book, section **What is R**: \"R is a modern version of S, a programming language originally created in Bell Laboratories (formerly AT&T, now Lucent Technologies).\""
In my case, I wanted the html version of the book to have all the solutions hidden by a clickable button – just like in webex
– while the pdf and ebook would only have the text of the questions. Here are the functions I used:
compile_eoc_exercises <- function(files_in, type_doc) {
my_counter <<- 1
if (is.null(type_doc)) {
type_doc = 'html'
#type_doc = 'latex'
}
for (i_ex in files_in) {
exercise_to_html(i_ex, my_counter = my_counter,
type_doc)
my_counter <<- my_counter +1
}
return(invisible(TRUE))
}
exercise_to_html <- function(f_in, my_counter, type_doc) {
require(exams)
require(webex)
require(tidyverse)
text_pre_solution <- paste0('To reach the same result, you must execute the code below. ',
'For that, open a new R script in RStudio (Control+shift+N), ',
'copy and paste the code, and execute it whole by pressing ',
'Control+Shift+Enter or line by line with shortcut ',
'Control+Enter.')
my_dir <- file.path(tempdir(), basename(tempfile()))
dir.create(my_dir)
suppressMessages({
l_out <- exams::xexams(f_in, driver = list(sweave = list(png = TRUE)),
dir = my_dir)
})
exercise_text <- paste0(l_out$exam1$exercise1$question, collapse = '\n')
alternatives <- l_out$exam1$exercise1$questionlist
correct <- l_out$exam1$exercise1$metainfo$solution
solution <- paste0(l_out$exam1$exercise1$solution,
collapse = '\n')
ex_type <- l_out$exam1$exercise1$metainfo$type
if (type_doc %in% c('latex', 'epub3')) {
my_str <- str_glue('\n\n {sprintf("%02d", my_counter)} - {exercise_text} \n\n ')
if (ex_type == 'schoice') {
n_alternatives <- length(alternatives)
for (i_alt in seq(1, n_alternatives)) {
my_str <- paste0(my_str,
letters[i_alt],') ', alternatives[i_alt],
'\n')
}
}
cat(my_str)
return(invisible(TRUE))
} else if (type_doc == 'html') {
if (ex_type == 'schoice') {
vec_mcq <- sample(
c(alternatives[!correct],
answer = alternatives[correct])
)
my_answers_text <- str_glue('<br> Solution: {mcq(vec_mcq)}')
numeric_sol <- alternatives[correct]
text_sol <- str_glue('The solution is {numeric_sol}. {text_pre_solution}')
} else if (ex_type == 'num') {
numeric_sol <- correct
my_answers_text <- str_glue('<br><br> Your Answer: {fitb(correct)}')
text_sol <- str_glue('The solution is {numeric_sol}. {text_pre_solution}')
} else if (ex_type == 'string') {
my_answers_text <- ''
numeric_sol <- ''
if (stringr::str_detect(solution,
'```text')) {
text_sol <- paste0('In order to execute the code, open a new R script in RStudio (Control+shift+N), ',
'copy and paste the code, and execute it whole by pressing ',
'Control+Shift+Enter or line by line with shortcut ',
'Control+Enter.')
} else {
text_sol <- ''
}
}
my_str <- paste0('\n\n <hr> \n',
webex::total_correct(), '\n',
'### Q.', my_counter, '{-} \n',
exercise_text, '\n',
my_answers_text)
temp_id <- basename(tempfile(pattern = 'collapse_'))
sol_str <- str_glue(
' <div style="text-align: left; margin-top: 2px; padding: 13px 0 10px 0;"><p><button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#{temp_id}" aria-expanded="false" aria-controls="collapseExample">
Solution
</button> </p> </div>
<div class="collapse" id="{temp_id}">
{text_sol}
<div class="card card-body">
{solution}
</div>
</div>')
cat(paste0(my_str, '\n' ,
sol_str))
}
return(invisible(TRUE))
}
Html Exercises
The html output for the selected three exercises is given next. Do notice that the correct solution is not highlighted in this blog post due to the lack of css and javascript. In the final result you’ll see that it works correctly. Also, you’ll need to set results='asis'
in the knitr options of the chunk (the code output pure html).
compile_eoc_exercises(my_exercises, type_doc = 'html')
Q.1
The R language was developed based on what other programming language?
Solution:
Q.2
What are the names of the two authors of R?
Solution:
Straight from the book: “… The base code of R was developed by two academics, Ross Ihaka and Robert Gentleman, resulting in the programming platform we have today.”.
Q.3
Why is R special when comparing to other programming languages, such as Python, C++, javascript and others?
Solution:
Pdf/Ebook Exercises
And for latex (pdf) and epub3 (ebook), the result is:
compile_eoc_exercises(my_exercises, type_doc = 'latex')
01 - The R language was developed based on what other programming language?
- C++
- S
- Javascript
- Julia
- Python
02 - What are the names of the two authors of R?
- Guido van Rossum and Bjarne Stroustrup
- John Chambers and Robert Engle
- Roger Federer and Rafael Nadal
- Ross Ihaka and Robert Gentleman
- Linus Torvalds and Richard Stallman
03 - Why is R special when comparing to other programming languages, such as Python, C++, javascript and others?
- Works on any plataform such as Windows, Unix, MacOS
- Easy to use
- Quick code execution
- Makes it easy to write mobile apps
- It was designed for analyzing data and producing statistical output
As you can see, it works great. So, at the end of each chapter I simply called function compile_eoc_exercises()
with the knit chunk options results='asis'
and echo=FALSE
. Moreover, object my_engine
is set as my_engine <- knitr:::pandoc_to()
, which will figure out the format within the compilation of the book:
Conclusion
Its is amazing how much we can accomplish by learning and mixing different technologies. In this case, I used R, Latex, html, javascript and css to bundle reproducible and dynamic exercises for my book. You can find examples of the final output in html, pdf and ebook.
If you’re trying it for you own book, make sure to add the correct .js and .css files to the html compilation. In my case, I used my_javascript.js and style_html.css.