Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load.
The 9 matches
- [1] § 2. Method › 2.4 Data preparation and statistical modelling › 2.4.1 Pupillometry pre-processing. ↔ Code/1_pupils_cleaning.Rmd, lines 184 to 260 · score 0.67 · 0.05–4 Hz, linearly interpolated, Hanning, butterworth, tagged, tonic · Report a mismatch
- [2] § 2. Method › 2.3 Design and procedure ↔ Code/pupils_entropy_subjective.Rmd, lines 311 to 348 · score 0.66 · NASA TLX, subjective workload, critical event, vehicle · Report a mismatch
- [3] § 2. Method › 2.4 Data preparation and statistical modelling › 2.4.1 Pupillometry pre-processing. ↔ Code/2_pupils_modelling.Rmd, lines 646 to 690 · score 0.64 · pseudo change, cubic, subtracted, evoked, fitted, dilation · Report a mismatch
- [4] § 2. Method › 2.1 Participants ↔ Code/2_pupils_modelling.Rmd, lines 145 to 174 · score 0.64 · gender split, annually, miles, UK, license, age · Report a mismatch
- [5] § 3. Results › 3.6 Post-hoc analysis: driver gaze during transitions of control ↔ Code/2_pupils_modelling.Rmd, lines 1324 to 1364 · score 0.60 · pitch angle, post hoc, gaze distribution, variables, modelled, windows · Report a mismatch
- [6] § 2. Method › 2.2 Apparatus and materials ↔ Code/pupils_entropy_subjective.Rmd, lines 311 to 348 · score 0.58 · NASA TLX, subjective workload, gaze, pupil · Report a mismatch
- [7] § 3. Results › 3.6 Post-hoc analysis: driver gaze during transitions of control ↔ Code/2_pupils_modelling.Rmd, lines 1413 to 1504 · score 0.53 · dashboard area, post transition, pre RTI, gaze · Report a mismatch
- [8] § 3. Results › 3.1 Effect of N-back and lead vehicle presence on pupil diameter during hands-off L2 driving ↔ Code/2_pupils_modelling.Rmd, lines 446 to 527 · score 0.51 · credible intervals, pupil diameter increased, lead vehicle, CI, predicted · Report a mismatch
- [9] § 3. Results › 3.6 Post-hoc analysis: driver gaze during transitions of control ↔ Code/2_pupils_modelling.Rmd, lines 1324 to 1364 · score 0.50 · pitch angle, pre RTI, Modelling, Post, window, hoc · Report a mismatch
Paper
Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
R Markdown · 1,504 lines · 65 KB · no license · 6 matches
- ---
- title: "Pupillometry modelling"
- author: "Courtney Goodridge"
- date: "25/11/2024"
- output: html_document
- ---
- ```{r setup, include=FALSE}
- knitr::opts_chunk$set(echo = TRUE)
- ```
- ## Preamble
- ## Packages
- ```{r}
- if(!require(here)) install.packages("here")
- library(here)
- if(!require(ggplot2)) install.packages("ggplot2")
- library(ggplot2)
- if(!require(dplyr)) install.packages("dplyr")
- library(dplyr)
- if(!require(tidyr)) install.packages("tidyr")
- library(tidyr)
- if(!require(viridis)) install.packages("viridis")
- library(viridis)
- if(!require(gridExtra)) install.packages("gridExtra")
- library(gridExtra)
- if(!require(readr)) install.packages("readr")
- library(readr)
- if(!require(plyr)) install.packages("plyr")
- library(plyr)
- if(!require(stringr)) install.packages("stringr")
- library(stringr)
- if(!require(readxl)) install.packages("readxl")
- library(readxl)
- if(!require(data.table)) install.packages("data.table")
- library(data.table)
- if(!require(lmerTest)) install.packages("lmerTest")
- library(lmerTest)
- if(!require(plotly)) install.packages("plotly")
- library(plotly)
- if(!require(zoo)) install.packages("zoo")
- library(zoo)
- if(!require(viridis)) install.packages("viridis")
- library(viridis)
- if(!require(brms)) install.packages("brms")
- library(brms)
- if(!require(purrr)) install.packages("purrr")
- library(purrr)
- if(!require(MASS)) install.packages("MASS")
- library(MASS)
- if(!require(rstan)) install.packages("rstan")
- library(rstan)
- if(!require(ggdist)) install.packages("ggdist")
- library(ggdist)
- if(!require(bayestestR)) install.packages("bayestestR")
- library(bayestestR)
- if(!require(posterior)) install.packages("posterior")
- library(posterior)
- if(!require(distributional)) install.packages("distributional")
- library(distributional)
- if(!require(cowplot)) install.packages("cowplot")
- library(cowplot)
- if(!require(modelr)) install.packages("modelr")
- library(modelr)
- if(!require(purrr)) install.packages("purrr")
- library(purrr)
- if(!require(forcats)) install.packages("forcats")
- library(forcats)
- if(!require(tidybayes)) install.packages("tidybayes")
- library(tidybayes)
- if(!require(bayesplot)) install.packages("bayesplot")
- library(bayesplot)
- if(!require(BayesFactor)) install.packages("BayesFactor")
- library(BayesFactor)
- if(!require(patchwork)) install.packages("patchwork")
- library(patchwork)
- if(!require(scales)) install.packages("scales")
- library(scales)
- if(!require(emmeans)) install.packages("emmeans")
- library(emmeans)
- if(!require(zoo)) install.packages("zoo")
- library(zoo)
- if(!require(changepoint)) install.packages("changepoint")
- library(changepoint)
- if(!require(PupillometryR)) install.packages("PupillometryR")
- library(PupillometryR)
- if(!require(zoo)) install.packages("zoo")
- library(zoo)
- if(!require(rstudioapi)) install.packages("rstudioapi")
- library(rstudioapi)
- ```
- ## Loading linearly interpolated data and participant information
- ```{r}
- # loading eye tracking data with gaze direction columns
- options(digits = 15)
- int_data_df <- fread(file = here::here("Data/updated data with gaze/pupil_timecourse_lin_int_with_gaze.csv"))
- # load participant information
- participant_info <- read.csv(here::here("Data/participant_info.csv")) %>%
- dplyr::select(1:5) %>%
- dplyr::rename("age" = "Age", "ppid" = "Participant.ID")
- ```
- ## Participant information and computing variables during automation
- ```{r}
- # calculate avg and sd of pupil diameter for each automation period
- trial_pupil_diameter <- int_data_df %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(time = frame / 60) %>%
- dplyr::filter(critical_or_no == "automation", time <= 60) %>%
- dplyr::group_by(trialid_new, ppid, n_back, lead) %>%
- dplyr::summarise(mean_pupil_diameter = mean(mean_pupil_butter), sd_pupil_diameter = sd (mean_pupil_butter), sd_yaw = sd(yaw_angle_deg))
- # merging participant information
- trial_pupil_diameter <- merge(trial_pupil_diameter, participant_info, by = c("ppid"))
- trial_pupil_diameter$age.c <- scale(trial_pupil_diameter$age, center = T, scale = F)
- # age, driving experience, demographics
- trial_pupil_diameter %>%
- dplyr::group_by(ppid) %>%
- dplyr::slice(1) %>%
- dplyr::ungroup() %>%
- dplyr::summarise(min_age = min(age), max_age = max(age), mean_age = mean(age), sd_age = sd(age), min_miles = min(Approximate.Annual.Mileage..Miles.), max_miles = max(Approximate.Annual.Mileage..Miles.), mean_miles = mean(Approximate.Annual.Mileage..Miles.), sd_miles = sd(Approximate.Annual.Mileage..Miles.), min_license = min(No..of.years.holding.a.full.UK.driving.license), max_license = max(No..of.years.holding.a.full.UK.driving.license), mean_license = mean(No..of.years.holding.a.full.UK.driving.license), sd_license = sd(No..of.years.holding.a.full.UK.driving.license)) %>%
- View()
- # gender split
- trial_pupil_diameter %>%
- dplyr::group_by(ppid) %>%
- dplyr::slice(1) %>%
- dplyr::group_by(Gender) %>%
- dplyr::summarise(n = n())
- ```
- ## DATA SAVING AND LOADING
- Due to the size of the timecourse dataframe, it is not included in the Github repo. However, the trial averaged pupil diameters are. The following code chunk loads this dataframe from the Github repo folder
- ```{r}
- # loading eye tracking data with gaze direction columns
- options(digits = 15)
- trial_pupil_diameter <- fread(file = here::here("Data/updated data with gaze/trial_pupil_diameter.csv"))
- ## Data saving mean diameter
- fwrite(trial_pupil_diameter, file = here::here("Data/updated data with gaze/trial_pupil_diameter.csv"))
- ```
- ## Correlation between pupil diameter and sd of yaw
- In this code chunk, we look at the relationship between pupil diameter and horizontal gaze dispersion. Overall, no consistent relationship is present.
- ```{r}
- ggplot(trial_pupil_diameter, mapping = aes(x = sd_yaw, y = mean_pupil_diameter, col = n_back)) +
- geom_point() +
- facet_wrap(~ lead) +
- xlim(0, 25)
- mean_pupil_sd_yaw <- lmer(mean_pupil_diameter ~ sd_yaw * n_back * lead + (sd_yaw * n_back * lead | ppid), data = trial_pupil_diameter)
- summary(mean_pupil_sd_yaw)
- ggplot(trial_pupil_diameter, mapping = aes(x = sd_yaw, y = sd_pupil_diameter, col = n_back)) +
- geom_point() +
- facet_wrap(~ lead) +
- xlim(0, 25)
- sd_pupil_sd_yaw <- lmer(sd_pupil_diameter ~ sd_yaw * n_back * lead + (sd_yaw * n_back + lead | ppid), data = trial_pupil_diameter)
- summary(sd_pupil_sd_yaw)
- ```
- ## FIGURE 1
- # Distributions of raw mean pupil diameter by conditions
- ```{r}
- # labels
- lead_labs <- c("No lead vehicle", "Lead vehicle")
- names(lead_labs) <- c(FALSE, TRUE)
- fig1 <- ggplot(trial_pupil_diameter, mapping = aes(mean_pupil_diameter, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("Mean pupil diameter (mm)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 50)) +
- scale_x_continuous(limits = c(2.5, 5.5), breaks = seq(3, 5, 1), labels = label_number(accuracy = .50)) +
- facet_wrap(~ lead, labeller = labeller(lead = lead_labs)) +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig1.tiff"), plot = fig1, width = 10, height = 6, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## Model 1
- # mean pupil diameter as a function of n-back and lead vehicle during SAE L2 driving
- ```{r}
- # regularising priors (https://github.com/stan-dev/stan/wiki/prior-choice-recommendations)
- prior(normal(0, 10)) %>%
- parse_dist() %>%
- ggplot(aes(xdist = .dist_obj, y = prior)) +
- stat_halfeye(.width = c(.5, .99)) +
- scale_y_discrete(NULL, breaks = NULL, expand = expansion(add = 0.1)) +
- scale_x_continuous(expression(italic(p)(beta[1]))) +
- theme_bw()
- # mean pupil diameter
- mod_1_mean <- brm(data = trial_pupil_diameter,
- family = gaussian(),
- mean_pupil_diameter ~ n_back * lead + (n_back * lead | ppid),
- prior = c(prior(normal(0, 10), class = "Intercept"),
- prior(normal(0, 10), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_1_mean"))
- # reload mean pupil diameter model
- mod_1_mean <- readRDS(here::here("Models/mod_1_mean.rds"))
- # model summaries for mean pupil diameter
- print(summary(mod_1_mean), digits = 5)
- describe_posterior(mod_1_mean)
- # calculating the marginal effect of n-back
- mod_1_mean %>%
- emmeans(~ n_back,
- at = list(lead = FALSE),
- epred = TRUE, re_formula = NA) %>%
- contrast(method = "revpairwise") %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>%
- View()
- # calculating the marginal effect of lead vehicle
- mod_1_mean %>%
- emmeans(~ lead,
- at = list(n_back = FALSE),
- epred = TRUE, re_formula = NA) %>%
- contrast(method = "revpairwise") %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>%
- View()
- predicted_mean_pupils <- mod_1_mean %>%
- epred_draws(newdata = expand_grid(lead = c(FALSE, TRUE),
- n_back = c(FALSE, TRUE)),
- re_formula = NA)
- predicted_mean_pupils %>% mean_hdi() %>% View()
- ```
- ## Figure 2
- # Plotting heterogenity of effects
- ```{r}
- # labels
- lead_labs <- c("No lead vehicle", "Lead vehicle")
- names(lead_labs) <- c(FALSE, TRUE)
- fig2a <- ggplot(predicted_mean_pupils, aes(x = .epred, y = " ", fill = as.factor(n_back))) +
- stat_histinterval(alpha = .5) +
- ylab(NULL) +
- xlab("Predicted mean pupil diameter (mm)") +
- #xlim(3.5, 4.5) +
- scale_x_continuous(limits = c(3.5, 4.5), breaks = seq(3.60, 4.40, .40), labels = label_number(accuracy = .50)) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- facet_wrap(~ lead, labeller = labeller(lead = lead_labs)) +
- ggtitle("A") +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 12), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- # relative size of heterogeneity effect for N-back - more than .5 is note worthy (variance in the effect is half the size of the average effect)
- summary(mod_1_mean)[["random"]][["ppid"]][2,1] / summary(mod_1_mean)[["fixed"]][2,1]
- # CI interval for average effect
- bayes_credible_intervals <- as.data.frame(summary(mod_1_mean)[["fixed"]][3:4])
- # individual effects - random effects added to the average effect
- ppid_effects <- as.data.frame(ranef(mod_1_mean)) %>%
- dplyr::select(ppid.Estimate.n_backTRUE) %>%
- dplyr::mutate(ranef = ppid.Estimate.n_backTRUE + summary(mod_1_mean)[["fixed"]][2,1])
- ppid_effects$x <- "x"
- # calculate individual effects
- ran_effects <- summary(mod_1_mean)[["random"]][["ppid"]][2,1]
- # strip plot highlighting the individual effects of N-back on mean pupil diameter, alongside the average effct, the confidence intervals, and the heterogeneity intervals.
- fig2b <- ggplot() +
- geom_jitter(ppid_effects, mapping = aes(x = x, y = ranef), width = 0.01, height = 0, size = 3,
- shape = 21, colour = "black", alpha = .95, stroke = 1) +
- #scale_y_continuous(limits = c(-.35, .1), breaks = seq(-.3, .1, 0.1), labels = label_number(accuracy = 0.01)) +
- ylab("N-back effect on mean pupil diameter (mm)") +
- xlab("") +
- geom_hline(aes(yintercept = bayes_credible_intervals$`l-95% CI`[2], linetype ="95% CI"), size = 1.5, color = viridis(5)[3]) +
- geom_hline(aes(yintercept = bayes_credible_intervals$`u-95% CI`[2], linetype="95% CI"), size = 1.5, color = viridis(5)[3]) +
- geom_hline(aes(yintercept = summary(mod_1_mean)[["fixed"]][2,1], linetype = "beta_1"), size = 1.5, color = "black") +
- geom_hline(aes(yintercept = summary(mod_1_mean)[["fixed"]][2,1] + 1.96 * ran_effects, linetype = "95% HI"), size = 1.5, color = viridis(5)[2]) +
- geom_hline(aes(yintercept = summary(mod_1_mean)[["fixed"]][2,1] - 1.96 * ran_effects, linetype = "95% HI"), size = 1.5, color = viridis(5)[2]) +
- scale_linetype_manual(name = " ", values = c(2, 1, 1), labels = c(expression(CI[95]), expression(HI[95]), expression(beta[N])), guide = guide_legend(override.aes = list(color = c(viridis(5)[3], viridis(5)[2], "black")))) +
- scale_fill_manual(name = "Parameter", values = c(viridis(5)[2])) +
- ggtitle("B") +
- theme_bw() +
- theme(legend.position = "bottom", legend.key = element_blank(), legend.key.width = unit(.5, 'cm'), axis.text.y = element_blank(), axis.ticks.y = element_blank(), legend.key.size = unit(0.1, 'cm'), axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), title = element_text(size = 7), legend.title = element_text(size = 11), plot.title = element_text(size = 12, face = "bold"), legend.text = element_text(size = 10)) +
- coord_flip()
- fig2 <- fig2a | fig2b
- ggsave(here::here("Plots/fig2.tiff"), plot = fig2, width = 15, height = 7, units = 'cm', dpi = 300, type = 'cairo')
- # Proportion of people where mean pupil diameter increases
- mean <- summary(mod_1_mean)[["fixed"]][2,1] # Estimated n-back slope for the average person (fixed effect)
- sd <- summary(mod_1_mean)[["random"]][["ppid"]][2,1] # Estimated heterogeneity (in SD units) (random effect)
- ub <- 10
- lb <- 0
- # create an interval ranging from -4 to 4 and multiply by SD.
- # This gives values falling within +/- 4SD. Then add mean.
- x <- seq(-4,4,length=100)*sd + mean
- # put the above into a dataframe along with the density and lower and upper bounds
- # set ub (upper bound) to 0 so we can compute proportion of slope values under this number
- dendf <- data.frame(
- x = seq(-4,4,length=100)*sd + mean,
- hx = dnorm(x,mean,sd),
- ub = 10, lb = 0)
- # compute area under the curve with upper bound at 0 i.e. what proportion of people have reduced SGE when completing n-back
- area <- pnorm(ub, mean, sd) - pnorm(lb, mean, sd)
- signif(area, digits=2) * 100
- ```
- ## FIGURE 3
- # Distributions of raw sd pupil diameter by conditions
- ```{r}
- # labels
- lead_labs <- c("No lead vehicle", "Lead vehicle")
- names(lead_labs) <- c(FALSE, TRUE)
- fig3 <- ggplot(trial_pupil_diameter, mapping = aes(sd_pupil_diameter, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("SD of pupil diameter (mm)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 60)) +
- scale_x_continuous(limits = c(0, .6), breaks = seq(.2, .6, .2), labels = label_number(accuracy = .50)) +
- facet_wrap(~ lead, labeller = labeller(lead = lead_labs)) +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig3.tiff"), plot = fig3, width = 10, height = 6, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## Model 2
- # Standard deviation of pupil diameter
- ```{r}
- # sd pupil diameter
- mod_2_sd <- brm(data = trial_pupil_diameter,
- family = gaussian(),
- sd_pupil_diameter ~ n_back * lead + (n_back * lead | ppid),
- prior = c(prior(normal(0, 10), class = "Intercept"),
- prior(normal(0, 10), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_2_sd"))
- # reload sd pupil diameter model
- mod_2_sd <- readRDS(here::here("Models/mod_2_sd.rds"))
- # model summaries for sd pupil diameter
- print(summary(mod_2_sd), digits = 5)
- describe_posterior(mod_2_sd)
- # calculating the marginal effect of takeover window for mean pupil diameter (i.e., the contrast)
- mod_2_sd %>%
- emmeans(~ n_back,
- at = list(lead = FALSE),
- epred = TRUE, re_formula = NA) %>%
- contrast(method = "revpairwise") %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>%
- View()
- predicted_sd_pupils <- mod_2_sd %>%
- epred_draws(newdata = expand_grid(lead = c(FALSE, TRUE),
- n_back = c(FALSE, TRUE)),
- re_formula = NA)
- predicted_sd_pupils %>% mean_hdi() %>% View()
- ```
- ## FIGURE 4
- # heterogegnity of sd of pupil diameter effects
- ```{r}
- # labels
- lead_labs <- c("No lead vehicle", "Lead vehicle")
- names(lead_labs) <- c(FALSE, TRUE)
- fig4a <- ggplot(predicted_sd_pupils, aes(x = .epred, y = " ", fill = as.factor(n_back))) +
- stat_histinterval(alpha = .5) +
- ylab(NULL) +
- xlab("Predicted SD of pupil diameter (mm)") +
- #xlim(3.5, 4.5) +
- scale_x_continuous(limits = c(.15, .30), breaks = seq(.15, .25, .05)) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- facet_wrap(~ lead, labeller = labeller(lead = lead_labs)) +
- ggtitle("A") +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 12), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- # relative size of heterogeneity effect for N-back - more than .5 is note worthy (variance in the effect is half the size of the average effect)
- summary(mod_2_sd)[["random"]][["ppid"]][2,1] / summary(mod_2_sd)[["fixed"]][2,1]
- # CI interval for average effect
- bayes_credible_intervals <- as.data.frame(summary(mod_2_sd)[["fixed"]][3:4])
- # individual effects - random effects added to the average effect
- ppid_effects <- as.data.frame(ranef(mod_2_sd)) %>%
- dplyr::select(ppid.Estimate.n_backTRUE) %>%
- dplyr::mutate(ranef = ppid.Estimate.n_backTRUE + summary(mod_2_sd)[["fixed"]][2,1])
- ppid_effects$x <- "x"
- # calculate individual effects
- ran_effects <- summary(mod_2_sd)[["random"]][["ppid"]][2,1]
- # strip plot highlighting the individual effects of N-back on mean pupil diameter, alongside the average effct, the confidence intervals, and the heterogeneity intervals.
- fig4b <- ggplot() +
- geom_jitter(ppid_effects, mapping = aes(x = x, y = ranef), width = 0.01, height = 0, size = 3,
- shape = 21, colour = "black", alpha = .95, stroke = 1) +
- #scale_y_continuous(limits = c(-.35, .1), breaks = seq(-.3, .1, 0.1), labels = label_number(accuracy = 0.01)) +
- ylab("N-back effect on SD of pupil diameter (mm)") +
- xlab("") +
- geom_hline(aes(yintercept = bayes_credible_intervals$`l-95% CI`[2], linetype ="95% CI"), size = 1.5, color = viridis(5)[3]) +
- geom_hline(aes(yintercept = bayes_credible_intervals$`u-95% CI`[2], linetype="95% CI"), size = 1.5, color = viridis(5)[3]) +
- geom_hline(aes(yintercept = summary(mod_2_sd)[["fixed"]][2,1], linetype = "beta_1"), size = 1.5, color = "black") +
- geom_hline(aes(yintercept = summary(mod_2_sd)[["fixed"]][2,1] + 1.96 * ran_effects, linetype = "95% HI"), size = 1.5, color = viridis(5)[2]) +
- geom_hline(aes(yintercept = summary(mod_2_sd)[["fixed"]][2,1] - 1.96 * ran_effects, linetype = "95% HI"), size = 1.5, color = viridis(5)[2]) +
- scale_linetype_manual(name = " ", values = c(2, 1, 1), labels = c(expression(CI[95]), expression(HI[95]), expression(beta[N])), guide = guide_legend(override.aes = list(color = c(viridis(5)[3], viridis(5)[2], "black")))) +
- scale_fill_manual(name = "Parameter", values = c(viridis(5)[2])) +
- ggtitle("B") +
- theme_bw() +
- theme(legend.position = "bottom", legend.key = element_blank(), legend.key.width = unit(.5, 'cm'), axis.text.y = element_blank(), axis.ticks.y = element_blank(), legend.key.size = unit(0.1, 'cm'), axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), title = element_text(size = 7), legend.title = element_text(size = 11), plot.title = element_text(size = 12, face = "bold"), legend.text = element_text(size = 10)) +
- coord_flip()
- fig4 <- fig4a | fig4b
- ggsave(here::here("Plots/fig4.tiff"), plot = fig4, width = 15, height = 7, units = 'cm', dpi = 300, type = 'cairo')
- # Proportion of people where mean pupil diameter increases
- mean <- summary(mod_2_sd)[["fixed"]][2,1] # Estimated n-back slope for the average person (fixed effect)
- sd <- summary(mod_2_sd)[["random"]][["ppid"]][2,1] # Estimated heterogeneity (in SD units) (random effect)
- ub <- 10
- lb <- 0
- # create an interval ranging from -4 to 4 and multiply by SD.
- # This gives values falling within +/- 4SD. Then add mean.
- x <- seq(-4,4,length=100)*sd + mean
- # put the above into a dataframe along with the density and lower and upper bounds
- # set ub (upper bound) to 0 so we can compute proportion of slope values under this number
- dendf <- data.frame(
- x = seq(-4,4,length=100)*sd + mean,
- hx = dnorm(x,mean,sd),
- ub = 10, lb = 0)
- # compute area under the curve with upper bound at 0 i.e. what proportion of people have reduced SGE when completing n-back
- area <- pnorm(ub, mean, sd) - pnorm(lb, mean, sd)
- signif(area, digits=2) * 100
- ```
- ## Calulcation of reaction times and computation of pupil timecourse
- ```{r}
- # takeover times for non-critical takeovers
- takeover_times_non_critical <- int_data_df %>%
- dplyr::filter(critical == FALSE) %>%
- dplyr::filter(critical_or_no == "non_critical_takeover") %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(frame = row_number(), time = frame / 60) %>%
- dplyr::filter(vehicle_state == "manual") %>%
- dplyr::slice(1) %>%
- dplyr::mutate(r_time = time) %>%
- dplyr::select(trialid_new, r_time) %>%
- dplyr::ungroup() %>%
- dplyr::filter(r_time >= .3) %>% # filtering out taketimes less than 300 ms
- dplyr::mutate(z_score = scale(r_time)) %>% # calculate z scores
- dplyr::filter(z_score < 3) # filter out z scores more than 3
- # takeover times for critical takeovers
- takeover_times_critical <- int_data_df %>%
- dplyr::filter(critical == TRUE) %>%
- dplyr::filter(critical_or_no == "critical_takeover") %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(frame = row_number(), time = frame / 60) %>%
- dplyr::filter(vehicle_state == "manual") %>%
- dplyr::slice(1) %>%
- dplyr::mutate(r_time = time) %>%
- dplyr::select(trialid_new, r_time) %>%
- dplyr::ungroup() %>%
- dplyr::filter(r_time >= .3) %>% # filtering out taketimes less than 300 ms
- dplyr::mutate(z_score = scale(r_time)) %>% # calculate z scores
- dplyr::filter(z_score < 3) # filter out z scores more than 3
- # combining critical and non-critical takeover times
- takeover_times <- rbind(takeover_times_critical, takeover_times_non_critical)
- # detecting 5th and 95th percentiles for plotting
- lower_bound <- quantile(takeover_times$r_time, 0.05)
- upper_bound <- quantile(takeover_times$r_time, 0.95)
- # merging takeover times with timecourse data
- int_data_df <- merge(int_data_df, takeover_times, by = c("trialid_new"))
- #### computing timecourse #####
- # timecourse for takeover and manual drive
- takeover_manual <- int_data_df %>%
- dplyr::arrange(trialid_new, frame) %>%
- dplyr::filter(critical_or_no %in% c("non_critical_takeover", "critical_takeover")) %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(frame_new = row_number(), time = frame_new / 60) %>%
- dplyr::filter(time <= 20)
- # timecourse for 10 s of automation before takeover
- automation_pre <- int_data_df %>%
- dplyr::arrange(trialid_new, frame) %>%
- dplyr::filter(critical_or_no == "automation") %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(frame_new = row_number(), time = frame_new / 60) %>%
- dplyr::mutate(end_time = time[n()]) %>%
- dplyr::filter(time >= (end_time - 10))
- # combined timecourse 12 s = TOR, 24 s = manual drive
- takeover_manual_automation_pre <- rbind(takeover_manual, automation_pre) %>%
- dplyr::arrange(trialid_new, frame) %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(frame_timecourse = row_number()) %>%
- dplyr::mutate(timecourse = frame_timecourse / 60)
- ```
- ## FIGURE 5
- # TEPR timecourse for each condition
- ```{r}
- # standard error function
- stderror <- function(x) sd(x)/sqrt(length(x))
- pupil_timecourse_plot_df <- takeover_manual_automation_pre %>%
- dplyr::filter(r_time > .3) %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::filter(timecourse >= 10 - 1, timecourse <= 10 + 2.5) %>%
- dplyr::mutate(timecourse_onset = timecourse - 10) %>%
- dplyr::group_by(timecourse_onset, n_back, ttc_criticality) %>%
- dplyr::summarise(pupil = mean(mean_pupil_butter), pupil_sem = stderror(mean_pupil_butter))
- ttc_labs <- c("Non-critical", "TTC = 3 s", "TTC = 5 s")
- names(ttc_labs) <- c(0, 3, 5)
- fig5 <- ggplot(pupil_timecourse_plot_df, mapping = aes(timecourse_onset, pupil, fill = as.factor(n_back), colour = as.factor(n_back))) +
- geom_rect(aes(xmin = -.5, xmax = 0, ymin = -Inf, ymax = Inf), alpha = .1, fill = "grey90", colour = NA) +
- geom_rect(aes(xmin = .3, xmax = 1.8, ymin = -Inf, ymax = Inf), alpha = .1, fill = "grey90", colour = NA) +
- geom_line(size = 1) +
- geom_ribbon(aes(
- ymin = pupil - pupil_sem,
- ymax = pupil + pupil_sem,
- group = n_back),
- alpha = 0.25,
- colour = NA) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_colour_discrete(name = " ", labels = c("No N-back", "N-back")) +
- ylim(3.5, 4.5) +
- geom_vline(aes(xintercept = 0), linetype = "solid") +
- geom_vline(aes(xintercept = -.5), linetype = "dashed") +
- geom_vline(aes(xintercept = .3), linetype = "dashed") +
- geom_vline(aes(xintercept = 1.8), linetype = "dashed") +
- ylab("Pupil diameter (mm)") +
- xlab("Time (s)") +
- scale_x_continuous(limits = c(-1, 2), breaks = seq(-1, 1, 1), labels = label_number(accuracy = .50)) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 11), axis.text.x = element_text(size = 11), axis.title.y = element_text(size = 11), axis.text.y = element_text(size = 11), title = element_text(size = 18), legend.title = element_text(size = 12), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig5.tiff"), plot = fig5, width = 15, height = 8, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## Calculating correction for regression to the mean
- ```{r}
- # here we calculate baseline and change of a random point without any task relevant information. This highlights a "pseudo" change
- rtm_data <- takeover_manual_automation_pre %>%
- dplyr::filter(critical_or_no == "automation") %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::summarise(
- baseline = mean(mean_pupil_butter[timecourse > 5 & timecourse <= 5.5], na.rm = TRUE),
- pseudo_change = mean(mean_pupil_butter[timecourse > 6.3 & timecourse <= 7.8], na.rm = TRUE) -
- baseline,
- .groups = "drop"
- )
- # we fit a cubic model to predict the pseudo change as a function of the baseline period.
- rtm_model <- lm(
- pseudo_change ~ poly(baseline, 3, raw = TRUE),
- data = rtm_data
- )
- summary(rtm_model)
- # we caluclate the baseline and real task-evoked pupillary dilation
- task_data <- takeover_manual_automation_pre %>%
- dplyr::filter(r_time > .3) %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::mutate(ttc_criticality = as.factor(ttc_criticality)) %>%
- dplyr::group_by(trialid_new, n_back, ttc_criticality, ppid, lead, critical, r_time) %>%
- summarise(
- baseline = mean(mean_pupil_butter[timecourse >= 9.5 & timecourse <= 10], na.rm = TRUE),
- observed_ped =
- mean(mean_pupil_butter[timecourse > 10.3 & timecourse <= 11.8], na.rm = TRUE) -
- baseline,
- .groups = "drop"
- )
- # we predict the likely task-evoked dilation if baseline predicted it. Then subtracted this pseudo dilation from the real task-evoked dilation, which leaves us the corrected phasic response.
- task_data <- task_data %>%
- mutate(
- predicted_rtm = predict(rtm_model, newdata = task_data),
- ped_corrected = observed_ped - predicted_rtm
- )
- ```
- ## FIGURE 6
- # Disitribution of corrected and uncorrected TEPR
- ```{r}
- # labels
- ttc_labs <- c("Non-critical", "TTC = 3 s", "TTC = 5 s")
- names(ttc_labs) <- c(0, 3, 5)
- # uncorrected
- fig6a <- ggplot(task_data, mapping = aes(observed_ped, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("TEPRs (mm)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 60)) +
- ggtitle("A: Uncorrected TEPRs") +
- #scale_x_continuous(limits = c(0, .6), breaks = seq(.2, .6, .2), labels = label_number(accuracy = .50)) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- theme_bw() +
- theme(legend.position = c(.90, .73), axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- # corrected
- fig6b <- ggplot(task_data, mapping = aes(ped_corrected, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("TEPRs (mm)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 60)) +
- ggtitle("B: Corrected TEPRs") +
- #scale_x_continuous(limits = c(0, .6), breaks = seq(.2, .6, .2), labels = label_number(accuracy = .50)) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- theme_bw() +
- theme(legend.position = "none", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- fig6 <- fig6a / fig6b
- ggsave(here::here("Plots/fig6.tiff"), plot = fig6, width = 15, height = 12, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## MODEL 3
- # Correlation between baseline pupil diameter and TEPR - UNCORRECTED
- ```{r}
- # regularising priors (https://github.com/stan-dev/stan/wiki/prior-choice-recommendations)
- prior(normal(0, .5)) %>%
- parse_dist() %>%
- ggplot(aes(xdist = .dist_obj, y = prior)) +
- stat_halfeye(.width = c(.5, .99)) +
- scale_y_discrete(NULL, breaks = NULL, expand = expansion(add = 0.1)) +
- scale_x_continuous(expression(italic(p)(beta[1]))) +
- theme_bw()
- # mean pupil diameter
- mod_3_phasic_uncorrected <- brm(data = task_data,
- family = gaussian(),
- observed_ped ~ baseline * n_back * ttc_criticality + (baseline | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_3_phasic_uncorrected"))
- # model summaries for sd pupil diameter
- print(summary(mod_3_phasic_uncorrected), digits = 5)
- describe_posterior(mod_3_phasic_uncorrected)
- # posterior checks
- pp_check(mod_3_phasic_uncorrected , type = "dens_overlay_grouped", group = "ttc_criticality", ndraws = 100)
- pp_check(mod_3_phasic_uncorrected , type = "dens_overlay_grouped", group = "n_back", ndraws = 100)
- # reload uncorrected phasic model
- mod_3_phasic_uncorrected <- readRDS(here::here("Models/mod_3_phasic_uncorrected.rds"))
- # average change in TEPR for a change in baseline pupil diameter
- mod_3_phasic_uncorrected %>%
- emtrends(
- ~ 1,
- var = "baseline",
- epred = TRUE,
- re_formula = NA
- ) %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>% View()
- # average change in TEPR for a change in baseline pupil diameter, within each level of n-back
- mod_3_phasic_uncorrected %>%
- emtrends(
- ~ n_back,
- var = "baseline",
- epred = TRUE,
- re_formula = NA
- ) %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>% View()
- # create a new dataset based on the original dataset
- newdata <- task_data %>%
- distinct(ttc_criticality, n_back) %>%
- crossing(
- baseline = seq(
- min(task_data$baseline, na.rm = TRUE),
- max(task_data$baseline, na.rm = TRUE),
- length.out = 100
- )
- )
- # generate population level predictions from the model
- preds <- add_epred_draws(
- mod_3_phasic_uncorrected,
- newdata = newdata,
- re_formula = NA # re_formula = NA = fixed effects only
- )
- # creates 95% CIs for estimate
- pred_summary <- preds %>%
- mean_qi(.epred)
- ```
- ## MODEL 4
- # Correlation between baseline pupil diameter and TEPR - CORRECTED
- ```{r}
- # mean pupil diameter
- mod_4_phasic_corrected <- brm(data = task_data,
- family = gaussian(),
- ped_corrected ~ baseline * n_back * ttc_criticality + (baseline | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_4_phasic_corrected"))
- # reload mean pupil diameter model
- mod_4_phasic_corrected <- readRDS(here::here("Models/mod_4_phasic_corrected.rds"))
- # model summaries for corrected phasic model
- print(summary(mod_4_phasic_corrected), digits = 5)
- describe_posterior(mod_4_phasic_corrected)
- # average change in TEPR for a change in baseline pupil diameter
- mod_4_phasic_corrected %>%
- emtrends(
- ~ 1,
- var = "baseline",
- epred = TRUE,
- re_formula = NA
- ) %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>% View()
- # average change in TEPR for a change in baseline pupil diameter, within each level of n-back
- mod_4_phasic_corrected %>%
- emtrends(
- ~ n_back,
- var = "baseline",
- epred = TRUE,
- re_formula = NA
- ) %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>% View()
- # generate population level predictions from the model
- preds_correct <- add_epred_draws(
- mod_4_phasic_corrected,
- newdata = newdata,
- re_formula = NA # re_formula = NA = fixed effects only
- )
- # creates 95% CIs for estimate
- pred_summary_correct <- preds_correct %>%
- mean_qi(.epred)
- ```
- ## FIGURE 7
- # Plotting corrected and uncorrected TEPR model
- ```{r}
- ttc_labs <- c("Non-critical", "TTC = 3 s", "TTC = 5 s")
- names(ttc_labs) <- c(0, 3, 5)
- fig7a <- ggplot() +
- geom_point(task_data, mapping = aes(x = baseline, y = observed_ped, color = as.factor(n_back)), alpha = 0.6) +
- geom_ribbon(data = pred_summary, mapping = aes(x = baseline, ymin = .lower, ymax = .upper, fill = as.factor(n_back)), alpha = 0.2) +
- geom_line(data = pred_summary, mapping = aes(x = baseline, y = .epred,color = as.factor(n_back)), linewidth = 1) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_colour_discrete(name = " ", labels = c("No N-back", "N-back")) +
- xlab("Baseline pupil diameter (mm)") +
- ylab("Uncorrected TEPR (mm)") +
- theme_bw() +
- ggtitle("A: Uncorrected TEPRs") +
- xlim(2.5, 6) +
- ylim(-1, 1) +
- theme(legend.position = c(.56, .84), axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- fig7b <- ggplot() +
- geom_point(task_data, mapping = aes(x = baseline, y = ped_corrected, color = as.factor(n_back)), alpha = 0.6) +
- geom_ribbon(data = pred_summary_correct, mapping = aes(x = baseline, ymin = .lower, ymax = .upper, fill = as.factor(n_back)), alpha = 0.2) +
- geom_line(data = pred_summary_correct, mapping = aes(x = baseline, y = .epred,color = as.factor(n_back)), linewidth = 1) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_colour_discrete(name = " ", labels = c("No N-back", "N-back")) +
- xlab("Baseline pupil diameter (mm)") +
- ylab("Corrected TEPR (mm)") +
- theme_bw() +
- ggtitle("B: Corrected TEPRs") +
- xlim(2.5, 6) +
- ylim(-1, 1) +
- theme(legend.position = "none", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- fig7 <- fig7a / fig7b
- fig7
- ggsave(here::here("Plots/fig7.tiff"), plot = fig7, width = 15, height = 16, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## FIGURE 8
- # Reaction time distribution across each condition
- ```{r}
- fig8 <- ggplot(task_data, mapping = aes(r_time, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("Reaction time (s)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 60)) +
- #scale_x_continuous(limits = c(0, .6), breaks = seq(.2, .6, .2), labels = label_number(accuracy = .50)) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig8.tiff"), plot = fig8, width = 15, height = 6, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## MODEL 5
- # Relationship betweem reaction time and pupillary dilations
- Our previous research previously indicated that there were no interactions between n_back and ttc criticality (Goodridge et al, 2026) we opted to remove 2way and 3way interactions between these variables. The removal of these higher-order interactions provided improved interpretability of model parameters as well as more targeted assessment of the effects of interest.
- ```{r}
- # regularising priors (https://github.com/stan-dev/stan/wiki/prior-choice-recommendations)
- prior(normal(0, .5)) %>%
- parse_dist() %>%
- ggplot(aes(xdist = .dist_obj, y = prior)) +
- stat_halfeye(.width = c(.5, .99)) +
- scale_y_discrete(NULL, breaks = NULL, expand = expansion(add = 0.1)) +
- scale_x_continuous(expression(italic(p)(beta[1]))) +
- theme_bw()
- # mean pupil diameter
- mod_5_RT <- brm(data = task_data,
- family = lognormal(link = "identity"),
- r_time ~ ped_corrected +
- ttc_criticality +
- ped_corrected:n_back +
- ped_corrected:ttc_criticality +
- ped_corrected:n_back:ttc_criticality + (ped_corrected | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_5_RT"))
- # reload mean pupil diameter model
- mod_5_RT <- readRDS(here::here("Models/mod_5_RT.rds"))
- # posterior checks
- pp_check(mod_5_RT , type = "dens_overlay_grouped", group = "ttc_criticality", ndraws = 100)
- # model summaries for reaction time
- print(summary(mod_5_RT ), digits = 5)
- describe_posterior(mod_5_RT )
- # create a new dataset based on the original dataset
- newdata_ped_corrected <- task_data %>%
- distinct(ttc_criticality, n_back) %>%
- crossing(
- ped_corrected = seq(
- min(task_data$ped_corrected, na.rm = TRUE),
- max(task_data$ped_corrected, na.rm = TRUE),
- length.out = 100
- )
- )
- # generate population level predictions from the model
- preds_r_time <- add_epred_draws(
- mod_5_RT,
- newdata = newdata_ped_corrected,
- re_formula = NA # re_formula = NA = fixed effects only
- )
- # creates 95% CIs for estimate
- pred_summary_r_time <- preds_r_time %>%
- mean_qi(.epred)
- #
- mean_ped <- mean(task_data$ped_corrected, na.rm = TRUE)
- #
- mod_5_RT %>%
- emmeans(
- ~ ttc_criticality,
- at = list(
- ped_corrected = mean_ped,
- n_back = FALSE
- ),
- epred = TRUE,
- re_formula = NA
- )
- mod_5_RT %>%
- emmeans(
- ~ ttc_criticality,
- at = list(
- ped_corrected = mean_ped,
- n_back = FALSE
- ),
- epred = TRUE,
- re_formula = NA
- ) %>%
- contrast(method = "revpairwise") %>%
- gather_emmeans_draws() %>%
- mean_hdi()
- ```
- ## FIGURE 9
- # correlation between reaction times and TEPRs
- ```{r}
- ttc_labs <- c("Non-critical", "TTC = 3 s", "TTC = 5 s")
- names(ttc_labs) <- c(0, 3, 5)
- fig9 <- ggplot() +
- geom_point(task_data, mapping = aes(x = ped_corrected, y = r_time, color = as.factor(n_back)), alpha = 0.6) +
- geom_ribbon(data = pred_summary_r_time, mapping = aes(x = ped_corrected, ymin = .lower, ymax = .upper, fill = as.factor(n_back)), alpha = 0.2) +
- geom_line(data = pred_summary_r_time, mapping = aes(x = ped_corrected, y = .epred,color = as.factor(n_back)), linewidth = 1) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_colour_discrete(name = " ", labels = c("No N-back", "N-back")) +
- xlab("Corrected TEPR (mm)") +
- ylab("Reaction time (s)") +
- theme_bw() +
- ylim(0, 4) +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 15, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig9.tiff"), plot = fig9, width = 15, height = 8, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## MODEL 6
- # Correlation between M-pui and reaction time
- ```{r}
- mpui_df <- takeover_manual_automation_pre %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::mutate(ttc_criticality = as.factor(ttc_criticality)) %>%
- dplyr::filter(timecourse >= 10 - 1, timecourse <= 10) %>% # baseline window
- dplyr::group_by(trialid_new, n_back, ttc_criticality, critical, lead, ppid, r_time) %>%
- dplyr::mutate(dpupil_50 = mean_pupil_50 - lag(mean_pupil_50),
- dpupil_100 = mean_pupil_100 - lag(mean_pupil_100),
- dpupil_200 = mean_pupil_200 - lag(mean_pupil_200),
- dpupil_400 = mean_pupil_400 - lag(mean_pupil_400)) %>%
- dplyr::summarise(mpui_50 = sum(abs(dpupil_50), na.rm = TRUE) / sum(!is.na(dpupil_50)), n_samples = sum(!is.na(dpupil_50)),
- mpui_100 = sum(abs(dpupil_100), na.rm = TRUE) / sum(!is.na(dpupil_100)), n_samples = sum(!is.na(dpupil_100)),
- mpui_200 = sum(abs(dpupil_200), na.rm = TRUE) / sum(!is.na(dpupil_200)), n_samples = sum(!is.na(dpupil_200)),
- mpui_400 = sum(abs(dpupil_400), na.rm = TRUE) / sum(!is.na(dpupil_400)), n_samples = sum(!is.na(dpupil_400)),
- .groups = "drop"
- )
- # regularising priors
- prior(normal(0, .5)) %>%
- parse_dist() %>%
- ggplot(aes(xdist = .dist_obj, y = prior)) +
- stat_halfeye(.width = c(.5, .99)) +
- scale_y_discrete(NULL, breaks = NULL, expand = expansion(add = 0.1)) +
- scale_x_continuous(expression(italic(p)(beta[1]))) +
- theme_bw()
- # reaction time predicted by m-pui 50
- mod_6_mpui50 <- brm(data = mpui_df,
- family = lognormal(link = "identity"),
- r_time ~ mpui_50 + ttc_criticality + mpui_50:n_back + mpui_50:ttc_criticality + mpui_50:n_back:ttc_criticality + (mpui_50 | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_6_mpui50"))
- # reload mpui50 model
- mod_6_mpui50 <- readRDS(here::here("Models/mod_6_mpui50.rds"))
- # model summaries for sd pupil diameter
- print(summary(mod_6_mpui50), digits = 5)
- describe_posterior(mod_6_mpui50)
- # reaction time predicted by m-pui 100
- mod_6_mpui100 <- brm(data = mpui_df,
- family = lognormal(link = "identity"),
- r_time ~ mpui_100 + ttc_criticality + mpui_100:n_back + mpui_100:ttc_criticality + mpui_100:n_back:ttc_criticality + (mpui_100 | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_6_mpui100"))
- # reload mpui100 model
- mod_6_mpui100 <- readRDS(here::here("Models/mod_6_mpui100.rds"))
- # model summaries for sd pupil diameter
- print(summary(mod_6_mpui100), digits = 5)
- describe_posterior(mod_6_mpui100)
- # reaction time predicted by m-pui 200
- mod_6_mpui200 <- brm(data = mpui_df,
- family = lognormal(link = "identity"),
- r_time ~ mpui_200 + ttc_criticality + mpui_200:n_back + mpui_200:ttc_criticality + mpui_200:n_back:ttc_criticality + (mpui_200 | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_6_mpui200"))
- # reload mpui200 model
- mod_6_mpui200 <- readRDS(here::here("Models/mod_6_mpui200.rds"))
- # model summaries for sd pupil diameter
- print(summary(mod_6_mpui200), digits = 5)
- describe_posterior(mod_6_mpui200)
- # reaction time predicted by m-pui 400
- mod_6_mpui400 <- brm(data = mpui_df,
- family = lognormal(link = "identity"),
- r_time ~ mpui_400 + ttc_criticality + mpui_400:n_back + mpui_400:ttc_criticality + mpui_400:n_back:ttc_criticality + (mpui_400 | ppid),
- prior = c(prior(normal(0, .5), class = "Intercept"),
- prior(normal(0, .5), class = "b"),
- prior(cauchy(0, 2), class = sd)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_6_mpui400"))
- # reload mpui400 model
- mod_6_mpui400 <- readRDS(here::here("Models/mod_6_mpui400.rds"))
- # model summaries for sd pupil diameter
- print(summary(mod_6_mpui400), digits = 5)
- describe_posterior(mod_6_mpui400)
- # create a new dataset based on the original dataset
- newdata_mpui50 <- mpui_df %>%
- distinct(ttc_criticality, n_back) %>%
- crossing(
- mpui_50 = seq(
- min(mpui_df$mpui_50, na.rm = TRUE),
- max(mpui_df$mpui_50, na.rm = TRUE),
- length.out = 100
- )
- )
- # generate population level predictions from the model
- preds_mpui50 <- add_epred_draws(
- mod_6_mpui50,
- newdata = newdata_mpui50,
- re_formula = NA # re_formula = NA = fixed effects only
- )
- # creates 95% CIs for estimate
- pred_summary_mpui50 <- preds_mpui50 %>%
- mean_qi(.epred)
- ```
- ## Figure 10
- ```{r}
- ttc_labs <- c("Non-critical", "TTC = 3 s", "TTC = 5 s")
- names(ttc_labs) <- c(0, 3, 5)
- fig10 <- ggplot() +
- geom_point(mpui_df, mapping = aes(x = mpui_50, y = r_time, col = as.factor(n_back)), alpha = 0.6) +
- geom_ribbon(data = pred_summary_mpui50, mapping = aes(x = mpui_50, ymin = .lower, ymax = .upper,fill = as.factor(n_back)), alpha = 0.2) +
- geom_line(data = pred_summary_mpui50, mapping = aes(x = mpui_50, y = .epred, col = as.factor(n_back)), linewidth = 1) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_colour_discrete(name = " ", labels = c("No N-back", "N-back")) +
- xlab("M-PUI (mm/s)") +
- ylab("Reaction time (s)") +
- theme_bw() +
- ylim(0, 4) +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 15, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig10.tiff"), plot = fig10, width = 15, height = 8, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## FIGURE 11
- # Plotting tonic increases in pupil diameter during transitions
- ```{r}
- # baseline corrected changed in pupil diameter
- baseline_pupil <- task_data %>%
- dplyr::select(trialid_new, baseline)
- takeover_manual_automation_pre <- merge(takeover_manual_automation_pre, baseline_pupil, by = c("trialid_new"))
- # calculate maximum baseline-corrected change in pupil during transition of control
- transitions_pupil_change <- takeover_manual_automation_pre %>%
- dplyr::arrange(trialid_new, timecourse) %>%
- dplyr::filter(timecourse > 10, timecourse < (20 + median(takeover_times$r_time))) %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::group_by(n_back, ttc_criticality, ppid, trialid_new, baseline, critical) %>%
- dplyr::summarise(mean_pupil = mean(mean_pupil_butter))
- # loading eye tracking data with gaze direction columns
- options(digits = 15)
- transitions_pupil_change <- fread(file = here::here("Data/updated data with gaze/transitions_pupil_change.csv"))
- ## Data saving mean diameter
- fwrite(transitions_pupil_change, file = here::here("Data/updated data with gaze/transitions_pupil_change.csv"))
- fig11 <- ggplot(transitions_pupil_change, mapping = aes(mean_pupil, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("Mean pupil diameter (mm)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 40)) +
- scale_x_continuous(limits = c(2.5, 5.5), breaks = seq(3, 5, 1), labels = label_number(accuracy = .50)) +
- facet_wrap(~ ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig11.tiff"), plot = fig11, width = 15, height = 6, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## FIGURE 12 Plotting pupil diameter during takeover windows
- ```{r}
- # create change in pupil size measure
- takeover_manual_automation_pre <- takeover_manual_automation_pre %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(baseline = mean(mean_pupil_butter[timecourse > 0 & timecourse <= 10], na.rm = TRUE)) %>%
- dplyr::mutate(pupil_change = mean_pupil_butter - baseline)
- # raw average timecourse
- fig12 <- ggplot(takeover_manual_automation_pre %>%
- dplyr::filter(r_time > .3) %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::group_by(n_back, ttc_criticality, timecourse) %>%
- dplyr::summarise(m = mean(mean_pupil_butter), pupil_sem = stderror(mean_pupil_butter))) +
- geom_line(mapping = aes(x = timecourse, y = m, col = n_back)) +
- scale_color_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- geom_vline(aes(xintercept = 10), linetype = "dashed") +
- geom_vline(aes(xintercept = (20 + median(takeover_times$r_time))), linetype = "dashed") +
- geom_ribbon(mapping = aes(x = timecourse, y = m, ymin = m - pupil_sem, ymax = m + pupil_sem, fill = n_back), alpha = 0.25, colour = NA) +
- facet_wrap(~ttc_criticality, labeller = labeller(ttc_criticality = ttc_labs)) +
- xlim(0, 30) +
- ylim(3.5, 4.5) +
- ylab("Pupil diameter (mm)") +
- xlab("Time (s)") +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 11), axis.text.x = element_text(size = 11), axis.title.y = element_text(size = 11), axis.text.y = element_text(size = 11), title = element_text(size = 18), legend.title = element_text(size = 12), legend.text = element_text(size = 12), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 10))
- ggsave(here::here("Plots/fig12.tiff"), plot = fig12, width = 15, height = 7, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## MODEL 7
- ```{R}
- # regularising priors (https://github.com/stan-dev/stan/wiki/prior-choice-recommendations)
- prior(normal(0, 10)) %>%
- parse_dist() %>%
- ggplot(aes(xdist = .dist_obj, y = prior)) +
- stat_halfeye(.width = c(.5, .99)) +
- scale_y_discrete(NULL, breaks = NULL, expand = expansion(add = 0.1)) +
- scale_x_continuous(expression(italic(p)(beta[1]))) +
- theme_bw()
- # mean pupil diameter change during transition phase
- mod_7_mean_transition <- brm(data = transitions_pupil_change,
- family = gaussian(),
- mean_pupil ~ as.factor(n_back) * as.factor(ttc_criticality) +
- (as.factor(n_back) + as.factor(ttc_criticality) | ppid),
- prior = c(prior(normal(0, 10), class = "Intercept"),
- prior(normal(0, 10), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_7_mean_transition"))
- # model summaries
- print(summary(mod_7_mean_transition), digits = 5)
- describe_posterior(mod_7_mean_transition)
- # reload model
- mod_7_mean_transition <- readRDS(here::here("Models/mod_7_mean_transition.rds"))
- # posterior checks
- pp_check(mod_7_mean_transition, type = "dens_overlay_grouped", group = "ttc_criticality", ndraws = 100)
- # calculating the marginal effect of takeover window for mean pupil diameter (i.e., the contrast)
- mod_7_mean_transition %>%
- emmeans(~ ttc_criticality,
- at = list(n_back = TRUE),
- epred = TRUE, re_formula = NA) %>%
- contrast(method = "revpairwise") %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>%
- View()
- predicted_pupil_change <- mod_7_mean_transition %>%
- epred_draws(newdata = expand_grid(ttc_criticality = c(0, 3, 5),
- n_back = c(FALSE, TRUE)),
- re_formula = NA)
- predicted_pupil_change %>% mean_hdi() %>% View()
- ```
- ################# POST HOC ANALYSIS #########################
- ## FIGURE 13
- # plot gaze distribution histogram
- ```{r}
- # creating time window variable
- takeover_manual_automation_pre <- takeover_manual_automation_pre %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::group_by(trialid_new) %>%
- dplyr::mutate(time_windows = case_when(
- timecourse <= 10 ~ "pre-RTI",
- timecourse > 10 & timecourse <= (20 + median(takeover_times$r_time)) ~ "transition",
- timecourse > 20 ~ "post-transition"))
- sd_pitch_df <- takeover_manual_automation_pre %>%
- dplyr::group_by(n_back, ttc_criticality, time_windows, ppid, trialid_new) %>%
- dplyr::summarise(sd_pitch = sd(pitch_angle_deg))
- # labels - n-back
- t.labs <- c("Pre-RTI", "Transition", "Post-transition")
- names(t.labs) <- c("pre-RTI", "transition","post-transition")
- # reorder time windows for plotting
- sd_pitch_df$time_windows <- factor(sd_pitch_df$time_windows,
- levels = c("pre-RTI", "transition", "post-transition"))
- fig13 <- ggplot(sd_pitch_df, mapping = aes(sd_pitch, fill = as.factor(n_back))) +
- geom_histogram(alpha = .5) +
- xlab("SD of pitch angle (°)") +
- ylab("Count") +
- scale_fill_discrete(name = " ", labels = c("No N-back", "N-back")) +
- scale_y_continuous(expand = c(0, 0), limits = c(0, 65)) +
- #scale_x_continuous(limits = c(0, .6), breaks = seq(.2, .6, .2), labels = label_number(accuracy = .50)) +
- facet_grid(time_windows ~ ttc_criticality, labeller = labeller(time_windows = t.labs, ttc_criticality = ttc_labs)) +
- theme_bw() +
- theme(legend.position = "bottom", axis.title.x = element_text(size = 10), axis.text.x = element_text(size = 10), axis.title.y = element_text(size = 10), axis.text.y = element_text(size = 10), title = element_text(size = 18), legend.title = element_text(size = 11), legend.text = element_text(size = 10), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 12, face = "bold"), strip.text = element_text(face = "bold", size = 7))
- ggsave(here::here("Plots/fig13.tiff"), plot = fig13, width = 15, height = 10, units = 'cm', dpi = 300, type = 'cairo')
- ```
- ## MODEL 8
- ```{R}
- # regularising priors (https://github.com/stan-dev/stan/wiki/prior-choice-recommendations)
- prior(normal(0, 5)) %>%
- parse_dist() %>%
- ggplot(aes(xdist = .dist_obj, y = prior)) +
- stat_halfeye(.width = c(.5, .99)) +
- scale_y_discrete(NULL, breaks = NULL, expand = expansion(add = 0.1)) +
- scale_x_continuous(expression(italic(p)(beta[1]))) +
- theme_bw()
- # mean pupil diameter change during transition phase
- mod_8_sd_pitch <- brm(data = sd_pitch_df,
- family = gaussian(),
- sd_pitch ~ as.factor(n_back) * as.factor(ttc_criticality) * as.factor(time_windows) +
- (as.factor(time_windows) | ppid),
- prior = c(prior(normal(0, 5), class = "Intercept"),
- prior(normal(0, 5), class = "b"),
- prior(cauchy(0, 2), class = sd),
- prior(lkj(2), class = cor)),
- iter = 5000, warmup = 2000, chains = 2, cores = 2, seed = 13, file = here::here("Models/mod_8_sd_pitch"))
- # model summaries
- print(summary(mod_8_sd_pitch), digits = 5)
- describe_posterior(mod_8_sd_pitch)
- # calculating the marginal effect of takeover window for mean pupil diameter (i.e., the contrast)
- mod_8_sd_pitch %>%
- emmeans(~ n_back * time_windows,
- at = list(ttc_criticality = 0),
- epred = TRUE, re_formula = NA) %>%
- contrast(method = "revpairwise") %>%
- gather_emmeans_draws() %>%
- mean_hdi() %>%
- View()
- predicted_sd_pitch <- mod_8_sd_pitch %>%
- epred_draws(newdata = expand_grid(ttc_criticality = c(0, 3, 5),
- time_windows = c("pre-RTI", "transition", "post-transition"),
- n_back = c(FALSE, TRUE)),
- re_formula = NA)
- predicted_sd_pitch %>% mean_hdi() %>% View()
- ```
- ## FIGURE 14
- # plotting gaze dispersion for time window
- ```{r}
- # labels - n-back
- n.labs <- c("No N-back", "N-back")
- names(n.labs) <- c("FALSE", "TRUE")
- # pre-RTI
- fig14a <- ggplot(takeover_manual_automation_pre %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::filter(timecourse <= 10), mapping = aes(x = gaze_direction_x * 180 / pi, y = gaze_direction_y * 180 / pi)) +
- geom_density_2d(aes(col = ..level..), alpha = .5, binwidth = .00003) +
- facet_grid(ttc_criticality ~ n_back, labeller = labeller(n_back = n.labs, ttc_criticality = ttc_labs)) +
- scale_colour_viridis_c(option = "C") +
- annotate( # dashboard area
- 'rect',
- xmin = -10,
- xmax = 25,
- ymin = -30,
- ymax = -15,
- alpha = 0,
- size = .5,
- col = viridis(2)[2]
- ) +
- xlim(-50, 50) +
- ylim(-40, 30) +
- ggtitle("A: Pre-RTI") +
- xlab("Yaw (°)") +
- ylab("Pitch (°)") +
- theme_bw() +
- theme(legend.position = "none", axis.title.x = element_text(size = 11), axis.text.x = element_text(size = 11), axis.title.y = element_text(size = 11), axis.text.y = element_text(size = 11), title = element_text(size = 10), legend.title = element_text(size = 12), legend.text = element_text(size = 12), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 10, face = "bold"), strip.text = element_text(face = "bold", size = 6))
- # transition of control
- fig14b <- ggplot(takeover_manual_automation_pre %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::filter(timecourse > 10, timecourse <= (20 + median(takeover_times$r_time))), mapping = aes(x = gaze_direction_x * 180 / pi, y = gaze_direction_y * 180 / pi)) +
- geom_density_2d(aes(col = ..level..), alpha = .5, binwidth = .00003) +
- facet_grid(ttc_criticality ~ n_back, labeller = labeller(n_back = n.labs, ttc_criticality = ttc_labs)) +
- scale_colour_viridis_c(option = "C") +
- annotate( # dashboard area
- 'rect',
- xmin = -10,
- xmax = 25,
- ymin = -30,
- ymax = -15,
- alpha = 0,
- size = .5,
- col = viridis(2)[2]
- ) +
- xlim(-50, 50) +
- ylim(-40, 30) +
- ggtitle("B: Transition of control") +
- xlab("Yaw (°)") +
- ylab("Pitch (°)") +
- theme_bw() +
- theme(legend.position = "none", axis.title.x = element_text(size = 11), axis.text.x = element_text(size = 11), axis.title.y = element_text(size = 11), axis.text.y = element_text(size = 11), title = element_text(size = 10), legend.title = element_text(size = 12), legend.text = element_text(size = 12), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 10, face = "bold"), strip.text = element_text(face = "bold", size = 6))
- # post-transition of control
- fig14c <- ggplot(takeover_manual_automation_pre %>%
- dplyr::mutate(ttc_criticality = if_else(is.na(ttc_criticality), 0, ttc_criticality)) %>%
- dplyr::filter(timecourse > (20 + median(takeover_times$r_time))), mapping = aes(x = gaze_direction_x * 180 / pi, y = gaze_direction_y * 180 / pi)) +
- geom_density_2d(aes(col = ..level..), alpha = .5, binwidth = .00003) +
- facet_grid(ttc_criticality ~ n_back, labeller = labeller(n_back = n.labs, ttc_criticality = ttc_labs)) +
- scale_colour_viridis_c(option = "C") +
- annotate( # dashboard area
- 'rect',
- xmin = -10,
- xmax = 25,
- ymin = -30,
- ymax = -15,
- alpha = 0,
- size = .5,
- col = viridis(2)[2]
- ) +
- xlim(-50, 50) +
- ylim(-40, 30) +
- ggtitle("C: Post-transition") +
- xlab("Yaw (°)") +
- ylab("Pitch (°)") +
- theme_bw() +
- theme(legend.position = "none", axis.title.x = element_text(size = 11), axis.text.x = element_text(size = 11), axis.title.y = element_text(size = 11), axis.text.y = element_text(size = 11), title = element_text(size = 10), legend.title = element_text(size = 12), legend.text = element_text(size = 12), legend.key = element_blank(), legend.key.width = unit(0.3, 'cm'), legend.key.size = unit(0.1, 'cm'), plot.title = element_text(size = 10, face = "bold"), strip.text = element_text(face = "bold", size = 6))
- fig14 <- fig14a / fig14b + fig14c + plot_layout(axes = "collect")
- # plot saving
- ggsave(here::here("Plots/fig14.tiff"), plot = fig14, width = 10, height = 22, units = 'cm', dpi = 300, type = 'cairo')
- ```
2_pupils_modelling.Rmd, no license · at the source
Overview
- School of Psychology, University of Leeds, Leeds, United Kingdom
- Institute for Transport Studies, University of Leeds, Leeds, United Kingdom
- Toyota Motor Europe, Brussels, Belgium
- Seeing Machines, Canberra, Australia
- VEDECOM Institute, Versailles, France
Abstract
Arousal plays a vital role in facilitating the human ability to respond flexibly in a goal-directed manner, and pupillometry offers a non-invasive window into arousal-related neural processes given the close relationship between pupil size and Locus Coeruleus-Norepinephrine
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 9 matches between paragraphs and lines of code.
OSF vmxj3
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
3 files
- Code/
1_pupils_cleaning.Rmd , R, 291 lines, 1 match - Code/
2_pupils_modelling.Rmd , R, 1,504 lines, 6 matches - Code/
pupils_entropy_subjectiv , R, 369 lines, 2 matchese.Rmd
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 3 scripts, each with its path and the digest of its content;
- 9 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data Availability
Data, analysis code, and models can be found in the following link: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 12 authors, 11 MeSH terms, 1 funder, 96 references.
Cite
This paper
Goodridge, C. M., Gonçalves, R. C., Arabian, A., Horrobin, A., Solernou, A., Lee, Y. T., Bruneau, A., Kuo, J., Lenné, M. G., Merlhiot, G., Lee, Y. M., & Merat, N. (2026). Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load. PloS one, 21(8), e0355165. https://
BibTeX
@article{goodridge2026pu
author = {Goodridge, Courtney M. and Gonçalves, Rafael C. and Arabian, Ali and Horrobin, Anthony and Solernou, Albert and Lee, Yee Thung and Bruneau, Audrey and Kuo, Jonny and Lenné, Michael G. and Merlhiot, Gaëtan and Lee, Yee Mun and Merat, Natasha},
title = {{Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load}},
journal = {PloS one},
year = {2026},
month = aug,
volume = {21},
number = {8},
pages = {e0355165},
publisher = {PLOS},
issn = {1932-6203},
doi = {10.1371/
url = {https://
pmid = {42579679},
pmcid = {PMC13460587}
}
RIS
TY - JOUR
AU - Goodridge, Courtney M.
AU - Gonçalves, Rafael C.
AU - Arabian, Ali
AU - Horrobin, Anthony
AU - Solernou, Albert
AU - Lee, Yee Thung
AU - Bruneau, Audrey
AU - Kuo, Jonny
AU - Lenné, Michael G.
AU - Merlhiot, Gaëtan
AU - Lee, Yee Mun
AU - Merat, Natasha
TI - Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load
T2 - PloS one
J2 - PLoS One
PY - 2026
DA - 2026/
VL - 21
IS - 8
SP - e0355165
SN - 1932-6203
PB - PLOS
DO - 10.1371/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1371/
"type": "article-journal",
"title": "Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load",
"container-title": "PloS one",
"author": [
{
"family": "Goodridge",
"given": "Courtney M."
},
{
"family": "Gonçalves",
"given": "Rafael C."
},
{
"family": "Arabian",
"given": "Ali"
},
{
"family": "Horrobin",
"given": "Anthony"
},
{
"family": "Solernou",
"given": "Albert"
},
{
"family": "Lee",
"given": "Yee Thung"
},
{
"family": "Bruneau",
"given": "Audrey"
},
{
"family": "Kuo",
"given": "Jonny"
},
{
"family": "Lenné",
"given": "Michael G."
},
{
"family": "Merlhiot",
"given": "Gaëtan"
},
{
"family": "Lee",
"given": "Yee Mun"
},
{
"family": "Merat",
"given": "Natasha"
}
],
"container-title-short":
"volume": "21",
"issue": "8",
"page": "e0355165",
"DOI": "10.1371/
"PMID": "42579679",
"PMCID": "PMC13460587",
"ISSN": "1932-6203",
"publisher": "PLOS",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
11
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.3758/s13428-023-02098-1 [code]
- From pre-processing to advanced dynamic modeling of pupil data.Journal: Behavior research methodsIn common: 20 references
- [2] doi:10.1038/s41598-024-78772-x [code]
- Pupillary manifolds: uncovering the latent geometrical structures behind phasic changes in pupil size.Journal: Scientific reportsIn common: BayesFactor, emmeans, lmerTest, 6 other tools, cognitive, 6 references
- [3] doi:10.1038/s41598-024-78791-8 [code]
- Task-irrelevant stimuli reliably boost phasic pupil-linked arousal but do not affect decision formation.Journal: Scientific reportsIn common: cognitive, 15 references
- [4] doi:10.1038/s41398-026-03948-0 [code]
- Locus coeruleus modulation of neurophysiological sensory selectivity differs in autism and other mental health conditions.Journal: Translational psychiatryIn common: easystats, emmeans, lmerTest, 5 other tools, 7 references
- [5] doi:10.3758/s13415-023-01096-2 [code]
- Testing locus coeruleus-norepinephrine
accounts of working memory, attention control, and fluid intelligence. Journal: Cognitive, affective & behavioral neuroscienceIn common: lmerTest, cowplot, data.table, 2 other tools, cognitive, 10 references - [6] doi:10.7554/elife.109678 [code]
- Strong mnemonic prediction errors increase cognitive control, attention, and arousal.Journal: eLifeIn common: brms, mgcv, easystats, 6 other tools, cognitive, 4 references
- [7] doi:10.1038/s41562-026-02533-1 [code]
- Fluctuations in arousal reflect latent state transitions that facilitate behavioural optimization.Journal: Nature human behaviourIn common: cognitive, 13 references
- [8] doi:10.1186/s13229-025-00678-w [code]
- Locus coeruleus tonic upregulation increases selectivity to inconspicuous auditory information in autistic compared to non-autistic individuals: a combined pupillometry and electroencephalography study.Journal: Molecular autismIn common: brms, easystats, emmeans, 5 other tools, cognitive, 5 references
- [9] doi:10.3758/s13428-025-02755-7 [code]
- A novel method for modeling tonic and phasic pupil dynamics in humans.Journal: Behavior research methodsIn common: Stan, patchwork, tidyverse, 10 references
- [10] doi:10.1186/s11689-025-09651-3 [code]
- The association between infant EEG aperiodic exponent and the trajectory of restricted and repetitive behaviors for toddlers with and without autism.Journal: Journal of neurodevelopmental disordersIn common: brms, Stan, mgcv, 9 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 3 scripts, and 9 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:5f2868a4900e67e6…
Add the badge to the code's README
If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.
Markdown
<!-- oscr-badge:start -->
[](https://openscicode.org/badge/doi/10.1371/journal.pone.0355165)
<!-- oscr-badge:end -->reStructuredText
.. oscr-badge:start
.. image:: https://openscicode.org/badge/doi/10.1371/journal.pone.0355165.svg
:target: https://openscicode.org/badge/doi/10.1371/journal.pone.0355165
:alt: OSCR
.. oscr-badge:endHTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1371/journal.pone.0355165"><img src="https://openscicode.org/badge/doi/10.1371/journal.pone.0355165.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.
Report a mismatch between the code and the paper · Report a code error (signed in)
Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.
Report a reproduction failure (signed in)
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
