Data Cleaning


# Load data
load(file = "./data/Single_Trial_Data.rda")


# Create variables gng_accuracy (1 for hit, 0 for FA) and accuracy_prev_trial (use unfiltered data to make sure previous trial in table = previous trial in task)
single_trial_data <- single_trial_data %>%
  dplyr::mutate(
    gng_accuracy = ifelse(gng_response_type == "FH" | gng_response_type == "SH", 1,
      ifelse(gng_response_type == "FA", 0, NA)),
    accuracy_prev_trial = ifelse(lag(gng_response_type == "FH" | gng_response_type == "SH") == TRUE, "correct",
      ifelse(lag(gng_response_type == "FA") == TRUE, "incorrect", NA))) 


# For each first trial in a block, accuracy_prev_trial cannot be determined -> set these values to NA
single_trial_data[single_trial_data$trial == 1 |
                  single_trial_data$trial == 29  | single_trial_data$trial == 101|
                  single_trial_data$trial == 173 | single_trial_data$trial == 201| 
                  single_trial_data$trial == 273 | single_trial_data$trial == 345|
                  single_trial_data$trial == 373 | single_trial_data$trial == 445, "accuracy_prev_trial"] <- NA


# For go/no-go RT analysis, exclude inhibited gng responses, missing gng responses, gng responses with wrong key, and RT outliers
single_trial_data_gng_rt <- single_trial_data %>%
  dplyr::filter(
    gng_response_type != "IR" &
      gng_response_type != "miss" &
      gng_response_type != "wrong_key" &
      (is.na(gng_rt_invalid)  | gng_rt_invalid  == FALSE) &
      (is.na(word_rt_outlier) | word_rt_outlier == FALSE)
  ) # (11028 of 15480 trials left)


# Make categorical variables factors
single_trial_data_gng_rt$gng_response_type   <- factor(single_trial_data_gng_rt$gng_response_type, levels = c("SH", "FH", "FA"))
single_trial_data_gng_rt$participant_id      <- factor(single_trial_data_gng_rt$participant_id)
single_trial_data_gng_rt$accuracy_prev_trial <- factor(single_trial_data_gng_rt$accuracy_prev_trial)

Trials were excluded from all analyses if RT in the go/no-go task was shorter than 100 ms or longer than 700 ms, or if the word categorization RT was more than three median absolute deviations (Leys et al., 2013) above or below a participant’s median RT computed per condition. We further discarded trials in which responses in the go/no-go task were missing or performed with one of the word categorization keys. In the analysis of RT in the go/no-go task, trials with correctly inhibited responses were not considered. For details on the percentage of excluded trials, please see section “Data Cleaning” in the tab “Word Categorization Task”.


Descriptive Statistics


Means and CIs

This table corresponds to Table S2 in the supplemental material.

# Calculate descriptive statistics for RT per condition
descriptive_statistics_rt <- summarySEwithinO(
  data          = single_trial_data_gng_rt,
  measurevar    = "gng_rt",
  withinvars    = "gng_response_type",
  idvar         = "participant_id",
  conf.interval = .95
)


# Prepare calculation of descriptive statistics for proportion of response types (based on all trials)
descriptive_statistics_proportion <- single_trial_data %>%
  dplyr::group_by(participant_id) %>%
  dplyr::summarize(
    gng_SH         = sum(gng_response_type == "SH")        / length(participant_id) * 100,
    gng_FH         = sum(gng_response_type == "FH")        / length(participant_id) * 100,
    gng_FA         = sum(gng_response_type == "FA")        / length(participant_id) * 100,
    gng_IR         = sum(gng_response_type == "IR")        / length(participant_id) * 100,
    gng_misses     = sum(gng_response_type == "miss")      / length(participant_id) * 100,
    gng_wrong_key  = sum(gng_response_type == "wrong_key") / length(participant_id) * 100,
  ) %>%
  dplyr::ungroup()


# Calculate descriptive statistics for proportion (exclude first column containing participant ID)
descriptive_statistics_proportion <- as.data.frame(t(stat.desc(descriptive_statistics_proportion[, -1]))) %>%
  # Select relevant columns
  dplyr::select(c("mean", "CI.mean.0.95")) %>%
  # Create response type column for subsequent merging
  dplyr::mutate(
    gng_response_type = c("SH", "FH", "FA", "IR", "Missing response", "Wrong key response")
  )


# Make a nice merged table for display
descriptive_statistics <- full_join(descriptive_statistics_rt, descriptive_statistics_proportion,
  by = "gng_response_type"
) %>%
  # Format confidence interval column
  dplyr::mutate(
    ci_rt         = paste0("[", round(gng_rt - ci, digits = 0),
                           ", ", round(gng_rt + ci, digits = 0), "]"),
    ci_proportion = paste0("[", round(mean   - CI.mean.0.95, digits = 2),
                           ", ", round(mean   + CI.mean.0.95, digits = 2), "]")
  ) %>%
  # Round RT means to zero decimals
  dplyr::mutate_at("gng_rt", round, digits = 0) %>%  
  # Select columns to be displayed
  dplyr::select(c("gng_response_type", "gng_rt", "ci_rt", "mean", "ci_proportion"))


# Display descriptive statistics for RT and proportion (and rearrange order of rows)
my_table_template(descriptive_statistics[c(3, 2, 1, 4, 5, 6), ],
  caption = "Go/No-Go Task Behavioral Performance",
  col_names = c("Response type", "M", "95% CI", "M", "95% CI"),
  header_above_config = c(" " = 1, "RT (ms)" = 2, "Proportion (%)" = 2),
  footnote_config = c(general = "RT confidence intervals are adjusted for within-participant designs as described by Morey (2008).")
)
Go/No-Go Task Behavioral Performance
RT (ms)
Proportion (%)
Response type M 95% CI M 95% CI
SH 342 [340, 344] 40.39 [37.42, 43.36]
FH 270 [269, 272] 25.92 [23.11, 28.73]
FA 260 [257, 264] 10.91 [8.82, 13]
IR NA [NA, NA] 21.59 [19.5, 23.68]
Missing response NA [NA, NA] 1.01 [0.43, 1.59]
Wrong key response NA [NA, NA] 0.18 [0.11, 0.25]
Note:
RT confidence intervals are adjusted for within-participant designs as described by Morey (2008).



This table corresponds to Table S3 in the supplemental material.

# Calculate descriptive statistics for post-correct and post-error accuracy
# Note: Only SH/FH/FA trials with previous trial also SH/FH/FA are included
post_correct_post_error_accuracy <- summarySEwithinO(
  data          = single_trial_data_gng_rt[!is.na(single_trial_data_gng_rt$gng_accuracy)  & 
                                           !is.na(single_trial_data_gng_rt$accuracy_prev_trial), ],
  measurevar    = "gng_accuracy",
  withinvars    = "accuracy_prev_trial",
  idvar         = "participant_id",
  conf.interval = .95
) %>%
  # Multiply numeric values by 100 to obtain values in percent
  dplyr::mutate_if(is.numeric, list(~. * 100)) %>%
  # Format confidence interval column and add column with labels
  dplyr::mutate(
    ci   = paste0("[", round(gng_accuracy - ci, digits = 2),
                 ", ", round(gng_accuracy + ci, digits = 2), "]"),
    type = c("Post-correct accuracy", "Post-error accuracy")) %>%  
  # Select columns to be displayed
  dplyr::select(c("type", "gng_accuracy", "ci"))


# Display descriptive statistics for post-correct and post-error accuracy 
my_table_template(post_correct_post_error_accuracy,
  caption = "Post-Correct Accuracy and Post-Error Accuracy",
  col_names = c("Type of accuracy", "M", "95% CI"),
  footnote_config = c(general = "Confidence intervals are adjusted for within-participant designs as described by Morey (2008).")
)
Post-Correct Accuracy and Post-Error Accuracy
Type of accuracy M 95% CI
Post-correct accuracy 84.59 [83.43, 85.74]
Post-error accuracy 86.94 [84.29, 89.6]
Note:
Confidence intervals are adjusted for within-participant designs as described by Morey (2008).



Number of false alarms

The paradigm was effective in fostering a sufficient number of false alarms.

# Calculate number of false alarms (based on all trials)
FAs <- single_trial_data %>%
  dplyr::filter(gng_response_type == "FA") %>%
  dplyr::group_by(participant_id) %>%
  dplyr::summarize(
    number_FAs = n()
  ) %>%
  dplyr::ungroup()


FA_mean <- round(mean(FAs$number_FAs), digits = 2)
FA_sd   <- round(sd(FAs$number_FAs),   digits = 2)

Number of false alarms: M = 56.3, SD = 28.91


(G)LMM Analyses


To evaluate RT differences between response types in the go/no-go task, we fitted a LMM on the inverse-transformed RT with response type (slow hit, fast hit, false alarm) as fixed effect and by-participant random effects. LMM analysis was conducted on inverse-transformed RT values to meet the assumption of normally distributed residuals. For details on contrast coding and the procedure used to specify the random-effects structure, please see section “(G)LMM analyses” in the tab “Word Categorization Task”.

RT

Run first model

As for a factor with n levels, only n − 1 factor levels can be compared in a model, we computed two models to allow all pairwise comparisons of response types to be tested. The first model compared fast hits separately with both other response types. The second model compared false alarms with slow hits and it also compared fast hits with false alarms. The latter contrast was mathematically equivalent to one of the contrasts in the first model. The model containing the maximal random-effects structure was used.

This table corresponds to the results section “Behavioral Performance in the Go/No-Go Task” in the manuscript and Table S4 in the supplemental material.

# Define contrasts (sliding difference contrasts)
contrasts(single_trial_data_gng_rt$gng_response_type) <- contr.sdif(3)


# Add contrasts as numerical covariates via model matrix
model_matrix <- model.matrix(~gng_response_type, single_trial_data_gng_rt)


# Attach the model matrix (3 columns) to the dataframe
single_trial_data_gng_rt[, (ncol(single_trial_data_gng_rt) + 1):(ncol(single_trial_data_gng_rt) + 3)] <- model_matrix


# Assign descriptive names to the contrasts
names(single_trial_data_gng_rt)[(ncol(single_trial_data_gng_rt) - 2):ncol(single_trial_data_gng_rt)] <- c(
  "Grand Mean", "FH_SH", "FA_FH"
)


# Run model with maximal random-effects structure
LMM_gng_rt_1 <- lmer(gng_rt_inverse ~ gng_response_type +
  (1 + FH_SH + FA_FH | participant_id),
data = single_trial_data_gng_rt,
REML = TRUE,
control = lmerControl(optimizer = "bobyqa")
)


# Check model output
# summary(LMM_gng_rt_1) # Model does converge, no singular fit


# Re-check PCA of random-effects variance-covariance estimates
# summary(rePCA(LMM_gng_rt_1)) # All terms explain variance


# Prepare labels for LMM table
labels <- c(
  "(Intercept)"          = "Intercept",
  "gng_response_type2-1" = "FH - SH",
  "gng_response_type3-2" = "FA - FH"
)


# Display results (fixed effects)
tab_model(LMM_gng_rt_1,
  dv.labels = "RT", pred.labels = labels, show.stat = TRUE, show.icc = FALSE, show.r2 = FALSE,
  show.re.var = FALSE, show.ngroups = FALSE, string.est = "b", string.stat = "t value",
  string.ci = "95 % CI", string.p = "p value",  p.val = "satterthwaite"
)


# Display random effects
print("Random effects:")
print(VarCorr(LMM_gng_rt_1), digits = 2, comp = "Std.Dev.")
  RT
Predictors b 95 % CI t value p value
Intercept -3.60 -3.74 – -3.47 -52.10 <0.001
FH - SH -0.79 -0.87 – -0.71 -18.45 <0.001
FA - FH -0.08 -0.18 – 0.02 -1.48 0.150
Observations 11028
[1] "Random effects:"
 Groups         Name        Std.Dev. Corr       
 participant_id (Intercept) 0.38                
                FH_SH       0.22      0.47      
                FA_FH       0.26     -0.04 -0.56
 Residual                   0.57                

RTs were longer for slow hits compared to fast hits. RTs for fast hits and false alarms did not differ significantly.


Run second model

This table corresponds to the results section “Behavioral Performance in the Go/No-Go Task” in the manuscript and Table S4 in the supplemental material.

# Reorder factor levels
single_trial_data_gng_rt$gng_response_type <- factor(single_trial_data_gng_rt$gng_response_type, levels = c("SH", "FA", "FH"))


# Define contrasts (sliding difference contrasts)
contrasts(single_trial_data_gng_rt$gng_response_type) <- contr.sdif(3)


# Add contrasts as numerical covariates via model matrix
model_matrix <- model.matrix(~gng_response_type, single_trial_data_gng_rt)


# Attach the model matrix (3 columns) to the dataframe
single_trial_data_gng_rt[, (ncol(single_trial_data_gng_rt) + 1):(ncol(single_trial_data_gng_rt) + 3)] <- model_matrix


# Assign descriptive names to the contrasts
names(single_trial_data_gng_rt)[(ncol(single_trial_data_gng_rt) - 2):ncol(single_trial_data_gng_rt)] <- c(
  "Grand Mean", "FA_SH", "FH_FA"
)


# Run model with maximal random-effects structure
LMM_gng_rt_2 <- lmer(gng_rt_inverse ~ gng_response_type +
  (1 + FA_SH + FH_FA | participant_id),
data = single_trial_data_gng_rt,
REML = TRUE,
control = lmerControl(optimizer = "bobyqa")
)


# Check model output
# summary(LMM_gng_rt_2) # Model does converge, no singular fit


# Re-check PCA of random-effects variance-covariance estimates
# summary(rePCA(LMM_gng_rt_2)) # All terms explain variance


# Prepare labels for LMM table
labels <- c(
  "(Intercept)"          = "Intercept",
  "gng_response_type2-1" = "FA - SH",
  "gng_response_type3-2" = "FH - FA"
)


# Display results (fixed effects)
tab_model(LMM_gng_rt_2,
  dv.labels = "RT", pred.labels = labels, show.stat = TRUE, show.icc = FALSE, show.r2 = FALSE,
  show.re.var = FALSE, show.ngroups = FALSE, string.est = "b", string.stat = "t value",
  string.ci = "95 % CI", string.p = "p value",  p.val = "satterthwaite"
)


# Display random effects
print("Random effects:")
print(VarCorr(LMM_gng_rt_2), digits = 2, comp = "Std.Dev.")
  RT
Predictors b 95 % CI t value p value
Intercept -3.60 -3.74 – -3.47 -52.10 <0.001
FA - SH -0.87 -0.96 – -0.78 -18.84 <0.001
FH - FA 0.08 -0.02 – 0.18 1.48 0.150
Observations 11028
[1] "Random effects:"
 Groups         Name        Std.Dev. Corr       
 participant_id (Intercept) 0.38                
                FA_SH       0.23      0.41      
                FH_FA       0.26      0.04 -0.59
 Residual                   0.57                

RTs were longer for slow hits compared to false alarms.


Accuracy

To examine whether there was a post-error increase in accuracy, we fitted a binomial GLMM on response accuracy (false alarm vs. hit) with accuracy in the preceding go/no-go trial as fixed effect and by-participant random effects.
This table corresponds to the results section “Behavioral Performance in the Go/No-Go Task” in the manuscript and Table S5 in the supplemental material.

# Define contrasts (sliding difference contrasts)
contrasts(single_trial_data_gng_rt$accuracy_prev_trial) <- contr.sdif(2)


# Run model with maximal random-effects structure 
# Note: Only SH/FH/FA trials with previous trial also SH/FH/FA are included
GLMM_gng_accuracy <- glmer(gng_accuracy ~ accuracy_prev_trial +
  (1 + accuracy_prev_trial | participant_id),
data = single_trial_data_gng_rt,
family = binomial,
control = glmerControl(optimizer = "bobyqa")
)


# Check model output
# summary(GLMM_gng_accuracy) # Model does converge, no singular fit


# Re-check PCA of random-effects variance-covariance estimates
# summary(rePCA(GLMM_gng_accuracy)) # All terms explain variance


# Display results (fixed effects)
tab_model(GLMM_gng_accuracy,
  dv.labels = "Accuracy in the go/no-go task", show.stat = TRUE, show.icc = FALSE, show.r2 = FALSE,
  pred.labels = c("(Intercept)" = "Intercept", "accuracy_prev_trial2-1" = "Accuracy on trial N – 1 (false alarm – hit)"),
  show.re.var = FALSE, show.ngroups = FALSE, string.est = "b", string.stat = "z value",
  string.ci = "95 % CI", string.p = "p value"  
)


# Display random effects
print("Random effects:")
print(VarCorr(GLMM_gng_accuracy), digits = 2, comp = "Std.Dev.")
  Accuracy in the go/no-go task
Predictors b 95 % CI z value p value
Intercept 8.56 6.41 – 11.44 14.53 <0.001
Accuracy on trial N – 1
(false alarm – hit)
1.90 1.38 – 2.63 3.91 <0.001
Observations 8421
[1] "Random effects:"
 Groups         Name                   Std.Dev. Corr
 participant_id (Intercept)            0.69         
                accuracy_prev_trial2-1 0.34     0.91



Results of the GLMM revealed a significant main effect of the accuracy in the preceding go/no-go trial, indicating that response accuracy was higher following false alarms than following hits, suggesting that there was a post-error increase in accuracy.


References


Leys, C., Ley, C., Klein, O., Bernard, P., & Licata, L. (2013). Detecting outliers: Do not use standard deviation around the mean, use absolute deviation around the median. Journal of Experimental Social Psychology, 49(4), 764-766. https://doi.org/10.1016/j.jesp.2013.03.013

Morey, R. (2008). Confidence intervals from normalized data: A correction to Cousineau (2005). Tutorials in Quantitative Methods for Psychology, 4(2), 61-64. https://doi.org/10.20982/tqmp.04.2.p061


Session Info


sessionInfo()
R version 3.6.1 (2019-07-05)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19042)

Matrix products: default

locale:
[1] LC_COLLATE=German_Germany.1252  LC_CTYPE=German_Germany.1252   
[3] LC_MONETARY=German_Germany.1252 LC_NUMERIC=C                   
[5] LC_TIME=German_Germany.1252    

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] plyr_1.8.6       kableExtra_1.1.0 sjPlot_2.8.4     MASS_7.3-51.4   
 [5] lmerTest_3.1-0   lme4_1.1-21      Matrix_1.2-17    pastecs_1.3.21  
 [9] knitr_1.29       dplyr_0.8.3     

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.5          mvtnorm_1.0-11      lattice_0.20-38    
 [4] tidyr_1.0.0         assertthat_0.2.1    digest_0.6.22      
 [7] R6_2.4.1            backports_1.1.5     evaluate_0.14      
[10] highr_0.8           httr_1.4.1          ggplot2_3.3.2      
[13] pillar_1.4.6        rlang_0.4.8         minqa_1.2.4        
[16] rstudioapi_0.10     performance_0.4.3   nloptr_1.2.1       
[19] effectsize_0.3.2    rmarkdown_1.16      ggeffects_0.15.1   
[22] splines_3.6.1       webshot_0.5.1       readr_1.3.1        
[25] stringr_1.4.0       munsell_0.5.0       broom_0.5.2        
[28] compiler_3.6.1      numDeriv_2016.8-1.1 modelr_0.1.5       
[31] xfun_0.16           pkgconfig_2.0.3     parameters_0.8.2   
[34] htmltools_0.4.0     insight_0.9.0       tidyselect_0.2.5   
[37] tibble_3.0.4        viridisLite_0.3.0   crayon_1.3.4       
[40] sjmisc_2.8.2        grid_3.6.1          nlme_3.1-140       
[43] xtable_1.8-4        gtable_0.3.0        lifecycle_0.2.0    
[46] magrittr_1.5        bayestestR_0.7.2    scales_1.1.0       
[49] estimability_1.3    stringi_1.4.3       renv_0.12.0        
[52] xml2_1.2.2          ellipsis_0.3.0      generics_0.0.2     
[55] vctrs_0.3.4         boot_1.3-22         sjlabelled_1.1.3   
[58] tools_3.6.1         forcats_0.4.0       glue_1.3.1         
[61] purrr_0.3.3         sjstats_0.17.8      hms_0.5.2          
[64] emmeans_1.4.2       yaml_2.2.0          colorspace_1.4-1   
[67] rvest_0.3.6         haven_2.2.0