Assignment RMarkdown files differ from standard content files in that they can contain special code chunks named solution
. When the website is build source Assignment markdowns are used to make two different output documents: assignment tasks Rmd files, which contain templates for students to use to complete the assignment, and assignment solution HTML which contains only the solution chunk outputs without the solution code, for students to use as an answer key.
Here's an example task chunk: print a summary
of the cars
data frame.
summary(cars)
Note that you don't need to handle giving your solution
chunks unique names; that will be done automatically when the assignment markdown is built. This saves some headache when writing assignments at the expense of being able to run ad hoc knits.
Other chunks are left unchanged and included as-is in both task and solution outputs:
print("hello normal chunk!")
The courseR assignment framework only works if you're also building a course package. It is important that student accounts have read access to wherever this package is deployed.
Here we'll use a couple of modified versions of the excercises from Hadley Wickham's R for Data Science textbook to demonstrate the features of solution chunks.
For some tasks, like making plots, it doesn't make sense to try to include any kind of automated checking. For example:
3.2.4 #5. Using the mpg
data set, make a plot of hwy
versus cyl
:
library(ggplot2) ggplot(data = mpg) + geom_point(aes(x = cyl, y = hwy))
When students run the courseR::checkAssigment
app they'll be able to check their plot against the one created by the solution
block (but they won't see the code for the solution block). When instructors run the courseR::gradeAssignments
app they'll see the reference solution followed by both the code chunk and output from each assignment submitted by students. This makes it quick and easy to grade submissions. Available grades are defined in courseR.yml
.
There are other cases where it is possible to provide sane automated tests to provide students with instant feedback before submitting an assignment for grading. Currently, courseR
supports writing task code tests using the checkr package. Currently being developed to work with the learnr, there are some key differences between how learnr
and courseR
test code chunks and interopertate with checkr
. courseR
is more opinionated than learnr
but handles the boilerplate for you:
learnr
, each chunk within a document is evaluated in it's own environment.checkr
; unique names will automatically be generated. solution
chunks can be followed by zero, one or many checkr
chunks. checkr
chunk are joined by '%>%'. You don't explicitly write the pipe sequence for tests in a chunk, this is done automatically based on their order. You also don't explicitly pipe in captured user code, this is also handled by the framework. There's also no reason to need a branch, as you can have as many checkr
chunks for each task as you like; they are implicitly joined by AND
.checkr
chunks are initially evaluated when the site is being built (with courseR::build
). All of the test functions in checkr
return functions with the signature function(capture)
; any function created in a checkr
chunk that has this signature will be saved at build time and included in the test pipe. This of course allows these closures to bind any data in the R markdown document present in the parent environment when the assignment is built. This includes values in solution chunks that students won't see.checkAssignment
. Note this avoids security concerns as student code is only ever evaluated within students' own R sessions.On with some examples!
3.2.4 #2a. How many rows are in the mpg
data set?
library(datasets) nrow(mpg)
Obviously, if you have an answer key for this question it's easy to cheese a solution. So we can use checkr
to make sure they got the right number (duh) but also used the nrow
function.
library(checkr) rows <- nrow(mpg) find_call("nrow(whatever)", "You're not using the `nrow` function. Look it up!") check_value(match_number(rows), "That's an incorrect number of rows.")
Note the closure returned by check_value
captures rows
from the parental environment.
3.2.4 #2b. How many columns are in the mpg
data set?
ncol(mpg)
We'll use this contrived example to demonstrate using multiple checkr
chunks:
final_ check_value(match_number(11), "That's an incorrect number of columns.")
courseR::check_final
is just a shorthand for final_ %>% check_value
:
library(courseR) check_final(match_number(ncol(mpg)), "That's an incorrect number of columns.")
5.2.4 #3. How many flights in nycflights::flights
have a missing dep_time
?
library(nycflights13) sum(is.na(flights$dep_time))
You aren't limited to using the test functions generated by checkr
, you can use any function that adheres to the function(capture)
signature:
function(capture) { # We can be evil... capture$passed <- FALSE capture$message <- "You will never complete this task." capture }
5.2.4 #1. Create a new table from flights
containing only flights that had an arrival delay of two hours or more.
This is a better real world example:
library(dplyr) ref <- flights %>% filter(arr_delay >= 120) ref
check_final(match_data_frame(ref, names_match = TRUE), "Your table doesn't have the right columns.") check_final(match_data_frame(ref, nrow = TRUE), "Your table doesn't have the right number of rows.")
These are such common things to check in the context of data transformation or manipulation excercises, courseR exports these shortcuts:
check_cols(ref) check_rows(ref)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.