Scanpath
An R package for analyzing scanpath patterns in eye movements
Install / Use
/learn @tmalsburg/ScanpathREADME
#+TITLE: Scanpath – An R Package for Analyzing Scanpaths #+AUTHOR: Titus von der Malsburg #+EMAIL: malsburg@uni-potsdam.de #+LATEX_CLASS_OPTIONS: [12pt] #+LANGUAGE: en-us #+latex_header: \usepackage[USenglish]{babel} #+latex_header: \usepackage[a4paper, margin=1in]{geometry} #+latex_header: \linespread{1.05} #+latex_header: \parindent0pt #+latex_header: \setlength{\parskip}{0.8\baselineskip} #+latex_header: \usepackage{ragged2e} #+latex_header: \RaggedRight #+latex_header: \pagestyle{empty} #+latex_header: \usepackage{microtype} #+latex_header: \usepackage{soul} #+LaTeX_HEADER: \usepackage[scaled]{helvet} #+LaTeX_HEADER: \renewcommand\familydefault{\sfdefault} #+LaTeX_HEADER: \usepackage{fancyhdr} #+LaTeX_HEADER: \pagestyle{fancy} #+LaTeX_HEADER: \urlstyle{tt} #+LaTeX_HEADER: \usepackage{paralist} #+LaTeX_HEADER: \let\itemize\compactitem #+LaTeX_HEADER: \let\description\compactdesc #+LaTeX_HEADER: \let\enumerate\compactenum #+PROPERTY: header-args:R :session R :tangle yes :comments both :eval yes
[[http://dx.doi.org/10.5281/zenodo.31800][https://zenodo.org/badge/doi/10.5281/zenodo.31800.svg]]
An R package for analyzing scanpaths in eye movement data. The package includes a simple toy dataset and example code. Consult [[https://www.sciencedirect.com/science/article/pii/S0749596X11000179][von der Malsburg & Vasishth (2011)]] for the details of this analysis method. The manual of the package can be found [[https://github.com/tmalsburg/scanpath/blob/master/Documentation/scanpath-manual.pdf?raw=true][here]] and a PDF-version of this page [[https://github.com/tmalsburg/scanpath/blob/master/README.pdf?raw=true][here]].
- News
- [2021-07-09] :: Fixed a bug triggered when factors were used as trial identifiers.
- [2018-02-22] :: Added an interactive [[measure][demo app]] that shows how the scanpath measure works.
- [2018-02-21] :: Added new functions ~rscasim~ and ~plot_alignment~. See section ’How the sausage is made’ below for details. Also note that the order of parameters for ~plot_scanpaths~ has changed for consistency with ggplot2.
- [2018-02-20] :: The new default is no normalization of scasim scores. Improved documentation and examples for ~find.fixation~ and ~match.scanpath~. Minor improvement in functions for plotting scanpaths.
- [2018-01-30] :: Version 1.06 doesn’t logarithmize fixation durations when calculating scanpath similarities. (In previous versions, when ~normalize="durations"~ was used, the normalization was done using non-log-transformed durations, which could in some rare cases break the triangle inequality.)
- Install To install the latest version of the package, execute the following commands:
#+BEGIN_SRC R :eval no library("devtools"); install_github("tmalsburg/scanpath/scanpath", dependencies=TRUE) #+END_SRC
- Usage example The code shown below can also be found in the file [[file:README.R]]. Open that file in RStudio and play with it as you read through this mini-tutorial.
Let's have a look at the toy data that is included in the package:
#+BEGIN_SRC R :results table :exports both :colnames yes library(tidyverse) library(magrittr) library(scanpath) data(eyemovements) head(eyemovements) #+END_SRC
#+RESULTS: | subject | trial | word | x | y | duration | |---------+-------+------+-----+-----+----------| | Anne | 1 | 1 | 46 | 384 | 319 | | Anne | 1 | 3 | 131 | 388 | 147 | | Anne | 1 | 2 | 106 | 386 | 88 | | Anne | 1 | 3 | 165 | 387 | 156 | | Anne | 1 | 4 | 186 | 386 | 244 | | Anne | 1 | 5 | 264 | 388 | 193 |
** Plotting scanpaths To get a sense of what is going on in this data set, we create a series of plots. For this purpose, we use the function ~plot_scanpaths~ from the /scanpath/ package. In the first plot below, each panel shows the data from one trial. There are three participants which are coded by color. The data is from a sentence reading task. The x-axis shows words and the y-axis time within trial in milliseconds.
#+BEGIN_SRC R :results graphics :exports both :file Plots/scanpaths.png :width 600 :height 600 :res 100 plot_scanpaths(eyemovements, duration ~ word | trial, subject) #+END_SRC
#+RESULTS: [[file:Plots/scanpaths.png]]
We can see that the participants differ in their reading speed. Also we see that each participant read the sentence more or less straight from left to right (trials: 1, 4, 7), or with a short regressions from the end of the sentence to its beginning (trials: 2, 5, 8), or with a long regression from the end of the sentence (trials: 3, 6, 9).
In the next plot, we use the fixations’ x- and y-coordinates. Each circle is a fixation and the size of the circle represents the duration of the corresponding fixation.
#+BEGIN_SRC R :results graphics :exports both :file Plots/scanpaths2.png :width 600 :height 600 :res 100 plot_scanpaths(eyemovements, duration ~ x + y | trial, subject) #+END_SRC
#+RESULTS: [[file:Plots/scanpaths2.png]]
The function ~plot_scanpaths~ returns a /ggplot/ object. This means that we add more elements to the plot before rendering. For example, we can labels the fixations with their index and change the limits of the axes:
#+BEGIN_SRC R :results graphics :exports both :file Plots/scanpaths3.png :width 600 :height 600 :res 100 plot_scanpaths(eyemovements, duration ~ x + y | trial, subject) + geom_text(aes(label=i), vjust=2.5, show.legend=FALSE, size=3) + xlim(0, 600) + ylim(284, 484) #+END_SRC
#+RESULTS: [[file:Plots/scanpaths3.png]]
** Extracting subsets of fixations or sub-scanpaths In many analyses, it is not desirable to analyze the complete scanpaths as recorded during the experiment but to analyze some subset of the fixations. For instance, in a reading experiment, we might want to investigate how readers responded to a certain word and not care about what happened earlier. The scanpath package offers two functions that can be used to easily pinpoint and extract the fixations of interest: ~find.fixation~ and ~match.scanpath~.
The function ~find.fixation~ identifies fixations that match a set of criteria which can be specified using [[https://en.wikipedia.org/wiki/Regular_expression][regular expressions]]. For instance, the following code finds fixations on word 6:
#+BEGIN_SRC R :exports both :results value table :colnames yes idx <- find.fixation(eyemovements$word, eyemovements$trial, "6") eyemovements[idx,] #+END_SRC
#+RESULTS: | subject | trial | word | x | y | duration | |---------+-------+------+-----+-----+----------| | Anne | 1 | 6 | 330 | 381 | 290 | | Anne | 2 | 6 | 330 | 381 | 290 | | Anne | 3 | 6 | 330 | 381 | 290 | | Anne | 3 | 6 | 320 | 381 | 189 | | Udi | 4 | 6 | 330 | 381 | 319 | | Udi | 5 | 6 | 330 | 381 | 319 | | Udi | 6 | 6 | 330 | 381 | 319 | | Udi | 6 | 6 | 320 | 381 | 208 | | Gustave | 7 | 6 | 330 | 381 | 348 | | Gustave | 8 | 6 | 330 | 381 | 348 | | Gustave | 9 | 6 | 330 | 381 | 348 | | Gustave | 9 | 6 | 320 | 381 | 227 |
Finding these fixations could also have been achieved with a subset operation. However, if have more complex criteria for the fixations we’re interested in, things can get rather tricky. For instance, a subset is not enough when we’re only interested in the second fixation on word 6 in each trial. The following code extracts only those:
#+BEGIN_SRC R :exports both :results value table :colnames yes idx <- find.fixation(eyemovements$word, eyemovements$trial, "6", nth=2) eyemovements[idx,] #+END_SRC
#+RESULTS: | subject | trial | word | x | y | duration | |---------+-------+------+-----+-----+----------| | Anne | 3 | 6 | 320 | 381 | 189 | | Udi | 6 | 6 | 320 | 381 | 208 | | Gustave | 9 | 6 | 320 | 381 | 227 |
Regular expressions also allow us to specify the context in which the fixations of interest appear. For instance the code below finds fixations on word 3 but only those that are followed by fixations on word 4:
#+BEGIN_SRC R :exports both :results value table :colnames yes idx <- find.fixation(eyemovements$word, eyemovements$trial, "34") eyemovements[idx,] #+END_SRC
#+RESULTS: | subject | trial | word | x | y | duration | |---------+-------+------+-----+-----+----------| | Anne | 1 | 3 | 165 | 387 | 156 | | Anne | 2 | 3 | 165 | 387 | 156 | | Anne | 3 | 3 | 165 | 387 | 156 | | Udi | 4 | 3 | 165 | 387 | 172 | | Udi | 5 | 3 | 165 | 387 | 172 | | Udi | 6 | 3 | 165 | 387 | 172 | | Gustave | 7 | 3 | 165 | 387 | 187 | | Gustave | 8 | 3 | 165 | 387 | 187 | | Gustave | 9 | 3 | 165 | 387 | 187 |
Here, we find fixations on word 3 that are preceded by fixations on word 1:
#+BEGIN_SRC R :exports both :results value table :colnames yes idx <- find.fixation(eyemovements$word, eyemovements$trial, "1(3)", subpattern=1) eyemovements[idx,] #+END_SRC
#+RESULTS: | subject | trial | word | x | y | duration | |---------+-------+------+-----+-----+----------| | Anne | 1 | 3 | 131 | 388 | 147 | | Anne | 2 | 3 | 131 | 388 | 147 | | Anne | 3 | 3 | 131 | 388 | 147 | | Udi | 4 | 3 | 131 | 388 | 162 | | Udi | 5 | 3 | 131 | 388 | 162 | | Udi | 6 | 3 | 131 | 388 | 162 | | Gustave | 7 | 3 | 131 | 388 | 176 | | Gustave | 8 | 3 | 131 | 388 | 176 | | Gustave | 9 | 3 | 131 | 388 | 176 |
The following code finds fixations on the last word but only of those that are not directly preceded by fixations on words 4 to 7:
#+BEGIN_SRC R :exports both :results value table :colnames yes idx <- find.fixation(eyemovements$word, eyemovements$trial, "^4-7", subpattern=1) eyemovements[idx,] #+END_SRC
#+RES
