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–260 · score 0.67 · 0.05–4 Hz, linearly interpolated, Hanning, butterworth, tagged, tonic
- [2] § 2. Method › 2.3 Design and procedure ↔ Code/pupils_entropy_subjective.Rmd, lines 311–348 · score 0.66 · NASA TLX, subjective workload, critical event, vehicle
- [3] § 2. Method › 2.4 Data preparation and statistical modelling › 2.4.1 Pupillometry pre-processing. ↔ Code/2_pupils_modelling.Rmd, lines 646–690 · score 0.64 · pseudo change, cubic, subtracted, evoked, fitted, dilation
- [4] § 2. Method › 2.1 Participants ↔ Code/2_pupils_modelling.Rmd, lines 145–174 · score 0.64 · gender split, annually, miles, UK, license, age
- [5] § 3. Results › 3.6 Post-hoc analysis: driver gaze during transitions of control ↔ Code/2_pupils_modelling.Rmd, lines 1324–1364 · score 0.60 · pitch angle, post hoc, gaze distribution, variables, modelled, windows
- [6] § 2. Method › 2.2 Apparatus and materials ↔ Code/pupils_entropy_subjective.Rmd, lines 311–348 · score 0.58 · NASA TLX, subjective workload, gaze, pupil
- [7] § 3. Results › 3.6 Post-hoc analysis: driver gaze during transitions of control ↔ Code/2_pupils_modelling.Rmd, lines 1413–1504 · score 0.53 · dashboard area, post transition, pre RTI, gaze
- [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–527 · score 0.51 · credible intervals, pupil diameter increased, lead vehicle, CI, predicted
- [9] § 3. Results › 3.6 Post-hoc analysis: driver gaze during transitions of control ↔ Code/2_pupils_modelling.Rmd, lines 1324–1364 · score 0.50 · pitch angle, pre RTI, Modelling, Post, window, hoc
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: 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.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
- [2] doi:10.1038/s41467-026-73865-9 [code]
- Histamine shapes the neurocomputational dynamics of human learning.Journal: Nature communicationsIn common: BayesFactor, brms, easystats, 7 other tools, cognitive
- [3] doi:10.1016/j.celrep.2026.117505 [code]
- Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice.Journal: Cell reportsIn common: Stan, brms, mgcv, 7 other tools
- [4] doi:10.1016/j.isci.2026.116747 [code]
- Age and loneliness relate to reduced trust learning and alterations in amygdala function.Journal: iScienceIn common: Stan, brms, easystats, 7 other tools, cognitive
- [5] doi:10.1126/sciadv.adz6495
- Pupil-linked arousal heterogeneously modulates cell-type-specific sensory processing.Journal: Science advancesIn common: 10 references
- [6] doi:10.1523/eneuro.0076-26.2026 [code]
- Exogenously Driven Neural Reactivation of Spatially Matching Visual Working-Memory Contents.Journal: eNeuroIn common: BayesFactor, brms, emmeans, 5 other tools, cognitive, 1 reference
- [7] doi:10.1038/s41467-026-74753-y [code]
- A human-specific microRNA controls the timing of excitatory synaptogenesis.Journal: Nature communicationsIn common: easystats, emmeans, lmerTest, 7 other tools
- [8] doi:10.1038/s44271-026-00431-w [code]
- Alpha power increases spontaneously during a neurofeedback session.Journal: Communications psychologyIn common: BayesFactor, Stan, brms, 5 other tools, cognitive
- [9] doi:10.1162/imag.a.1258 [code]
- Non-specific increase in alpha power during a neurofeedback session targeting its downregulation.Journal: Imaging neuroscience (Cambridge, Mass.)In common: BayesFactor, Stan, brms, 5 other tools
- [10] doi:10.1038/s41593-026-02363-4 [code]
- Cortical thickness changes precede high levels of amyloid by at least 7 years.Journal: Nature neuroscienceIn common: mgcv, easystats, lmerTest, 6 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 its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[.
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
