This is an R markdown file explaining pupil pre-processing functions contained in the gazeR package. The example dataset is from a lexical decision task that had individuals judge the lexicality of printed and cursive stimuli. Cursive stimuli are ambiguous and non-segmented making them really hard to recognize. It was predicted that it should be harder to recognize cursive words than printed words. Good thing we have this new package to help us test this hypothesis!
Please see our pre-print for a more in-depth walkthrough of the functions below.
Before using this package, a number of steps are required. In order for gazeR to work you need several columns.
You should also include other columns of interest.
Colnames can be changes to match gazer conventions
We need to read in the file that contains data from 3 participants. Given how large pupil files can be, I did this to reduce computational processing.
pupil_path <- system.file("extdata", "pupil_sample_files_edf.xls.gz", package = "gazer")
pupil_sub1<-fread(pupil_path)In reality, you will have many Ss files. The function
merge_gazer_files will take all your pupil files from a
folder path and merge them together.
If you are also interested in analyzing behavioral data (RTs and
accuracy), the behave_pupil function will cull the
important behavioral data from the Sample Report.
behave_pupil will return a data frame without errors when
omiterrors=TRUE or a data frame with errors for accuracy/error analysis
when omiterrors=FALSE. The columns relevant for your experiment need to
be specified within the behave_col names argument. This does not
eliminate outliers. You must use your preferred method. I recommend the
very good trimr package by Jim Grange.
behave_data<-behave_pupil(pupil_sub1, omiterrors = FALSE, behave_colnames = c("subject","script","alteration", "trial", "target","accuracy","rt", "block", "cb"))If you are exporting files from SR, there is an option to extend
blinks within Data Viewer. It is generally recommended that you extend
blinks 100 ms before the blink and 100 ms after the blink. If you have
not done this before exporting into R, you can use the
extend_blinks function. The fillback argument extends
blinks back in time and the fillforward argument extends blinks forward
in time. For this experiment the tracker sampled at 250Hz (once every 4
ms) and we extend the blinks forward and backward 100 ms in time.
pup_extend<- pupil_sub1 %>%
group_by(subject, trial) %>%
mutate(extendpupil=extend_blinks(pupil, fillback=100, fillforward=100, hz=250)) %>%
relocate(extendpupil, .after=trial)
pup_extend
#> # A tibble: 585,898 × 15
#> # Groups: subject, trial [444]
#> subject trial extendpupil time pupil x y blink message acc block
#> <chr> <int> <int> <int> <int> <dbl> <dbl> <int> <chr> <int> <int>
#> 1 11c.edf 1 3635 0 3635 972. 563. 0 MODERECO… 1 0
#> 2 11c.edf 1 3634 4 3634 973. 565. 0 <NA> 1 0
#> 3 11c.edf 1 3626 8 3626 972. 567. 0 <NA> 1 0
#> 4 11c.edf 1 3622 12 3622 971 566. 0 <NA> 1 0
#> 5 11c.edf 1 3621 16 3621 972. 566. 0 <NA> 1 0
#> 6 11c.edf 1 3621 20 3621 972. 566. 0 <NA> 1 0
#> 7 11c.edf 1 3618 24 3618 972. 565. 0 <NA> 1 0
#> 8 11c.edf 1 3618 28 3618 971. 564. 0 <NA> 1 0
#> 9 11c.edf 1 3617 32 3617 971. 564. 0 <NA> 1 0
#> 10 11c.edf 1 3611 36 3611 971. 565. 0 <NA> 1 0
#> # ℹ 585,888 more rows
#> # ℹ 4 more variables: rt <int>, item <chr>, script <chr>, alt <chr>Pupil data can be extremely noisy! There are many ways to smooth
pupil data. In gazeR, you can use a n-point moving average or a hanning
filter. To smooth the data using a n-point moving average set the
filter argument to “moving.” To use this function, you need
to specify the column that contains the interpolated pupil values and
how many samples you want to average over. In this example, we use a
5-point moving average (n= 5).
Missing data stemming from blinks or failure of the eye tracker need
to be interpolated.smooth_interpolate_pupil searches the
data and reconstructs the pupil size for each trial from the relevant
samples using either linear interpolation [@Bradley2008; @Cohen2015;
@Siegle2003;@Steinhauer2000] or cubic-spline interpolation (@Mathot2018). If extendblinks argument = FALSE,
samples with blinks are turned into “NAs” and are then interpolated
linearly or by cubic interpolation using the type argument. This
function returns a tibble with a column called interp which contains
interpolated values. As an important note, if you had Data Viewer extend
blinks, the extendblink argument must be set to FALSE. If you used our
function, the extendblink argument should be set to TRUE. It is
important to note that SR only extends the blink column and does not set
blinks to NA in the sample report. For this example, we will set
extendblinks to false and use cubic interpolation.
To control for variability arising from non-task related processing,
baseline correction is commonly done. There are several different types
of baseline correction. In a recent paper by Mathot et al. (2018), it
was recommended that a subtractive baseline correction be done based on
the median. The baseline_correction_pupil_msg function
finds the median pupil size during a specified baseline period for each
trial. In this example, we use messages to calcuate our baseline. We
take 100ms before the target event.
baseline_pupil<-baseline_correction_pupil_msg(smooth_interp3, pupil_colname='pup_interp', baseline_dur=100, event="target", baseline_method = "sub")
#> Calculating median baseline from:target
#> Joining with `by = join_by(subject, trial)`
#> Joining with `by = join_by(subject, trial)`
#> Merging baseline
#> Performing subtractive baseline correction
head(baseline_pupil)
#> # A tibble: 6 × 19
#> subject trial baseline extendpupil time pupil x y blink message acc
#> <chr> <int> <dbl> <int> <int> <int> <dbl> <dbl> <int> <chr> <int>
#> 1 11c.edf 1 3584. 3635 0 3635 972. 563. 0 MODERE… 1
#> 2 11c.edf 1 3584. 3634 4 3634 973. 565. 0 <NA> 1
#> 3 11c.edf 1 3584. 3626 8 3626 972. 567. 0 <NA> 1
#> 4 11c.edf 1 3584. 3622 12 3622 971 566. 0 <NA> 1
#> 5 11c.edf 1 3584. 3621 16 3621 972. 566. 0 <NA> 1
#> 6 11c.edf 1 3584. 3621 20 3621 972. 566. 0 <NA> 1
#> # ℹ 8 more variables: block <int>, rt <int>, item <chr>, script <chr>,
#> # alt <chr>, interp <dbl>, pup_interp <dbl>, baselinecorrectedp <dbl>Subjects and trials with a lot of missing data–due to blinks–should
be removed. count_missing_pupil will remove subjects and
items that pass a specified missing threshold (we have it set at .2, but
users can change this to whatever value they would like). The percentage
of subjects and trials thrown out are returned for reporting.
Artifacts can arise from quick changes in pupil size (Kret & Sjak-Shie, in press). The max_dilation function calculates the normalized dilation speed, which is the max absolute change between samples divided by the temporal separation for each sample, preceding or succeeding the sample. To detect out liters, the median absolute deviation is calculated from the speed dilation variable, multiplied by a constant, and added to the median dilation speed variable–values above this threshold are then removed.
max_pup<-pup_missing %>%
dplyr::group_by(subject, trial) %>%
dplyr::mutate(speed=speed_pupil(pup_interp,time)) %>%
dplyr::mutate(MAD=calc_mad(speed))%>%
dplyr::filter(speed < MAD)In most psychological experiments, each trial includes several
events. We can use this information to start our trial onset at zero. To
do this we can use the onset_pupil function. This function
requires three arguments to be specified: time column, sample message
column, and the event of interest (“target”) in our example). This will
allow us to start the trial at the onset of the target.
baseline_pupil_onset<-baseline_pupil %>%
dplyr::group_by(subject, trial) %>%
dplyr::mutate(time_zero=onset_pupil(time, message, event=c("target"))) %>%
ungroup() %>%
relocate(time_zero,time, baseline, .after="trial")
baseline_pupil_onset
#> # A tibble: 585,898 × 20
#> subject trial time_zero time baseline extendpupil pupil x y blink
#> <chr> <int> <int> <int> <dbl> <int> <int> <dbl> <dbl> <int>
#> 1 11c.edf 1 -212 0 3584. 3635 3635 972. 563. 0
#> 2 11c.edf 1 -208 4 3584. 3634 3634 973. 565. 0
#> 3 11c.edf 1 -204 8 3584. 3626 3626 972. 567. 0
#> 4 11c.edf 1 -200 12 3584. 3622 3622 971 566. 0
#> 5 11c.edf 1 -196 16 3584. 3621 3621 972. 566. 0
#> 6 11c.edf 1 -192 20 3584. 3621 3621 972. 566. 0
#> 7 11c.edf 1 -188 24 3584. 3618 3618 972. 565. 0
#> 8 11c.edf 1 -184 28 3584. 3618 3618 971. 564. 0
#> 9 11c.edf 1 -180 32 3584. 3617 3617 971. 564. 0
#> 10 11c.edf 1 -176 36 3584. 3611 3611 971. 565. 0
#> # ℹ 585,888 more rows
#> # ℹ 10 more variables: message <chr>, acc <int>, block <int>, rt <int>,
#> # item <chr>, script <chr>, alt <chr>, interp <dbl>, pup_interp <dbl>,
#> # baselinecorrectedp <dbl>Place data into timebins (users can specifiy a timebin to use). For
this example, we will use 200 ms timebins. The argument
aggvars requires a vector of coloumn names to aggregate on.
If you are using pupil data, type should set to
“pupil.”
timebins1<- downsample_gaze(baseline_pupil_onset, bin.length=200, timevar = "time_zero", aggvars = c("subject", "script", "timebins"), type="pupil")
timebins1
#> # A tibble: 319 × 4
#> subject script timebins aggbaseline
#> <chr> <chr> <dbl> <dbl>
#> 1 11c.edf cursive -200 0.865
#> 2 11c.edf cursive 0 0.0982
#> 3 11c.edf cursive 200 1.36
#> 4 11c.edf cursive 400 8.13
#> 5 11c.edf cursive 600 4.09
#> 6 11c.edf cursive 800 1.13
#> 7 11c.edf cursive 1000 12.7
#> 8 11c.edf cursive 1200 34.7
#> 9 11c.edf cursive 1400 59.4
#> 10 11c.edf cursive 1600 80.4
#> # ℹ 309 more rows