Site icon R-bloggers

Shiny apps with math exercises

[This article was first published on R-bloggers on Mikkel Meyer Andersen, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

It is often very useful to practise mathematics by automatically generated exercises. One approach is multiple choice quizzes (MCQ), but it turns out to be fairly difficult to generate authentic wrong answers. Instead, we want the user to input the answer and be able to parse the answer and check whether this is the correct answer. There are many fun challenges in this, e.g. to verify that 2 is equal to 1 + 1 (as text strings the two are different, but mathematically they are equal, at least to a convenient approximation in this case).

In this post I will demonstrate how to use R package Ryacas (computer algebra system, CAS) and the R package iomath under development (as well as shiny) to make a small, powerful Shiny app. The resulting app is available at https://github.com/r-cas/shinymathexample.

First I will show the app, and then I will show a few central lines of code.

The shinymathexample app

First, the shinymathexample app presents the question:

The answer can the be written and checked:

It even works for mathematical/numerical equality, not just text/string equality:

Finally wrong answers are caught, too:

Exercise generation

The exercise generation code (boiled down) is something like this:

choices_x_coef <- c("a", "2*a", "3*a")
choices_x_pow <- 1:3

generate_f <- function() {
  x_coef <- sample(choices_x_coef, 1)
  x_pow <- sample(choices_x_pow, 1)
  x_part <- paste0(x_coef, "*x^", x_pow)
  eq <- ysym(x_part)
  eq
}

problem_f_eq <- generate_f()

true_ans <- list(
  x = deriv(problem_f_eq, "x")
)

output$problem <- renderUI({
  problem <- paste0("Let $$f(x) = ", tex(problem_f_eq), ".$$",
                    "Calculate the derivative with respect ", 
                    "to \\(x\\) and enter the result below.")
  
  res <- withMathJax(
    helpText(problem)
  )
  
  return(res)
})

Validation

The validation code (boiled down) is something like this:

reply <- input$answer_x

parsed_input <- iomath::prepare_input(reply)

if (inherits(parsed_input, "error")) {
  stop("Could not prepare the input (remember that I'm simple-minded!).")
}

reply_sym <- tryCatch(Ryacas::ysym(parsed_input), 
                      error = function(e) e)

if (inherits(reply_sym, "error")) {
  stop("Could not understand the input (remember that I'm simple-minded!).")
}

compare_grid <- expand.grid(
    x = seq(-10, 10, len = 6),
    a = seq(-10, 10, len = 6)
)
is_correct <- tryCatch(iomath::compare_reply_answer(reply = reply, 
                                                    answer = true_ans$x, 
                                                    compare_grid = compare_grid), 
                       error = function(e) e)

if (inherits(is_correct, "error")) {
  stop(paste0("Error: ", is_correct$message))
}

is_correct # TRUE/FALSE

Remarks

Take a look at the complete code at https://github.com/r-cas/shinymathexample.

The provided example should illustrate that it is fairly easy to make something relatively sophisticated.

Beside the central aspect of Ryacas (e.g. for derivatives etc.), iomath has the important function compare_reply_answer that compares reply to answer over the grid of values defined by compare_grid. Thus, equality of expressions are measured as point-wise equality over a finite number of points (e.g. 100) for different values of variables including an allowed tolerance.

To leave a comment for the author, please follow the link and comment on their blog: R-bloggers on Mikkel Meyer Andersen.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.