moodlequizR is a package that allows the user to easily create fully randomized tests for Moodle, or any other online assessment platform that uses the XML format to transfer questions. It includes a shiny web application that makes it possible for even those with limited knowledge of R to create simple quizzes and exams.
The idea is to create a file outside of Moodle that can then be imported. The steps are
Create an R script quizxyz.R either with the shiny app by running shinymoodlequizR, with Rstudio or with any ASCII word processor.
Create a file called quizxyz.xml. This is done automatically by the shiny app, or by running
XML is one of the standard file types recognized by Moodle and several other online test systems.
The best way to get started is by using the interactive shiny app. Start it with
It is largely self-explanatory (I hope!) and also has a few hints on what to do. Here are some general suggestions:
If a quiz was previously created with the app before and one wants to either run more copies or make changes to the quiz, entering the name and folder of the quiz is enough to read all the information into the app, so one doesn’t have to start from scratch each time.
in the name of the quiz and the folder empty spaces and special symbols such as -, @, back and forward slashes should be avoided, so for example don’t use ‘Year 1’ as a name of an object but use ‘Year1’.
Let’s say we want to create a quiz where the students are presented with a random data set and are asked to find the mean with R. The data is to be 50 observations drawn from a normal distribution with mean 20 and standard deviation 5, all numbers rounded to 1 digit. After opening the shiny app we can enter the following information:
Here is a screenshot:
Notice the drop-down box Included MoodlequizR Quizzes. There are 15 quizzes of increasing complexity included in the package.
Next we enter:
Here again is a screen shot:
This is all we need for now, so we can go to the bottom of the app and hit the Create the Files! button. If all goes well we then see the MyFirstQuiz.R script which will be saved in Folder. In Folder there will also be a file MyFirstQuiz.dta, which has all the info we just entered as an R list and which can be read back into the app at a later time if we want to make changes. Finally there will be a file called MyFirstQuiz.xml, which we can read into Moodle.
Now we go to Moodle and import the file MyFirstQuiz.xml.xml into the Question bank. Previewing one quiz we see
The students can high-light the data, hit Copy, switch to R and run
Finally the student enters the answer into Moodle and hits Submit to see:
The package includes 15 quizzes of increasing complexity. The best way to get started to make your own quiz is to pick one of these and make the needed changes!
In the app a Question / Part of Question is usually just that, a place where the students have to enter their answer to a question. However, a bit more general it is any place where the quiz is randomized, and sometimes that might appear as just text. For an example consider the built-in example 13, where the first “Question” just displays the output of an R command.
In the text that the user enters in the box the randomized part will appear at the @ symbol, or at the end if no @ is found.
The shiny app is a good way to get started and learn about moodlequizR. However, there are limits to the complexity of the randomization that can be done with the app. For example, the data that all students see in any one question will always come from the same distribution. Another limitation is discussed below in the multiple stories section. All these limitations can be overcome by writing your own R scripts. Again a good way to get started is using the shiny app to get a first version and then add to the R script directly.
The “heart” of moodlequizR are R scripts that can be turned into an XML file. These have to have a certain structure.
Here is a very simple example of a problemxxx.R:
example0=function() {
category="Examples / 1" # moodle category where quiz will be stored
quizname="problem -" # running counter for problems
n=50+1*sample(0:50, 1) # randomized sample size
m=round(runif(1, 90, 110), 1) # randomized mean
s=runif(1, 8, 12) # randomized standard deviation
d=sample(1:3) # randomized number of digits
x=rnorm(n, m, s) # generate data
x=round(x, d) # round it to d digits
res=as.list(1:1)
res[[1]]= round(mean(x), d+1) # calculate correct answer, to one digit more than the data.
qtxt = paste0( "<p>The mean of the data is ",
moodlequizR::nm(res[[1]], w = c(100,80), eps = c(0, 0.01))," </p>" )
# create question text with correct answer
atxt = paste0( "<p>The mean of the data is ", res[[1]]," </p>" )
# create answer text with correct answer
htxt = "Use the mean command"
# create hint
list(qtxt = paste0("<h5>", qtxt, "</h5>", moodle.table(x)),
htxt = paste0("<h5>", htxt, "</h5>"),
atxt = paste0("<h5>", atxt, "</h5>"),
category = category, quizname = quizname)
}
The routine generates a data set of size n (randomly chosen between 50 and 100 and rounded to d (1-3) digits) from a normal distribution with a random mean (90-110) and a random standard deviation (8-12). The student has to find the mean and round to d+1 digits, and gets partial credit (80%) if the rounding is not done.
The output of the routine has to be a list with five elements:
category: this is the category under which Moodle will file the the problem.
quizname: the name of the problem (usually just problem - )
qtxt: the ENTIRE text of the problem as a single string as it will appear to the students.
htxt: Hints that students see after their first attempt
atxt: the ENTIRE text of the answers that the students see after the deadline for the quiz has passed, again as a single string .
Note the <h5> at then end of the routine. This is html code for heading 5. I use this here to make the text a bit more readable in Moodle.But more generally we can use any html code and Moodle knows what to do with it. The same is true for latex, as we will see later.
Here is an example with two questions on the same page:
example1 <- function() {
category <- "Examples / Percentage 1"
quizname <- "problem - "
#----------------------
# Question 1
#----------------------
n <- sample(200:500, 1)
p <- runif(1, 0.5, 0.6)
x <- rbinom(1, n, p)
per <- round(x/n*100, 1)
qtxt <- paste0("Question 1: In a survey of ", n, "
people ", x, " said that they prefer Coke
over Pepsi. So the percentage of people who prefer
Coke over Pepsi is
{:NM:%100%", per,":0.1~%80%", per, ":0.5}%")
htxt <- "Don't forget to multiply by 100, don'tinlcude % sign in answer"
atxt <- paste0("Question 1: x/n*100 = ", x, "/", n,
"*100 = ", per, " (rounded to 1 digit)")
#----------------------
# Question 2
#----------------------
p1 <- round(runif(1, 50, 60), 1)
if(per<p1) mc <- c("{:MC:~%100%lower~%0%the same~%0%higher}", " < ")
if(p1==per) mc <- c("{:MC:~%0%lower~%100%the same~%0%higher}", " = ")
if(per>p1) mc <- c("{:MC:~%0%lower~%0%the same~%100%higher}", " > ")
qtxt <- paste0(qtxt, "<p>Question 2: In a survey some
years ago the percentage was ", p1, "%.
So the percentage now is ", mc[1])
htxt <- ""
atxt <- paste0(atxt, "<p>Question 2: ", per , mc[2], p1)
list(qtxt = paste0("<h5>",qtxt,"</h5>"),
htxt = paste0("<h5>", htxt,"</h5>"),
atxt = paste0("<h5>", atxt,"</h5>"),
category = category, quizname = quizname)
}
so one simply creates a long string with all the questions and another with all the answers.
Moodle has a number of formats for writing questions. moodlequizR uses the CLOZE format because it has a number of features that work well in the kinds of quizzes that are most common. There are three basic question types:
Example: For this data set the mean is {1:MC:~%0%lower~%0%the same~%100%higher} than the median.
In this case the students are presented with a drop-down box with the three options: lower, the same and higher.
higher is the correct answer, so it gets 100%, the others get 0%. One can also give (say) 65% for partial credit. The 1 in the front means the problem is worth 1 point.
In the package moodlequizR there are some routines that make this easier. The routine mc creates the code needed for multiple choice questions. It has a number of standard choices already implemented.
here options is a character vector with the choices (or a number for some common ones that I predefined), w is the vector of percentages, usually 100 for the correct answer and 0 for the others.
So the following creates the text for the multiple choice question above:
moodlequizR::mc(1, c(0,0, 100))
#> $qmc
#> [1] "{1:MC:%0%lower~%0%not equal to~%100%higher~%0%can't tell}"
#>
#> $amc
#> [1] "higher"
Notice that this routine creates both the question and the answer text.
The built-in options are:
Example: The mean is {2:NM:%100%54.7:0.1~%80%54.7:0.5}.
Now the students see a box and have to type in a number.
Here 54.7 is the correct answer, which get’s 100%. Any answer within \(\pm 0.1\) is also correct, allowing for rounding to 1 digit. The answer 55 gets 80% (a bit to much rounding) .
moodlequizR::nm creates numerical questions:
x is a vector of possible answers, w is the vector of percentages and eps is a vector of acceptable range \(\pm\). Alternatively one can use the argument ndigits. With (say) ndigits=1 the answer has to be rounded to one digit behind the decimal, all other rounding get partial credit.
So for the above example we need to run
moodlequizR::nm(c(54.7, 54.7), c(100, 80), c(0.1, 0.5), pts=2)
#> [1] "{2:NM:%100%54.7:0.1~%80%54.7:0.5}"
If just one number is correct, say 50, and the answer has to be given exactly, use nm(50).
Example: The correct method for analysis is the {1:SA:correlation coefficient}
Here the student sees a box and has to type in some text.
There is also {1:SAC:correlation coefficient} if the case has to match.
This type of problem has the issue of empty spaces. So if the student typed correlation coefficient with two spaces it would be judged wrong. The solution is to use *s: {1:SA:*correlation*coefficient*}
Use moodlequizR::sa for text answers
the function automatically adds *s in a number of places. txt can be a vector.
With these routines we can update our quiz:
example2 <- function() {
category <- "moodlequizR / Percentage 2"
quizname <- "problem - "
#----------------------
# Question 1
#----------------------
n <- sample(200:500, 1)
p <- runif(1, 0.5, 0.6)
x <- rbinom(1, n, p)
per <- round(x/n*100, 1)
qtxt <- paste0("Question 1: In a survey of ", n, "
people ", x, " said that they prefer Coke over
Pepsi. So the percentage of people who prefer
Coke over Pepsi is ",
nm(c(per, per), c(100, 80), c(0.1, 0.5)), "%")
htxt <- "Don't forget to multiply by 100, don'tinlcude % sign in answer"
atxt <- paste0("Question 1: x/n*100 = ", x, "/", n, "*100 = ", per, " (rounded to 1 digit)")
#----------------------
# Question 2
#----------------------
p1 <- round(runif(1, 0.5, 0.6)*100, 1)
if(per<p1) {w <- c(100, 0, 0); amc <-"<"}
if(per==p1) {w <- c(0, 100, 0); amc <-"="}
if(per>p1) {w <- c(0, 0, 100); amc <-">"}
opts <- c("lower", "the same", "higher")
qtxt <- paste0(qtxt, "<p>Question 2: In a survey some
years ago the percentage was ", p1, "%.
So the percentage now is ", mc(opts, w))
htxt <- ""
atxt <- paste0(atxt, "<p>Question 2: ", per , amc, p1)
list(qtxt = paste0("<h5>",qtxt,"</h5>"),
htxt = paste0("<h5>", htxt,"</h5>"),
atxt = paste0("<h5>", atxt,"</h5>"),
category = category, quizname = quizname)
}
moodlequizR has a number of routines that help with making questions:
moodle.table(x, DoRowNames = FALSE, DoBorder = FALSE, ncols = 10) creates the html code to properly display the data in Moodle. x is either a vector of a data.frame, DoRowNames and DoBorder are obvious and ncol is used if x is a vector to decide how many numbers appear in one row.
paste.data is used to read data copied from Moodle into R. Just run
qamatrix(tbl, points = 100, precision = 0, Border = 1, before, after) displays a matrix of questions and answers, for example if the students have to find a sample correlation matrix. Border=0 means no border. before and after can be used to rows before and after tbl, for example to add names and row totals.
RtoHTML(method, x, y, n, varnames, …) creates the html code to display the output of a number of R routines in Moodle the same as they appear in R. x, y, n are the variables as they are required by the respective methods, varnames is used to get the correct variable names.
The methods currently included are:
All the usual arguments, for example mu=10 for a hypothesis test in t.test, can be passed to the methods. Say, for example, we want to ask the students a number of questions about the output of the regression command. In R we generate some random data and run
x=1:20
y=2*x+round(rnorm(20, 0, 3),2)
summary(lm(y~x))
#>
#> Call:
#> lm(formula = y ~ x)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -5.0002 -1.8665 -0.0302 0.9981 4.5181
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) -0.97226 1.17867 -0.825 0.42
#> x 1.94583 0.09839 19.776 1.17e-13 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 2.537 on 18 degrees of freedom
#> Multiple R-squared: 0.956, Adjusted R-squared: 0.9536
#> F-statistic: 391.1 on 1 and 18 DF, p-value: 1.17e-13
Now RtoHTML(lm, x, y) will generate code that in Moodle appears the same!
The way to use this in the shiny app is as a question with the Verbatim option. See the included example 13.
Once the quizxyz.R is written we can read it into R and then use the make.xml routine to create the Moodle input file quizxyz.xml:
This creates 20 quizzes and saves a file called in the folder given by getwd(). The routine can also pass arguments to the .R file.
Next we need to open Moodle and import this file.
Note Moodle has two Import places. One is for importing existing questions from other courses. We need to go to Questions - Import, select XML and drop .
In statistics we like to use word problems. So how can we do that? Essentially we can make up a couple of stories:
example3 <- function(whichstory) {
category <- paste0("moodlequizR / Percentage - Story - ", whichstory)
quizname <- "problem -"
if(whichstory==1) {
n <- sample(200:500, 1)
p <- runif(1, 0.5, 0.6)
x <- rbinom(1, n, p)
per <- round(x/n*100, 1)
qtxt <- paste0("In a survey of ", n, " people ", x, "
said that they prefer Coke over Pepsi. So the
percentage of people who prefer Coke over
Pepsi is ",
nm(c(per, per), c(100, 80), c(0.1, 0.5)), "%")
}
if(whichstory==2) {
n <- sample(1000:1200, 1)
p <- runif(1, 0.45, 0.55)
x <- rbinom(1, n, p)
per <- round(x/n*100, 1)
qtxt <- paste0("In a survey of ", n, " likely
voters ", x, " said that they would vote for
candidate A. So the percentage of people who
will vote for candidate A is ",
nm(c(per, per), c(100, 80), c(0.1, 0.5)), "%")
}
if(whichstory==3) {
n <- sample(100:200, 1)
p <- runif(1, 0.75, 0.95)
x <- rbinom(1, n, p)
per <- round(x/n*100, 1)
qtxt <- paste0("In a survey of ", n, " people ",
x, " said that they are planning a vacation
this summer. So the percentage of people who
are planning a vacation is ", nm(c(per, per),
c(100, 80), c(0.1, 0.5)), "%")
}
htxt <- ""
atxt <- paste0("x/n*100 = ", x, "/", n, "*100 = ",
per, " (rounded to 1 digit)")
list(qtxt = paste0("<h5>",qtxt,"</h5>"),
htxt = paste0("<h5>", htxt,"</h5>"),
atxt = paste0("<h5>", atxt,"</h5>"),
category = category, quizname = quizname)
}
Note we can match each story with likely numbers, so in the Coke vs Pepsi story n is between 200 and 500 whereas in the votes story it is between 1000 and 1200.
Note that this routine has the argument whichstory, which we can add to genquiz or make.xml:
Often in Statistics we want the students to work with data sets. So these data sets need to be displayed properly in the quiz and it must be easy for the students to “transfer” them to R.
To display the data in the quiz use the moodle.table function. If it is called with a vector it arranges it as a table with (ncol=) 10 columns. If it is called with a matrix or data frame it creates a table as is.
To get the data from the quiz into R moodlequizR has the routine paste.data(). All the students have to do is highlight the data (including column names if present) in the quiz with the mouse, right click copy, switch to R and run
The routine correctly reads vectors, both numeric and character. Also tables with a mix of character and numeric columns. If on a arare occasion it reads a table as a vector we can use the argument is.table=TRUE. It works on both Windows and Apple operating systems.
So we could have another version of the above example, this time presenting the students with the the raw data:
example4 <-
function() {
category <- "moodlequizR / Percentage from Raw Data"
quizname <- "problem - "
n <- sample(200:500, 1)
p <- runif(1, 0.5, 0.6)
x <- sample(c("Coke", "Pepsi"), size=n,
replace=TRUE, prob=c(p,1-p))
per <- round(table(x)[1]/n*100, 1)
qtxt <- paste0("In a survey people were asked whether
they prefer Coke over Pepsi. Their answers
are below. So the percentage of people who
prefer Coke over Pepsi is ",
nm(c(per, per), c(100, 80), c(0.1, 0.5)), "%<hr>",
moodle.table(x))
htxt <- ""
atxt <- paste0("x/n*100 = ", table(x)[1], "/", n,
"*100 = ", per, " (rounded to 1 digit)")
list(qtxt = paste0("<h5>",qtxt,"</h5>"),
htxt = paste0("<h5>", htxt,"</h5>"),
atxt = paste0("<h5>", atxt,"</h5>"),
category = category, quizname = quizname)
}
Often in statistics we use graphics. We can do this as follows: first you need to install the R package base64, available on CRAN.
Say we want the quiz to show a histogram and the student has to decide whether or not it is bell-shaped:
example5 <- function(bell=TRUE) {
require(base64)
category <- paste0("moodlequizR /
bell-shaped? - ", ifelse(bell, "Yes", "No"))
quizname <- "problem - "
n <- 1000
if(bell) x <- rnorm(n, 10, 3)
else x<- rchisq(n, 2) + 8
plt <- hplot(x, n=50, returnGraph=TRUE)
plt64 <- moodlequizR::png64(plt)
if(bell) mmc <- mc(5, c(100, 0))
else mmc <- mc(5, c(0, 100))
qtxt <- paste0("The data shown in this histogram ",
mmc, " bell-shaped<hr>", plt64)
htxt <- ""
if(bell) atxt <- "It is bell-shaped"
else atxt <- "It is not bell-shaped"
list(qtxt = paste0("<h5>", qtxt, "</h5>"),
htxt = paste0("<h5>", htxt, "</h5>"),
atxt = paste0("<h5>", atxt, "</h5>"),
category = category, quizname = quizname)
}
The same basic ideas can be used to make random quizzes for other courses. In principle any other computer language can be used as well, but I will stick with R and make a quiz for calculus:
example6 <- function() {
category <- "moodlequizR / Integral"
quizname <- "problem - "
A <- round(runif(1), 1)
B <- round(runif(1, 1, 2), 1)
fun <- function(x) {x*exp(x)}
I <- round(integrate(fun, A, B)$value, 2)
qtxt <- paste0("\\(\\int_{", A, "}^{", B, "} xe^x dx = \\)",
nm(I, eps=0.1))
htxt <- ""
atxt <- paste0("\\(\\int_{", A, "}^{", B, "} xe^x dx = \\)", I)
list(qtxt = paste0("<h5>", qtxt, "</h5>"),
htxt = paste0("<h5>", htxt, "</h5>"),
atxt = paste0("<h5>", atxt, "</h5>"),
category = category, quizname = quizname)
}
Note here we see that one can use latex notation in Moodle quizzes, and so display formulas!
If there are any problems with the package feel free to email me at wolfgang.rolke@upr.edu. Also, I am always interested to hear your opinion, good or bad!