Introduction
Moodle is a widespread open source learning platform. It is the official teaching support platform of the University of Granada (Spain), where we develop this package.
The goal of the moodef
package is to harness the power
of R to make defining Moodle elements easier. In particular,
this first version is focused on the definition of questions for
quizzes.
The process is as follows: we define the questions in R, from the definition, we generate an XML file that we can import directly into Moodle.
What does this package provide? On the one hand, we have simplified the process of manually defining the questions, considering only the essential parameters for each type. On the other hand, the package offers an infrastructure that allows the automatic or semi-automatic generation of questions from R: to define a question we simply need to include a row in a data frame, a CSV file or an Excel file.
The rest of this document is structured as follows: First, the question definition styles and the general process of defining questions is presented. Next, we show the types of questions considered and how to define them. Finally, the document ends with conclusions.
Question Definition Styles
One of the primary objectives in designing this package has been to simplify the question definition process.
For example, while it is possible to explicitly define the question type, it can also be inferred from the provided definition. In certain cases, you may need to specify additional details, such as the orientation of answer presentation, but generally, the type is deduced from the content of the definition.
At a global level, category level, several elements can be defined for all questions, including:
-
Category
-
Copyright
-
License
-
Author
-
Fraction
- Feedback (correct, incorrect, or partially correct responses)
The package supports two styles of question definition: the simple style and the extended style. In the extended style, some of the global elements can be redefined for individual questions.
Simple Style
In the simple style, the most frequently used default values are assumed for each question. This allows you to define only the specific components of the question:
-
Statement
- An optional image
- Answers
All questions belong to the same category and share the globally defined values for copyright, license, author, fraction, and feedback. Fraction does not make sense for all question types.
Extended Style
The extended style allows you to define or override additional elements for each question. These elements are optional, and if not explicitly defined, the global values are used. In addition to the components of the simple style, the following elements can be specified for each question:
-
Category
-
Fraction
-
Identifier
-
Name
-
Author
-
Feedback (general, correct, incorrect, or partially
correct responses)
- Feedback for each answer
- Tags
Question Definition Process
Questions are defined within the framework of a category (a Moodle concept). The category level includes the general configuration parameters that apply to all questions, as explained in the previous section.
Within the category definition, a set of questions is then defined. Questions can be created either by:
- Using a specific function for each question,
- defining a data frame, or
- importing a definition from a CSV or Excel file.
Once the questions are defined, the XML structure can be generated either as a string or as a file for direct import into Moodle.
In this section, we will explain the three steps of this process:
-
Define the category
-
Define questions
- Generate the XML file
Define the category
To define a category we use the function
question_category()
and we have to indicate its name: It
creates an object of class question_category
.
Below is a call to the function with the values of the parameters that we usually define.
library(moodef)
qc <- question_category(category = 'Initial test',
copyright = 'Copyright © 2025 Universidad de Granada',
license = 'License Creative Commons Attribution-ShareAlike 4.0',
author = 'Jose Samos')
In addition to the category name in the category
parameter, we define the copyright
, license
and author
parameters that will appear between comments in
the XML file associated with each question, as can be seen below.
<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<question type="category">
<category> <text>$course$/top/Initial test</text> </category>
<info format="html"> <text></text> </info>
<idnumber></idnumber>
</question>
<question type="...">
...
<questiontext format="html">
<text><![CDATA[
<!-- Copyright © 2025 Universidad de Granada -->
<!-- License Creative Commons Attribution-ShareAlike 4.0 -->
<!-- Author: Jose Samos -->
... ]]></text>
...
</questiontext>
...
</question>
</quiz>
Below is the function call being executed, including the values for the remaining parameters.
qc <- question_category(category = 'Initial test',
first_question_number = 1,
copyright = 'Copyright © 2025 Universidad de Granada',
license = 'License Creative Commons Attribution-ShareAlike 4.0',
author = 'Jose Samos',
fraction = 0,
correct_feedback = 'Correct.',
partially_correct_feedback = 'Partially correct.',
incorrect_feedback = 'Incorrect.',
adapt_images = TRUE,
width = 800,
height = 600)
We are going to discuss the use of each parameter.
Questions have a name, which is displayed as a summary in the
Moodle question bank. In the extended style, the name can be
explicitly defined. However, if it is not defined—or in the case of the
simple style—we generate the name by combining the question number
(obtained from first_question_number
parameter), the
deduced question type, and the beginning of its statement. For example,
the following is a generated question name.
<?xml version="1.0" encoding="UTF-8"?>
<quiz>
...
<question type="multichoice">
<name> <text>q_001_multichoice_what_are_the_basic_arithmetic_operations</text> </name>
...
</question>
</quiz>
Using the parameter first_question_number
, the number
that will be assigned to the first question is defined, with a
three-digit format. This number will increase for each question.
The fraction
attribute is used in various question types
to determine how a specific answer impacts the question’s score.
Specifically, for incorrect answers in the multichoice
and
truefalse
types, the value calculated by dividing
fraction
by the number of incorrect answers available is
considered as the amount deducted in case of an incorrect response. If
it has a value of 0, incorrect answers for those question types are not
penalized.
For each question we can indicate feedback text for cases in which
the answer is correct, partially correct or incorrect: The corresponding
values are provided by the correct_feedback
,
partially_correct_feedback
and
incorrect_feedback
parameters.
Finally, each question stem can include an image. Sometimes the
images we use have very different sizes and require a prior
transformation to homogenize their presentation. This transformation can
be carried out automatically by indicating it using the parameter
adapt_images
(default is FALSE
). Using the
parameters width
and height
we indicate the
size of the resulting image. It is advisable to test this functionality
and adjust the size accordingly by viewing the result when defining a
questionnaire in Moodle. The original images are not
modified, only those that are embedded in the XML file.
Define questions
Once we have a question_category
object created, we can
define questions that are added to it.
We can define the questions one by one using a definition function or in bulk using a data frame, a CSV file or an Excel file. Below we show the possibilities offered.
Definition function
We provide two functions for defining questions:
define_question()
and
define_extended_question()
. These are used to define
questions in the simple style and the extended style, respectively.
They can be mixed.
Simple Style
The minimum we have to define about a question is its statement.
Here’s how to define a question using the define_question()
function.
qc <- qc |>
define_question(
question = 'Describe the addition operation.'
)
Below is the definition of a simple style question with all possible parameters, which we explain below.
qc <- qc |>
define_question(
type = 'v',
question = 'Place the name of the operations as they appear in the figure.',
image = system.file("extdata", "ops.png", package = "moodef"),
image_alt = 'Operations',
answer = 'Addition',
a_1 = 'Multiplication',
a_2 = 'Division',
a_3 = 'Subtraction'
)
The type of question can be explicitly indicated using its associated
code. If it is not specified, it is deduced from the statement and the
answers. In this case, the type
parameter is used to
distinguish between two types of questions and to specify the
presentation of the answers. It can take four values: empty,
h
, v
, or x
. In the example above,
the value is v
. As we will see in the next section, this
indicates that it is not a multichoice
question and that
the answers are displayed vertically.
The question statement is defined using the question
parameter.
An image can be displayed after the statement. The file containing
the image is specified using the image
parameter. In the
example, we use the name of a file included in this package. Each image
must also include a description, which is defined with the
image_alt
parameter.
Finally, the answers to the question are defined. The first answer is
indicated using the answer
parameter. If additional answers
are needed, they can be included as variable parameters using any
parameter names (or no names), as many as required.
Extended Style
We can achieve the same definition as above using the extended style. In this case, the result will be exactly the same as before, but this function allows for the definition of additional parameters.
qc <- qc |>
define_extended_question(
type = 'v',
question = 'Place the name of the operations as they appear in the figure.',
image = system.file("extdata", "ops.png", package = "moodef"),
image_alt = 'Operations',
answer = 'Addition',
a_1 = 'Multiplication',
a_2 = 'Division',
a_3 = 'Subtraction'
)
The previous definition is equivalent to the following. In the code below, the default obtained values have been used. We could modify any of the indicated values or add new ones according to our needs. For example, additional answers, feedback for the answers, and tags to describe the questions can be defined.
qc <- qc |>
define_extended_question(
category = 'Initial test',
type = 'ordering<|>v',
fraction = 0,
id = '',
name = 'q_001_ordering_v_place_the_name_of_the_operations_as_they',
author = 'Jose Samos',
fb_general = '',
fb_correct = 'Correct.',
fb_partially = 'Partially correct.',
fb_incorrect = 'Incorrect.',
question = 'Place the name of the operations as they appear in the figure.',
image = system.file("extdata", "ops.png", package = "moodef"),
image_alt = 'Operations',
answer = 'Addition',
a_1 = 'Multiplication',
a_2 = 'Division',
a_3 = 'Subtraction',
fb_a_1 = '',
fb_a_2 = '',
fb_a_3 = '',
tag_1 = ''
)
Definition using a CSV file or an Excel file
We can create an empty CSV file with the necessary columns using the
create_question_csv()
function. In addition to the file
name, we can specify the separator to use (,
or
;
), and if we need to use the extended style, we indicate
it with the parameter extended = TRUE
(default is
FALSE
).
file <- create_question_csv(file = tempfile(fileext = '.csv'))
The content of the file for the simple style is shown below.
"type","question","image","image_alt","answer","a_1","a_2","a_3"
For the extended style, the content is also shown afterward.
"category", "type", "fraction", "id", "name", "author", "fb_general",
"fb_correct", "fb_partially", "fb_incorrect", "question", "image",
"image_alt", "answer", "a_1", "a_2", "a_3", "a_4", "fb_answer",
"fb_a_1", "fb_a_2", "fb_a_3", "fb_a_4", "tag_1", "tag_2", "tag_3"
We can add as many additional columns as we deem necessary. In the case of the extended style, the same number of answer columns as feedback columns must be included, and at least one tags column must be present (even if the values in these columns are not defined).
From that file, we can include rows using a text editor, R or some tool to edit CSV files in spreadsheet format.
Below is the content of a CSV file included in this package for the simple style in table format.
type | question | image | image_alt | answer | a_1 | a_2 |
---|---|---|---|---|---|---|
What are the basic arithmetic operations? | Addition, subtraction, multiplication and division. | Addition and subtraction. | Addition, subtraction, multiplication, division and square root. | |||
Match each operation with its symbol. | Addition<|>+ | Subtraction<|>- | Multiplication<|>* | |||
The square root is a basic arithmetic operation. | False | |||||
What basic operation does it have as a + symbol? | Addition | |||||
The symbol for addition is [[1]], the symbol for subtraction is [[2]]. | + | - | ||||
x | The symbol for addition is [[1]], the symbol for subtraction is [[2]]. | + | - | |||
h | Sort the result from smallest to largest. | 6/2 | 6-2 | 6+2 | ||
v | Sort the result from smallest to largest. | 6/2 | 6-2 | 6+2 | ||
What is the result of SQRT(4)? | 2 | -2 | ||||
What is the result of 4/3? | 1.33<|>0.03 | |||||
Describe the addition operation. |
Once available, we can generate all the questions using the
define_questions_from_csv()
function, as shown below.
file <- system.file("extdata", "questions.csv", package = "moodef")
qc <- qc |>
define_questions_from_csv(file)
There are types of questions in which two string values must be
indicated for each answer. The criterion we have adopted to include two
values in a cell is to use a separator and define them in a single
string, the separator is <|>
: It can be seen in the
definition of some of the previous questions, for example,
Addition<|>+
or 1.33<|>0.03
.
In this example, questions with images have not been included due to the difficulty of referencing the image files in the package from the CSV file, but they can be included without any problem.
For Excel files the definition is carried out in the same
way. The corresponding functions are:
create_question_excel()
and
define_questions_from_excel()
.
Definition using a data frame
We can also define questions in bulk using a data frame. An empty
data frame can be created with the
create_question_data_frame()
function (which can also be
used with the parameter extended = FALSE
, as shown in the
previous example for the CSV file) or read from a CSV file using the
read_question_csv()
function. In this example, we will read
a data frame that includes images in the question statements.
file <- system.file("extdata", "questions_image.csv", package = "moodef")
df <- read_question_csv(file = file)
The content of the data frame is shown below.
type | question | image | image_alt | answer | a_1 | a_2 | a_3 |
---|---|---|---|---|---|---|---|
What basic operation has the symbol shown in the figure as its symbol? | xxxxx/divide.png | Operation | Division | ||||
x | Place the name of the operations as they appear in the figure. | xxxxx/ops.png | Operations | Addition | Multiplication | Division | Subtraction |
In this case we are going to update the figure data with the files included in the package (in a local example, this is not necessary).
df[1, 'image'] <- system.file("extdata", "divide.png", package = "moodef")
df[2, 'image'] <- system.file("extdata", "ops.png", package = "moodef")
From R we can add new rows or modify existing ones. As we have
explained in the previous section, there are questions in which we need
to define two values in a cell using a separator:
<|>
. To facilitate the definition of these cells, we
have defined the function vector_to_string()
that, from a
vector, generates a string including that separator, as shown in the
following example.
s <- vector_to_string(c('Addition', '+'))
s
#> [1] "Addition<|>+"
Once the definition of the questions in the data frame is finished,
we can add it to the question_category
object using the
define_questions_from_data_frame()
function, as it’s shown
in the following.
qc <- qc |>
define_questions_from_data_frame(df)
Generate the XML file
Once the questions we need have been defined, the next step is to
generate the XML file. We use the generate_xml_file()
function, to which we have to indicate a file name.
file <- tempfile(fileext = '.xml')
qc <- qc |>
generate_xml_file(file)
The generated file is the one that we can import from Moodle in the Question Bank section.
If we have included images, the file size increases considerably because they are embedded in XML (not referenced).
file.size(file)
#> [1] 277408
The definition file for a single question is shown below.
<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<question type="category">
<category>
<text>$course$/top/Initial test</text>
</category>
<info format="html">
<text></text>
</info>
<idnumber></idnumber>
</question>
<question type="multichoice">
<name>
<text>q_001_multichoice_what_are_the_basic_arithmetic_operations</text>
</name>
<questiontext format="html">
<text><![CDATA[
<!-- Copyright © 2025 Universidad de Granada -->
<!-- License Creative Commons Attribution-ShareAlike 4.0 -->
<!-- Author: Jose Samos -->
<p>What are the basic arithmetic operations?</p>
]]></text>
</questiontext>
<generalfeedback format="html">
<text></text>
</generalfeedback>
<defaultgrade>1.0</defaultgrade>
<penalty>0.5</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<single>true</single>
<shuffleanswers>true</shuffleanswers>
<answernumbering>abc</answernumbering>
<showstandardinstruction>0</showstandardinstruction>
<correctfeedback format="moodle_auto_format">
<text>Correct.</text>
</correctfeedback>
<partiallycorrectfeedback format="moodle_auto_format">
<text>Partially correct.</text>
</partiallycorrectfeedback>
<incorrectfeedback format="moodle_auto_format">
<text>Incorrect.</text>
</incorrectfeedback>
<answer fraction="100" format="html">
<text>Addition, subtraction, multiplication and division.</text>
<feedback format="html">
<text>Correct.</text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text>Addition and subtraction.</text>
<feedback format="html">
<text>Incorrect.</text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text>Addition, subtraction, multiplication, division and square root.</text>
<feedback format="html">
<text>Incorrect.</text>
</feedback>
</answer>
</question>
</quiz>
Kind of questions
We have simplified and generalized the definition process for 10
types of questions, so that we do not even have to indicate the type to
define them, it is deduced from the definition: It depends on the value
of the answer
field. In some cases a character in the
type
field is used to distinguish between two types whose
definition is identical or to indicate the orientation of the answers
(horizontal or vertical), as we will see below.
For each kind of questions, the code for its type is specified,
followed by (separated by ‘:’) the code that can also be used to deduce
the same type. When no value is required, it is expressed as
''
(there is no need to include the quotation marks; simply
leave the cell blank or use the default parameter value). Either
of the two codes provided can be used interchangeably to identify the
question type.
Essay (essay
: ''
)
Only includes the question statement with or without an image (the
answer
field is empty). The answer to the question is made
through free text. Below is an example that we had shown before.
qc <- qc |>
define_question(
question = 'Describe the addition operation.'
)
True/False (truefalse
: ''
)
The answer
field contains the correct answer. If this is
one of the literals corresponding to the Boolean values
('True'
or 'False'
), nothing else needs to be
indicated. Here is an example.
qc <- qc |>
define_question(
question = 'The square root is a basic arithmetic operation.',
answer = 'False'
)
If we use the extended style for defining the question, we can specify feedback for each answer.
Numerical (numerical
: ''
)
If the value of field answer
is a number with or without
decimal places, or a vector of two numbers, it is a
numerical
question.
In this type of questions we only have to indicate correct values of the answer. If, instead of a value, we indicate a vector of two values, the second number represents the margin of error in which the answer is accepted as valid. Below are two examples.
qc <- qc |>
define_question(
question = 'What is the result of SQRT(4)?',
answer = '2',
a_1 = '-2'
) |>
define_question(
question = 'What is the result of 4/3?',
answer = c('1.33', '0.03')
)
In this case as well, with the extended definition style, feedback can be defined for each answer.
Short Answer (shortanswer
: ''
)
If only field answer
is defined and it is not a Boolean
or numeric value, it is of type shortanswer
, for
example:
qc <- qc |>
define_question(
question = 'What basic operation does it have as a + symbol?',
answer = 'Addition'
)
Feedback for the answer can also be defined using the extended style.
Multiple Choice (multichoice
: ''
)
If field answer
and the rest of the answers are of type
string, it is of type multichoice
. The content of field
answer
is the correct answer. Here is an example.
qc <- qc |>
define_question(
question = 'What are the basic arithmetic operations?',
answer = 'Addition, subtraction, multiplication and division.',
a_1 = 'Addition and subtraction.',
a_2 = 'Addition, subtraction, multiplication, division and square root.'
)
As mentioned earlier, feedback for the answers can be specified using the extended definition style.
Ordering (ordering<|>h
: h
or
ordering<|>v
: v
)
The ordering
type is defined as a
multichoice
type. To differentiate it, the
type
parameter is assigned a value other than its default
empty value. Assigning h
displays the answers horizontally,
while v
displays them vertically. To specify the detailed
name, you must also indicate the orientation using
ordering<|>h
for horizontal or
ordering<|>v
for vertical.
In the example below, the two questions are the same, only the way the answers are presented changes.
qc <- qc |>
define_question(
type = 'h',
question = 'Order the result from smallest to largest.',
answer = '6/2',
a_1 = '6-2',
a_2 = '6+2',
a_3 = '6*2'
) |>
define_question(
type = 'v',
question = 'Order the result from smallest to largest.',
answer = '6/2',
a_1 = '6-2',
a_2 = '6+2',
a_3 = '6*2'
)
Drag and Drop into Text (ddwtos
: ''
) and
Select Missing Words (gapselect
: x
)
In questions of these types the objective is to fill in the gaps with
the terms indicated. If the variable type
has the default
value (empty string), the values are filled by dragging and dropping. If
we define a value x
in the variable type
, they
are filled by selecting from a list.
Gaps in the statement are defined by numbers in double brackets, as shown in the following examples.
qc <- qc |>
define_question(
question = 'The symbol for addition is [[1]], the symbol for subtraction is [[2]].',
answer = '+',
a_1 = '-'
) |>
define_question(
type = 'x',
question = 'The symbol for addition is [[1]], the symbol for subtraction is [[2]].',
answer = '+',
a_1 = '-'
)
We must specify as many answers as there are gaps, in the correct order. Alternatively, a single gap can be defined with multiple selectable answers, where the correct answer will be the first one.
Matching (matching
: ''
)
If the answers are made up of vectors of pairs of strings, we are in
a type matching
question. Below we show an example.
qc <- qc |>
define_question(
question = 'Match each operation with its symbol.',
answer = c('Addition', '+'),
a_1 = c('Subtraction', '-'),
a_2 = c('Multiplication', '*')
)
Drag and Drop Markers (ddmarker
: ''
)
The ddmarker
question type allows users to drag markers
onto predefined areas of an image. Each marker corresponds to a specific
target, enabling visually interactive and location-based
assessments.
In this case, even if the category level specifies that images should be transformed, the image remains unchanged because the definition specifies the position of the image where the cursors must be placed for the answer to be correct.
qc <- qc |>
define_question(
question = 'Place the cursor over the indicated operation.',
image = system.file("extdata", "ops.png", package = "moodef"),
image_alt = 'Operations',
answer = 'rectangle<|>142,85;554,553<|>Sum'
)
Conclusions
The moodef
package makes it easy to define questions for
Moodle quizzes.
We can define the questions one by one by calling a function or in bulk using a CSV file, Excel file or a data frame. We have simplified and generalized the definition so that the types considered are defined with the same parameters.
Two definition styles are offered: simple and extended. The extended mode allows defining elements that are considered default in the simple mode.
The functionality offered by the package can be used to manually define the questions quickly or to define them automatically or semi-automatically from R.
The result of the definition process is an XML file that can be imported directly into the Moodle question bank.