In my experience, most people in psychology start using R because they need it to solve problems with data analysis. Either they need to organise a data set, visualise it cleanly, or apply a statistical tool to the data that isn’t available via any other mechanism. Those are the areas in which R has traditionally held an advantage over other programming languages or other statistical software, and in my opinion that’s still the area where it’s strongest.

That being said, R is a proper programming language and you can use it for all sorts of other things. For example, one question I’m often asked is whether R can be used to run behavioural experiments. Of course the answer to that question is “yes, absolutely”, but I think it’s worth thinking about when this is a good idea. There are a few examples I can think of where I’d strongly argue that R isn’t the right tool for the job:

  • If all you need is a simple survey without a complicated design, tools like SurveyMonkey and Qualtrics are much easier to use and they’re not generally all that expensive. I’ve never really found a use case for R in this context
  • If you need high precision timing and very low level control over stimulus presentation (i.e., down to the level of specifying exactly what happens at each screen refresh), I’d be wary about using R. Everything I’ve read about how event handling in R works suggests that it’s just not as good as Python for that task.
  • More generally if you already know how to implement the experiment using a tool like jsPsych - which is free, open source and has a community of users in psychology alread - why switch? If your system isn’t broken don’t try to fix it.

So what does that leave? When should you consider using R to implement a behavioural experiment? I can think of a few possibilities.

  • If you have a text based task that doesn’t require very high precision timing, but has a more complicated interactive structure than is possible with a questionnaire, R can deliver a perfectly workable task without a lot of effort
  • If your task is graphics-based and you’re okay with running it on your own machine (i.e. not online) you can repurpose the R graphics system for this.
  • If your experiment needs to take the form of a complicated web application and you aren’t already a skilled web programmer, you can build “Shiny” apps through R that work really well

In the long run I suspect most people will want to find other solutions to the problem besides R, but at the same time I think it’s really important to recognise that you can’t learn all the things at once. Just mastering one programming language is hard enough 😀 so it’s worth the effort to try to get as much mileage out of that as possible!

With that in mind, I’ll talk about all three of those use cases in these notes. The “Shiny” approach is complex enough that I’ve broken it off into its own section, but I’ll talk about the other two approaches in this section.

23.1 Text based tasks

To start with, lets consider a very simple task. Imagine that we wanted to investigate how people play "hangman. On the off chance you’re unfamiliar with the game, it’s a straightforward two player game. One player thinks of a word and the other person has to work it out by making a series of guesses as to what letters it contains. If the guesser works it out before making a predetermined number of errors then they win. To investigate this, we will need to implement this game in R, with the computer choosing the word and the human guessing. It’s a text based task, so we should be able to do the whole thing in the console. Some problems we’ll need to solve:

  • How do we present the current state of the game?
  • How do we get input from the participant?
  • How do we “refresh” the state of game after every event?
  • How do we record the data?

Let’s start by writing a function called show_hangman() that will take three inputs, the guesses the person has made made so far, the word that the person needs to guess, and the total number of errors they’re allowed to make before losing.

show_hangman <- function(guesses, word, errors) {
  
  library(stringr)
  reg <- function(x){ paste0("[",x,"]") }
  
  # what letters are they missing
  missing <- letters %>% str_flatten() %>% str_remove_all(reg(guesses))

  # what has the participant revealed
  stimulus <- word %>% str_replace_all(reg(missing),".")
  
  # how many errors?
  n_guess <- guesses %>% str_length()
  n_hit <- guesses %>% str_count(reg(word))
  n_err <- n_guess - n_hit
  
  # print the results:
  cat("\n\n")
  cat("what you know:\n\n")
  cat(stimulus,"\n\n")
  cat("your guesses:  ", guesses, "\n")
  cat("errors so far: ", n_err, "\n")
  cat("chances left:  ", errors-n_err, "\n\n")
  
}

Here’s what that looks like:

show_hangman(guesses = "aest", word = "computational", errors = 5)
## 
## 
## what you know:
## 
## .....tat...a. 
## 
## your guesses:   aest 
## errors so far:  2 
## chances left:   3

That’s a good beginning! Next, we’ll need a function that can ask the participant to make a guess, and store the response. The readline() function is good for this:

prompt_user <- function(){
  guess <- readline("what letter would you like to guess next? ")
  return(guess)
}

Ideally, during our experiment what we would do is clear the screen every time a person makes a guess and then update the display. To do this, we’ll need a function to clear any text from the console. This is handled differently depending on what system you’re using. I’ll assume here that you’re working within RStudio, in which case the trick here is to send the “form feed” character to the console1

clear_screen <- function(){
  cat("\f") # form feed character
}

This seems to be most of what we would need. However, we will probably also need a function that checks to see whether the game is over. Most of what we needed to do this is contained in the show_hangman() function above, but here it is is stripped down:

game_state <- function(guesses, word, errors) {
  
  library(stringr)
  reg <- function(x){ paste0("[",x,"]") }
  
  n_missing <- word %>% str_remove_all(reg(guesses)) %>% str_length()
  if(n_missing == 0) {
    return("win")
  }
  
  # how many errors?
  n_guess <- guesses %>% str_length()
  n_hit <- guesses %>% str_count(reg(word))
  n_err <- n_guess - n_hit
  if(n_err >= errors) {
    return("lose")
  }
  
  return("continuing")
}

Now we can organise this into a function that plays a game of hangman!

hangman <- function(word, errors) {
  
  guesses <- ""
  state <- "continuing"
  while(state == "continuing") {
    
    clear_screen()
    show_hangman(guesses, word, errors)
    g <- prompt_user()
    
    guesses <- guesses %>% str_c(g)
    state <- game_state(guesses, word, errors)
  }
  
  dat <- c(word = word, guesses = guesses, errors = errors, outcome = state)
  
  clear_screen()
  show_hangman(guesses, word, errors)
  
  return(dat)
}

From there, all you need to do is put them all into a single script and you’re ready to go. The hangman1.R script provides an example that plays one game of hangman, and hangman2.R plays a series of three games and then stores the results in a data frame d. The video below illustrates this in action:

23.2 Graphical tasks

The rabbit example gives you a sense of how far you can go using nothing other than text, but in many situations you’ll probably need something a little more sophisticated. The natural intuition that you might have is that we can use the R graphics system as the foundation for a psychological experiment: on every trial you’d use use a graphics tool (e.g. ggplot) to draw the stimulus on screen, and then the participant could respond using the keyboard or the mouse.

That intuition is correct, but we’ll need to modify our approach a little bit. Earlier on when we discussed data visualisation I didn’t really talk much about the capabilities of different graphics devices because it didn’t matter much. In this context it starts to matter whether we draw the plot in the RStudio plot pane or somewhere else, because there are different graphics devices associated with each one, and only some of them are responsive to user input. Clearly, in order to design a behavioural experiment we will need to use a device that supports interactivity, and that means the RStudio graphics device is no good to us: it’s designed for the user to draw plots, not for interaction. A simple way to get around this if you’re on a Unix based system (e.g. Mac, Linux) is to use the X11() graphics device,2 as this does support user interaction. To give you a sense for what you can realistically achieve, the orientation.R script contains an example of an experiment that displays triangles that might be pointing upwards or downwards, and the task is to guess whether there are more upwards pointing arrows (press the “6” key) or downwards ones (press “b”):

23.3 Shiny apps?

The third and most sophisticated approach is to build a web application using Shiny. This has a number of advantages. Firstly, you can run a Shiny app locally on your machine or deploy it to the internet - so you can collect online data easily. Secondly, it has a lot more flexibility in terms of what you can do with it. However, the advantages do come at a cost. There’s a certain amount of effort involved in learning how to build a Shiny app, and some additional work to adapt the framework to behavioural experiments. It’s a big enough topic that I’m not going to try to force it into a subsection here. Instead, there’s a whole section devoted to shiny!


  1. Form feed dates back to the early days of computing and doesn’t serve much of a purpose anymore, but it’s still recognised. As a consequence many software packages have co-opted it and used it for other purposes. Since the original definition was something like “clear the current page from the printer”, it makes some sense to use it to mean “clear the current console log from the screen”↩︎

  2. I’m not entirely sure what the fix would be if you’re on Windows. Also note that newer Macs don’t ship with X11 by default, so you have to install XQuartz. Sigh.↩︎

  3. I should mention that although you the viewer can see the numbers scrolling past in these videos - so that you can see what decisions I was making - they were all far enough outside my focus of attention that I couldn’t see them while doing the task. I’m actually shocked I performed as well I did. It totally felt like I was guessing pretty randomly!↩︎