gradethis helps instructors provide automated feedback for interactive exercises in learnr tutorials. If you are new to writing learnr tutorials, we recommend that you get comfortable with the learnr tutorial format before incorporating gradethis.
gradethis provides two grading modalities. You can:
Compare student code to model solution code with grade_this_code()
,
or
Write custom grading logic with grade_this()
.
To use gradethis in a learnr tutorial, load gradethis after learnr in
the setup
chunk of your tutorial:
To help authors provide consistent feedback, gradethis uses global
options to control the default behavior of many of its functions. You
can set or change these default values using
gradethis_setup()
.
Suppose we ask our students to calculate the average of the first ten
integers. In our tutorial’s R Markdown, we include a prompt, followed by
an exercise
chunk.
**Calculate the average of all of the integers from 1 to 10.**
```{r average, exercise = TRUE}
____(1:10)
```
Our hope is that students will replace the ____
with the
correct R function, in this case mean()
.
To grade an exercise, we need to associate checking code with the
exercise. In learnr, this code is written in a chunk named
<exercise label>-check
, where
<exercise label>
is the label of the chunk with
exercise = TRUE
.
To use gradethis to grade our average
exercise, we
create a new chunk named average-check
and we call either
grade_this()
or grade_this_code()
inside the
chunk.
In the average-check
chunk, we can either call
grade_this()
or grade_this_code()
. The first
gives authors control over the grading logic, which is typically written
inside curly braces as the first argument of
grade_this()
:
**Calculate the average of all of the integers from 1 to 10.**
```{r average, exercise = TRUE}
____(1:10)
```
```{r average-check}
grade_this({
# custom grading code
})
```
We’ll cover custom grading logic in more detail below.
On the other hand, grade_this_code()
requires authors to
include a model solution to which the student’s submission will be
compared. The solution is written in the
<exercise label>-solution
chunk, while
grade_this_code()
is called in the
<exercise label>-check
chunk:
**Calculate the average of all of the integers from 1 to 10.**
```{r average, exercise = TRUE}
____(1:10)
```
```{r average-solution}
mean(1:10)
```
```{r average-check}
grade_this_code()
```
When a student submits their exercise solution, it is automatically
compared to the model solution in the -solution
chunk, and
the first encountered difference is reported to the student. If there
are no differences, a positive statement is returned to the student.
Beautiful! Correct!
If the student submits an incorrect answer, such as
max(1:10)
, the feedback message attempts to point the
student in the right direction.
I expected you to call mean()
where you called max()
. Try it again. I have a good feeling about this.
If you want to provide custom feedback for specific errors that
students may be likely to make in your exercise,
grade_this()
provides a high level of flexibility.
When using grade_this()
, your goal is to write a series
of tests around the student’s submission to determine whether or not it
passes or fails. A passing or failing grade is signaled with
pass()
or fail()
. You may provide a custom
message or rely on the gradethis default passing or failing message.
In the case of our average
exercise, a simple version of
exercise grading might check that the student’s .result
is
equal to mean(1:10)
. If it is, the grading code returns a
passing grade, otherwise a failing grade is returned.
```{r average-check}
grade_this({
if (identical(.result, mean(1:10))) {
pass("Great work! You calculated the average of the first ten integers.")
}
fail()
})
```
If a hypothetical student submitted mean(1:10)
for this
exercise, our checking code will return:
Great work! You calculated the average of the first ten integers.
If the student submits an incorrect answer, the default
fail()
message is encouraging and helpful:
Incorrect. I expected you to call mean()
where you called min()
. Give it another try.
This example highlights three important aspects of custom grading logic that we will cover in more detail:
grade_this()
makes available a set of objects related to the exercise and
the student’s submission, such as .result
.
pass()
and fail()
signal a final grade
as soon as they are called. There are also additional grade-signaling
functions for common scenarios.
The default fail()
message includes code feedback,
if a solution is available. Code feedback, encouragement, and praise can
be enabled or disabled for
pass()
and fail()
grades directly or via
global options.
The checking code you write inside grade_this()
will be
executed in an environment where a number of submission- and
exercise-specific objects have been added.
Among these objects, grade_this()
includes all of the
objects provided by learnr
to an exercise checking
function. For convenience, gradethis also includes a few additional
objects. To avoid name collisions with user or instructor code, the
names of these objects all start with .
:
Object | Description |
---|---|
.label |
Label for exercise chunk |
.solution_code |
Code provided within the *-solution chunk
for the exercise |
.user_code |
R code submitted by the user |
.last_value |
The last value from evaluating the user’s exercise submission |
.result , .user |
A direct copy of .last_value for
friendlier naming |
.solution |
When accessed, will be the result of evaluating the
.solution_code in a child environment of
.envir_prep |
.check_code |
Code provided within the *-check (or
*-code-check ) chunk for the exercise |
.envir_prep |
A copy of the R environment before the execution of the chunk |
.envir_result |
The R environment after the execution of the chunk |
.evaluate_result |
The return value from the
evaluate::evaluate function |
You can reference any of these objects in your grading logic.
Additionally, you can use debug_this()
as your exercise
checker or in lieu of a grade to return an informative message in the
tutorial for your visual inspection of the value of each of these
objects. This is useful when debugging or writing exercises.
There are a number of grading helper functions in addition to
pass()
and fail()
:
pass_if_equal()
and fail_if_equal()
compare the submitted .result
(or another object) to an
expected value and pass or fail if the two values are equal.
pass_if()
and fail_if()
pass or fail if
their first argument, a condition, is TRUE
.
fail_if_code_feedback()
fails if there are
differences between the submitted code and the model solution code. This
is useful when you want to use custom logic to check specific failure
modes, but want to fall back to code comparison for modes you may not
have anticipated.
Each of these functions signals a final grade as soon as they are
called. This allows tutorial authors to construct grading logic in such
a way that later tests presume earlier tests in the grading code were
not triggered. If you are familiar with writing R functions, the
pass()
and fail()
helper functions are similar
to early return()
statements.
Be careful to end your grade_this()
grading code with a
final fallback grade in case no other grades are returned. Typically,
this fallback grade will be a call to pass()
or
fail()
without any arguments.
The pass()
and fail()
functions all use the
glue package for custom message
templating. With a glue::glue()
template string, you can
easily interleave R values with the feedback.
fail(
"Your code returned {round(.result, 2)}, but the average of `1:10` is
{if (.result > .solution) 'lower' else 'higher'} than that value."
)
Your code returned 5.5, but the average of 1:10
is
higher than that value.
Notice that you may include R code wrapped in { }
inside
the template string and you may reference any of the exercise objects in that string.
There are a number of message components that you may want to consistently include in your feedback:
pass()
includes an additional praise
argument to prepend random_praise()
to the
feedback
fail()
includes both hint
and
encouarge
. When TRUE
, hint
adds
code feedback to the failing message (if a solution is available) and
encourage
appends random_encouragement()
to
the end of the feedback message.
That's exactly the average of 1 through 10.
Magnificent! That's exactly the average of 1 through 10.
Each of the praise
, encourage
, and
hint
arguments can be enabled or disabled for all
pass()
or fail()
grades via the global options
set by gradethis_setup()
.
Magnificent! That's exactly the average of 1 through 10.
Your answer was not the number I expected. I expected you to call mean()
where you called min()
. Don't give up now, try it one more time.
Notice that by globally turning on praise
,
encourage
, and hint
, custom feedback messages
will contain the same components as the default pass and fail messages.
(You can control those defaults with the pass
and
fail
arguments of gradethis_setup()
.) Without
those options enabled globally, custom messages will only contain the
text included in the message.