--- title: "mousetRajectory" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{mousetRajectory} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Motivation Within the community of cognitive scientists, the analysis of movement data is becoming more and more popular. While continuous measurements promise insights that reaction times cannot provide, researchers conducting their first tracking experiment may feel overwhelmed by the novel data handling requirements. Consequently, they often resort to out-of-the-box software restricting their analysis choices or poorly documented code snippets from colleagues. Thus, time is spend learning peculiarities of certain software (or colleagues) that could also be invested in learning overarching principles of data analysis or the development of own analysis routines. The aim of `mousetRajectory` is to provide scientists with an easy-to-understand and modular introduction to the analysis of mouse-tracking and other 2D movement data. While `mousetRajectory` should provide most functions needed to analyze your first experiment, we strongly encourage you to extend and/or replace certain modules with own code; a deeper understanding of the analysis process will naturally lead to better interpretations of the results. Therefore, we tried to make the source code as easy to understand as possible even when this leads to slower function execution. We further recommend to inspect and understand the functions you are executing (if you are using RStudio on a Windows Machine, hit `F2` to inspect source code of functions). ## Installation You can install mousetRajectory from CRAN with ``` r install.packages("mousetRajectory") ``` Alternatively, you can keep up to date and install the latest development version of mousetRajectory from [github.com/mc-schaaf/mousetRajectory](https://github.com/mc-schaaf/mousetRajectory) with: ``` r if(!require("devtools")){install.packages("devtools")} devtools::install_github("mc-schaaf/mousetRajectory") ``` ## Function Overview Currently, the following functions are featured: * Preprocessing: * `is_monotonic()` checks whether your timestamps make sense and warns you if they don't. * `is_monotonic_along_ideal()` checks whether your trajectories make sense and warns you if they don't. * `time_circle_left()` tells you the time at which the starting area was left. * `time_circle_entered()` tells you the time at which the end area was entered. * `point_crosses()` tells you how often a certain value on the x or y axis is crossed. * `direction_changes()` tells you how often the direction along the x or y axis changes. * `interp1()` directs you to the interpolation function from the awesome `signal` package. Thus, you do not have to call `library("signal")`. Such time-saving, much wow. Also, not having to attach the `signal` package avoids ambiguity between `signal::filter()` and `dplyr::filter()` in your search path. * `interp2()` is a convenience wrapper to `interp1()` that rescales the time for you. * Spatial measures: * `starting_angle()` computes (not only starting) angles. * `auc()` computes the (signed) Area Under the Curve (AUC). * `max_ad()` computes the (signed) Maximum Absolute Deviation (MAD). * `curvature()` computes the curvature. * `index_max_velocity()` computes the time to peak velocity, assuming equidistant times between data points. * `index_max_acceleration()` computes the time to peak acceleration, assuming equidistant times between data points. * Other measures * `sampen()` computes the sample entropy. ## A Simple Example ```{r setup, message=FALSE} library(mousetRajectory) library(ggplot2) library(dplyr) ``` Let us assume we are conducting a simple experiment. In our setup, participants must respond by moving a mouse cursor from a starting position (bottom circle, coordinates (0,0)) to one of two end positions (top circles, coordinates (-1,1) and (1,1)): ```{r draw_exp_layout, echo=FALSE} draw_background <- function() { pi_seq <- seq(0, 2 * pi, length.out = 360) circle <- data.frame(x = sin(pi_seq), y = cos(pi_seq)) rectangle <- data.frame( x <- c(-1, 1, 1, -1, -1), y <- c(-1, -1, 1, 1, -1) ) p1 <- ggplot() + theme_void() + coord_equal() p1 <- p1 + geom_path(aes(2 * x, 0.5 + y), rectangle) p1 <- p1 + geom_path(aes(0 + 0.2 * x, 0 + 0.2 * y), circle) # home p1 <- p1 + geom_path(aes(1 + 0.2 * x, 1 + 0.2 * y), circle) # right target p1 <- p1 + geom_path(aes(-1 + 0.2 * x, 1 + 0.2 * y), circle) # left target return(p1) } draw_background() + geom_path(aes(x = c(0, 1, 1, 1, 0.9), y = c(0, 1, 0.9, 1, 1))) ``` `dat` stores simple toy data that may reflect our pilot results. Let's inspect the structure of the data: ```{r define_trajectories, echo=FALSE} dat <- data.frame( Trial = rep(1:4, each = 3), Time2 = rep(c(0, 50, 100), 4), Target = c(rep("left", 6), rep("right", 6)), x_coord = c( 0, -0.5, -1, # 1 0, -0.7, -1, # 2 0, 0.55, 1, # 3 0, 0.45, 1 # 4 ), y_coord = c( 0, 0.6, 1, # 1 0, 0.6, 1, # 2 0, 0.7, 1, # 3 0, 0.8, 1 # 4 ) ) %>% group_by(Trial, Target) %>% reframe( Time = c(0:100), x_coord = interp1(Time2, x_coord, Time, method = "spline"), y_coord = interp1(Time2, y_coord, Time, method = "spline") ) %>% group_by(Trial, Target) dat <- dat %>% as_tibble() gg_background <- draw_background() ``` ```{r show data} head(dat) # gg_background has been created previously and is a ggplot object gg_background + geom_path(aes(x_coord, y_coord, group = Trial), dat) ``` In this example, `Time` reflects the time passed since the onset of the imperative stimulus and always increases by 1 arbitrary unit. In a real experiment, a sensible first pre-processing step would be to double-check the order of your data via `is_monotonic()` applied to `Time`. Further, in a real experiment you would likely group by not only by `Time`, but also other variables like a subject or block identifier. ```{r recode_x_coords} dat <- dat %>% group_by(Trial) %>% mutate( # will throw a warning if times are not monotonically increasing is_ok = is_monotonic(Time) ) ``` As a next step, we will recode the coordinates so we can treat movements to the left and to the right in the same way. This enables us to restrict the trajectories to their relevant parts, i.e., after the home was left and before the target has been reached. This also allows us to extract our first dependent measures, `InitiationTime` and `MovementTime`: ```{r filter_data} dat <- dat %>% group_by(Trial) %>% mutate( x_coord = ifelse(Target == "left", -x_coord, x_coord), InitiationTime = time_circle_left( x_coord, y_coord, Time, x_mid = 0, y_mid = 0, radius = 0.2 ), CompletionTime = time_circle_entered( x_coord, y_coord, Time, x_mid = 1, y_mid = 1, radius = 0.2 ), MovementTime = CompletionTime - InitiationTime ) %>% filter(Time >= InitiationTime & Time < CompletionTime) dat %>% group_by(Trial, InitiationTime, CompletionTime, MovementTime) %>% count() gg_background + geom_path(aes(x_coord, y_coord, group = Trial), dat) ``` Note that filtering the data to the relevant part leads to an unequal amount of data points for each trajectory (column `n`). This is bad news when you want to display average trajectories! One solution for this problem is "time-normalization," i.e., a separate linear interpolation of the x and y coordinates at certain time points. It has been proposed that this process should be done prior to the computation of MAD, AUC, etc. So let's do this via `interp2()`, a convenient wrapper to `signal::interp1()`: ```{r interpolate, message=FALSE} dat_int <- dat %>% group_by(Trial) %>% reframe( Time_new = 0:100, x_new = interp2(Time, x_coord, 101), y_new = interp2(Time, y_coord, 101), ) ``` Now we are ready to compute dependent measures like AUC and MAD: ```{r compute_DVs} dat_int %>% group_by(Trial) %>% summarise( MAD = max_ad(x_new, y_new), AUC = auc(x_new, y_new), CUR = curvature(x_new, y_new) ) ``` As a last step, you may want to plot your average trajectory: ```{r plot_avg} dat_avg <- dat_int %>% group_by(Time_new) %>% summarise( x_avg = mean(x_new), y_avg = mean(y_new) ) gg_background + geom_path(aes(x_avg, y_avg), dat_avg) + geom_path(aes(c(0, 1), c(0, 1)), linetype = "dashed") # ideal trajectory ``` Congratulations, you worked trough your first mouse-tracking analysis! As a final note: This package is currently under development. If you spot any bugs or have other improvement suggestions, please let us know by filing an issue at [github.com/mc-schaaf/mousetRajectory/issues](https://github.com/mc-schaaf/mousetRajectory/issues).