This will acquaint you with some of the basics of itrackR. There are a number of function for doing high-level analyses of eyetracking data.
Some sample data is included with itrackR. This includes 2 EDF files from an SR-Research Eyetracker, and a behavioral file (saved as a .rds). You can load them using itrackR.data
:
library(itrackR) datapath <- itrackr.data('path') edf_files <- itrackr.data('edfs') beh <- itrackr.data('beh')
datapath
will point to the data folder wherever itrackR is installed:
datapath
edfs
is a list of the 2 edfs found in that folder:
edf_files
beh
is a data frame of the behavioral data for the same 2 subjects.
knitr::kable(head(beh, 10))
We start by initializing the itrackR object and loading the data. This can be done in a couple different ways. If we have a list of all edf files (in the current working directory, or with full path names) we can do it like this.
z <- itrackr(edfs=edf_files) #Alternatively, we can provide the path and a search pattern to find all edfs in a certain folder: #z <- itrackr(path=datapath, pattern='*.edf')
The itrackR
object consists of fields for each relevant event. Each one is a data frame. The ID
fields specifies each subject. The subject ID is formed from extracting only the numeric data from the EDF file.
z$fixations
knitr::kable(head(z$fixations, 10))
z$saccades
knitr::kable(head(z$saccades, 10))
z$messages
shows the messages that were sent to Eyelink during the experiment
knitr::kable(head(z$messages, 10))
Much of the analysis depends on specifying regions of interest (ROIs). We can then determine if fixations lie within these ROIs. First we specify all possible ROIs that may occur in an experiment. The function radialCoords
makes it easy to specify a set of evenly-spaced coordinates arranged in a ring. We will create elliptical ROIs in this example. roiFlower
makes it easy to rotate the ellipses to make a flower-like pattern.
#generate coordinates for our ROIs innercoords <- radialCoords(x=512,y=384,numpoints=6,radius=240); outercoords <- radialCoords(512, 384,6, 280,starting_angle=30); #larger radius, starting w/ 30 degree offset #specify rotations of ellipses angles <- roiFlower(12)
We use makeROIs
to specify them. We can make elliptical or circular ROIs. First we make the inner ones. We can check our progress using plot_rois
. Note the plots have the origin at the upper-left. This means that ROI #1 is actually at the 12 o'clock position if we viewed the plot in the regular orientation.
#make elliptical ROIs and plot them z <- makeROIs(z,innercoords,shape='ellipse',xradius=60, yradius=120, angles=angles[c(1,3,5,7,9,11)]) plot_rois(z)
Now we add the outer ROIs. We make sure and specify the append
option, and also provide names. For now, names are limited to numbers. If you don't specify them, it will just use 1...n.
#make elliptical ROIs and plot them z <- makeROIs(z,outercoords,shape='ellipse',xradius=60, yradius=120, angles=angles[c(2,4,6,8,10,12)], names=7:12, append=T) plot_rois(z)
Finally let's include a central, circular ROI.
#coordinates have to be a matrix: centercoords <- matrix(c(512,384),nrow=1) z <- makeROIs(z,centercoords,shapes='circle',radius=65, names=13, append=T) plot_rois(z)
Once the ROIs are added, we can easily make scatterplots of fixations for each subject. This allows us to find calibration issues.
plot(z, zoom=TRUE)
You can also plot everyone's data on one plot using the oneplot
argument.
plot(z,zoom=TRUE,oneplot=TRUE)
Ideally, we send a message to Eyelink on every trial in order to identify it in the EDF file. Every time the eyetracker is started and stopped, we call this a separate trial. In this example, every trial the message "BLOCK X" and "TRIAL X" was sent to eyelink. We can see the trial-wise information in the header of our object:
z$header
knitr::kable(head(z$header, 10))
Next we specify index variables that uniquely identify trials. This should be present in both the edf and behavioral file. set_index
searches through the messages, finds the relevant ones, and extracts the numeric data. set_index
can take a regular expression to find anything that matches this pattern, and numeric.only
tells it to ignore any text (e.g., "BLOCK "). The variable names are stored in z$indexvars
and the information is added to z$header
.
find_messages
is for pulling other message information from the EDF file. Here we want to have the timestamps of the stimulus onset and the response so we can refer to them later.
add_behdata
merges the behavioral file with the eye data, based on the index variables. This only works if we run set_index
first (so we have Block
and Trial
variables in the behavioral data frame, and in our itrackR object).
#find messages to use as our index variables (to merge with our behavioral data) z<- set_index(z,varnames=c('Block','Trial'), patterns=c('^BLOCK [0-9]*','^TRIAL [0-9]*'), numeric.only=T) #find messages that specify the onset of events, extract the timestamps z <- find_messages(z,varnames=c('STIMONSET','RESPONSE'), patterns=c('STIMONSET','RESPONSE'), timestamp=T) #merge with behavioral data z <- add_behdata(z,beh,append=F)
Now z$beh
contains the behavioral data that matches with the eyetracking data, based on the index variables you specified (Block
and Trial
). It adds the variable eyetrial
, which is also found in the header
,fixations
, saccades
, blinks
, and messages
data frames in the itrackR object. It also adds the timestamps that you requested using find_messages
:
knitr::kable(head(z$beh, 10))
We can also see that the header has been updated
z$header
knitr::kable(head(z$header, 10))
Now we can plot a subset of our data based on the behaivoral data. Just use the condition
argument when plotting. Note it uses a similar syntax as subset
. Here we're plotting data only from the first 5 blocks:
plot(z,zoom=TRUE,oneplot=TRUE, condition = Block<=5)
It looks like subject 104 is off-center, due to poor calibration. We can correct for this using drift_correct
. We can optionally specify a grouping variable (from the behavioral data) so that correction is done separately for each level of that variable. In this case, let's perform correction for each subject and block. The threshold specifies the minimum amount of movement detected before we actually do any correction.
z <- drift_correct(z,vars='Block',threshold=15) plot(z,zoom=T)
Much better!
Next we code whether each fixation and saccade "hit" any of the ROIs using calcHits
z <- calcHits(z)
Note that the fixations
data frame now has binary vectors for each ROI specifying whether the fixation hit that item or not:
knitr::kable(head(z$fixations, 10))
This is not terribly useful if your task-relevant ROI changes positions on each trial. You can use mapROIs
map your experiment-wide ROIs (1,2,3...13) to trial-specific ROIs ('target','distractor'). You just need a variable in your behavioral data that specifies the number of the relevant ROI. Here, Targetpos
specifies the target location, and Distractorpos
specifies the distractor location:
z <- mapROIs(z,names=c('target','distractor'),indicators=c('Targetpos','Distractorpos'))
Now we can see a target_hit
and distractor_hit
variable in our fixation data frame
knitr::kable(head(z$fixations, 10))
Next we probably want to do statistics on our eyetracking data. We want to have our behavioral data merged with the eye data, including our ROI "hits". Just use eyemerge
to pull out the relevant information. Any eyetracking data that does not match behavioral data will still be included (all behavioral variables will just be NA
).
fixes <- eyemerge(z,'fixations') #including only some behavioral variables. ID and indexvars are always included saccs <- eyemerge(z,'saccades',behdata=c('Task')) #by default only mapped ROIs are included. Here we can include all 13 rois, plus the mapped ones fixes_all <- eyemerge(z,'fixations',all.rois = T)
We can also subset based on our behavioral variables using the condition
argument:
fixes_first5 <- eyemerge(z,'fixations',condition = Block <= 5) max(fixes_first5$Block,na.rm=T)
Sometimes you want to see the tendency of the eyes to look at a particular ROI over time, relative to some event. To look at this, we first determine epochs around our event of interest, in this case, STIMONSET
. We first run epoch_fixations
for each ROI that we're interested in.
#start at stimulus onset, going 700ms after that point. Bin the data into 25ms time bins. #repeate for the target and the distractor ROIs we created z <- epoch_fixations(z,c('target','distractor'),event='STIMONSET',start = 0, end = 700, binwidth = 25)
Next we want to visualize these timeseries using plot_fixation_epochs
. We can generate separate lines for each level of a factor (specified in the behavioral data) or for different ROIs. You can specify variables that define the different lines, as well as the rows and columns in separate panels. Behind the scenes, the function first aggregates based on ID
and the factors your specify, then across subjects.
#plot the timeseries data for fixations to target, separately for the Conflict and Task conditions plot_fixation_epochs(z,event='STIMONSET',rois=c('target'),group=c('Conflict'),cols='Task') #plot fixations to target and disctractor for the same conditions #you must specify 'roi' as one of the plotting variables for it to work. plot_fixation_epochs(z,event='STIMONSET',rois=c('target','distractor'),group=c('roi','Conflict'),color='Conflict',cols='Task') #plot difference waves (target - distractor fixations). Plot on separate rows insetead of columns. #This example doesn't make much sense because distractors aren't present on no-conflict trials plot_fixation_epochs(z,event='STIMONSET',rois=c('target','distractor'),group=c('roi','Conflict'),rows='Task',type='difference') #Repeat, but on a subset of the data. Say only when Conflict==1 plot_fixation_epochs(z,event='STIMONSET',rois=c('target','distractor'),group=c('roi','Conflict'),rows='Task',type='difference',condition = Conflict==1)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.