- amjd rdwan: beautiful colors
- Sindy Süßengut: Photo
- Ivan Aleksic: From the exhibition “The Nineties: A Glossary of…
- Isabela Kronemberger: set of tools
- David Pisnoy: Photo
- Chris Lee: Behind the leaves.
- Max Saeling: Photo
Creating flexible e-learning quizzes with literate programming
15th December 2023 @ OZCOTS, Wollongong

Keep your hands up you have…
Making Moodle quizzes isn’t hard
However it is tedious and limits flexibility for creating good quizzes for teaching statistics

🎓 I use it at Monash University
I want to make my life easier when assessing students!
🎉 It’s free and open source
My software is also free and open source!
This improves accessibility.



Literate programming
An approach for creating documents or code using both writing and code in the same place
For R users, you might know this as Sweave/RMarkdown
For Python users, you probably think about notebooks
Statistics education ❤️ Literate programming
Literate programming for statistical quizzes is a great match! We work closely with data and code, and want to test students on these skills
💥 This isn’t a new idea…
The exams package for R has been on CRAN for over 15 years!

Lots of information at https://www.r-exams.org/

Why re-invent the R/exams wheel?
✅ It ticks a lot of boxes
❌ But it leaves some things to be desired
Using R/exams
boxhist.Rmd [106 lines]
```{r data generation, echo = FALSE, results = "hide"}
## DATA
n <- sample(30:50, 1)
m <- sample(1:4, 1)
s <- runif(1, 0.5, 2)
delta <- ifelse(runif(1) < 0.2, sample(8:12), 0)
p2 <- runif(1, 0.45, 0.55)
skewed <- left <- FALSE
if(!delta) {
skewed <- runif(1) < 0.6
left <- runif(1) < 0.3
}
dgpBoxhist <- function(n = 40, mean = 0, sd = 1, delta = 0,
p2 = 0.5, skewed = FALSE, left = FALSE)
{
SK <- function(x) abs(diff(diff(fivenum(x)[2:4]))/diff(fivenum(x)[c(2, 4)]))
sim <- function(x){
x <- rnorm(n)
if(skewed) exp(x) else x
}
x <- sim()
if(skewed) while(SK(x) < 0.7) x <- sim() else while(SK(x) > 0.15) x <- sim()
if(left) x <- -x
x <- mean + sd * scale(x)
k <- sample(1:n, round(p2 * n))
x[k] <- x[k] + delta
as.vector(sample(x))
}
x <- round(dgpBoxhist(n = n, mean = m, sd = s, delta = delta,
p2 = p2, skewed = skewed, left = left), digits = 2)
b <- boxplot(x, plot = FALSE)
spread <- tol <- signif(diff(range(c(b$stats, b$out)))/25, 1)
write.csv(data.frame(x), file = "boxhist.csv", quote = FALSE, row.names = FALSE)
## QUESTION/SOLUTION
questions <- solutions <- explanations <- rep(list(""), 6)
type <- rep(list("schoice"), 6)
questions[[1]] <- paste("The distribution is ", c("", "_not_ "), "unimodal.", sep = "")
solutions[[1]] <- c(delta < 1, delta > 1)
questions[[2]] <- paste("The distribution is", c("symmetric.", "right-skewed.", "left-skewed."))
solutions[[2]] <- c(!skewed, skewed & !left, skewed & left)
questions[[3]] <- paste("The boxplot shows ", c("", "_no_ "), "outliers.", sep = "")
solutions[[3]] <- c(length(b$out) > 0, length(b$out) < 1)
questions[[4]] <- "A quarter of the observations is smaller than which value?"
solutions[[4]] <- explanations[[4]] <- signif(b$stats[[2]], 3)
type[[4]] <- "num"
questions[[5]] <- "A quarter of the observations is greater than which value?"
solutions[[5]] <- explanations[[5]] <- signif(b$stats[[4]], 3)
type[[5]] <- "num"
questions[[6]] <- paste("Half of the observations are",
sample(c("smaller", "greater"), 1), "than which value?")
solutions[[6]] <- explanations[[6]] <- signif(b$stats[[3]], 3)
type[[6]] <- "num"
explanations[1:3] <- lapply(solutions[1:3], function(x) ifelse(x, "True", "False"))
solutions[1:3] <- lapply(solutions[1:3], mchoice2string)
if(any(explanations[4:6] < 0)) explanations[4:6] <- lapply(solutions[4:6], function(x) paste("$", x, "$", sep = ""))
```
Question
========
For the `r n` observations of the variable `x` in the data file
[boxhist.csv](boxhist.csv) draw a histogram, a boxplot and a stripchart.
Based on the graphics, answer the following questions or check the correct
statements, respectively. _(Comment: The tolerance for numeric answers is
$\pm`r tol`$, the true/false statements are either about correct or clearly wrong.)_
```{r questionlist, echo = FALSE, results = "asis"}
answerlist(unlist(questions), markup = "markdown")
```
Solution
========
\
```{r boxplot_hist, echo = FALSE, results = "hide", fig.height = 4.5, fig.width = 9, fig.path = "", fig.cap = ""}
par(mfrow = c(1, 2))
boxplot(x, axes = FALSE)
axis(2, at = signif(b$stats, 3), las = 1)
box()
hist(x, freq = FALSE, main = "")
rug(x)
```
```{r solutionlist, echo = FALSE, results = "asis"}
answerlist(unlist(explanations), markup = "markdown")
```
Meta-information
================
extype: cloze
exsolution: `r paste(solutions, collapse = "|")`
exclozetype: `r paste(type, collapse = "|")`
exname: Boxplot and histogram
extol: `r tol`Using R/exams


✅ Literate programming for quizzes with literate questions
cloze question types❌ Some downsides (and future work)
How it works
demo.Rmd [88 lines]
---
title: Learning with penguins
output:
moodlequiz::moodlequiz:
replicates: 1
moodlequiz:
category: penguins
---
# Basics
## Introduction
```{r setup, include = FALSE}
library(moodlequiz)
knitr::opts_chunk$set(echo = FALSE)
```
**Meet the penguins!**

> The `palmerpenguins` data contains size measurements for three penguin species observed on three islands in the Palmer Archipelago, Antarctica.
```{r data, echo = TRUE}
library(palmerpenguins)
penguins
```
## Plotting weight
**Which penguins are heaviest?**
A summary of the penguins dataset is shown below.
```{r}
summary(penguins)
```
The average weight of a penguin is `r cloze(mean(penguins$body_mass_g, na.rm = TRUE), tolerance = 1)`, which is slightly `r cloze("more", c("more", "less"))` than the median weight of `r cloze(median(penguins$body_mass_g, na.rm = TRUE), tolerance = 1)`.
Complete the code to produce the graphic shown in figure \@ref(fig:boxplot).
```r
library(ggplot2)
penguins |>
ggplot(aes(x = `r cloze("body_mass_g", colnames(penguins))`, y = `r cloze("species", colnames(penguins))`, fill = `r cloze("species", colnames(penguins))`)) +
`r cloze("geom_boxplot")`() +
scale_fill_manual(values = c("darkorange","darkorchid","cyan4")) +
theme_minimal()
```
```{r boxplot, fig.cap="Boxplots of penguin weight by species"}
library(ggplot2)
penguins |>
ggplot(aes(x = body_mass_g, y = species, fill = species)) +
geom_boxplot() +
scale_fill_manual(values = c("darkorange","darkorchid","cyan4")) +
theme_minimal()
```
Modify the code to investigate differences in `species` weight by `sex` that produces figure \@ref(fig:boxplot-sex).
```r
library(ggplot2)
penguins |>
ggplot(aes(x = `r cloze("body_mass_g", colnames(penguins))`, y = `r cloze("sex", colnames(penguins))`, fill = `r cloze("species", colnames(penguins))`)) +
`r cloze("geom_boxplot")`() +
scale_fill_manual(values = c("darkorange","darkorchid","cyan4")) +
theme_minimal()
```
```{r boxplot-sex, fig.cap="Boxplots of penguin weight by species and sex"}
penguins |>
ggplot(aes(x = body_mass_g, y = sex, fill = species)) +
geom_boxplot() +
scale_fill_manual(values = c("darkorange","darkorchid","cyan4")) +
theme_minimal()
```
Male penguins `r cloze("tend to", c("almost always", "tend to"))` weigh `r cloze("more", c("more", "less"))` than female penguins. Within each species, male penguins `r cloze("almost always", c("almost always", "tend to"))` weigh `r cloze("more", c("more", "less"))` than female penguins.
## Telling the story {type=essay}
**Tell the story**
Write a short paragraph about how a penguin's weight relates to their species and sex.✍️ Create a quarto template for Moodle XML
Allows use of quarto extensions and better support for other programming languages.
🧑🏫 Support quiz formats for other LMS
Conceptually literate programming of quizzes isn’t specific to Moodle.
🧑💻 Add capability of running code in the quiz
Using WebAssembly (WASM) we can run code in the web browser alongside a quiz.
Final remarks
install_github("numbats/moodlequiz")
Thanks to these Unsplash contributors for their photos