This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.
When you click the Knit button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
summary(cars)
## speed dist
## Min. : 4.0 Min. : 2.00
## 1st Qu.:12.0 1st Qu.: 26.00
## Median :15.0 Median : 36.00
## Mean :15.4 Mean : 42.98
## 3rd Qu.:19.0 3rd Qu.: 56.00
## Max. :25.0 Max. :120.00
You can also embed plots, for example:
echo = FALSE parameter was added to the
code chunk to prevent printing of the R code that generated the
plot.title: “PerfectArtDiff” author: “Jutta Stahl” date: “September - 2024” output: html_document: toc: yes code_folding: hide toc_float: yes toc_depth: 3 number_sections: yes style: null pdf_document: toc: yes toc_depth: ‘3’ warning: False —
library(dplyr)
library(tidyr)
library(lme4)
library(lmerTest)
library(ggplot2)
library(afex)
library(apa)
library(emmeans)
library(knitr)
library(kableExtra)
library(ggsci)
library(cowplot)
library(texreg)
library(ggeffects)
library(interactions)
library(lsmeans)
library(flextable)
library(sjPlot)
library(scales)
library(Hmisc)
library(corrplot)
library(performance)
library(see)
library(PerformanceAnalytics)
library(tseries)
library(forcats)
library(readxl)
library(stringr)
library(parameters)
library(see)
library(sjPlot)
library(interactions)
library(effectsize)
setwd(dir = "/Users/faezehsadipour/Desktop")
# Suppress summaries info
options(dplyr.summarise.inform = FALSE)
sessionInfo()
## R version 4.4.1 (2024-06-14)
## Platform: aarch64-apple-darwin20
## Running under: macOS Sonoma 14.0
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Asia/Tehran
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] effectsize_1.0.0 parameters_0.26.0
## [3] stringr_1.5.1 readxl_1.4.3
## [5] forcats_1.0.0 tseries_0.10-58
## [7] PerformanceAnalytics_2.0.4 xts_0.14.0
## [9] zoo_1.8-12 see_0.11.0
## [11] performance_0.14.0 corrplot_0.94
## [13] Hmisc_5.1-3 scales_1.3.0
## [15] sjPlot_2.8.17 flextable_0.9.6
## [17] lsmeans_2.30-0 interactions_1.2.0
## [19] ggeffects_1.7.1 texreg_1.39.4
## [21] cowplot_1.1.3 ggsci_3.2.0
## [23] kableExtra_1.4.0 knitr_1.46
## [25] emmeans_1.10.4 apa_0.3.4
## [27] afex_1.4-1 ggplot2_3.5.1
## [29] lmerTest_3.1-3 lme4_1.1-35.5
## [31] Matrix_1.7-0 tidyr_1.3.1
## [33] dplyr_1.1.4
##
## loaded via a namespace (and not attached):
## [1] rstudioapi_0.16.0 jsonlite_1.8.8 datawizard_1.1.0
## [4] magrittr_2.0.3 estimability_1.5.1 nloptr_2.1.1
## [7] rmarkdown_2.27 ragg_1.3.2 vctrs_0.6.5
## [10] minqa_1.2.8 askpass_1.2.0 base64enc_0.1-3
## [13] htmltools_0.5.8.1 curl_5.2.1 broom_1.0.5
## [16] cellranger_1.1.0 Formula_1.2-5 sjmisc_2.8.10
## [19] TTR_0.24.4 sass_0.4.9 parallelly_1.38.0
## [22] bslib_0.8.0 htmlwidgets_1.6.4 plyr_1.8.9
## [25] cachem_1.1.0 uuid_1.2-1 lifecycle_1.0.4
## [28] pkgconfig_2.0.3 sjlabelled_1.2.0 R6_2.5.1
## [31] fastmap_1.2.0 future_1.34.0 digest_0.6.36
## [34] numDeriv_2016.8-1.1 colorspace_2.1-0 furrr_0.3.1
## [37] textshaping_0.4.0 fansi_1.0.6 httr_1.4.7
## [40] abind_1.4-8 compiler_4.4.1 fontquiver_0.2.1
## [43] withr_3.0.2 pander_0.6.5 htmlTable_2.4.3
## [46] backports_1.4.1 carData_3.0-5 highr_0.10
## [49] broom.mixed_0.2.9.5 MASS_7.3-60.2 openssl_2.2.0
## [52] sjstats_0.19.0 tools_4.4.1 foreign_0.8-86
## [55] quantmod_0.4.26 zip_2.3.1 nnet_7.3-19
## [58] glue_1.7.0 quadprog_1.5-8 nlme_3.1-164
## [61] grid_4.4.1 checkmate_2.3.2 cluster_2.1.6
## [64] reshape2_1.4.4 generics_0.1.3 gtable_0.3.5
## [67] MBESS_4.9.3 data.table_1.15.4 xml2_1.3.6
## [70] car_3.1-3 utf8_1.2.4 pillar_1.9.0
## [73] splines_4.4.1 lattice_0.22-6 tidyselect_1.2.1
## [76] fontLiberation_0.1.0 fontBitstreamVera_0.1.1 gridExtra_2.3
## [79] svglite_2.1.3 xfun_0.52 stringi_1.8.3
## [82] yaml_2.3.8 boot_1.3-30 evaluate_1.0.3
## [85] codetools_0.2-20 officer_0.6.6 gdtools_0.4.0
## [88] tibble_3.2.1 cli_3.6.2 rpart_4.1.23
## [91] xtable_1.8-4 systemfonts_1.1.0 munsell_0.5.1
## [94] jquerylib_0.1.4 Rcpp_1.0.13 globals_0.16.3
## [97] coda_0.19-4.1 parallel_4.4.1 bayestestR_0.16.0
## [100] listenv_0.9.1 viridisLite_0.4.2 mvtnorm_1.3-1
## [103] insight_1.3.0 purrr_1.0.2 rlang_1.1.3
## [106] jtools_2.3.0
getwd()
## [1] "/Users/faezehsadipour/Desktop"
# Functions to plot the 2 and 3 way interactions with continous predictors
source("threeway_interactions.R")
## Customized functions
stderror <- function(x) sd(x)/sqrt(sum(!is.na(x)))
## Function to print lme result summary
tab_summary <- function(..., pred.labels, dv.labels, title = NULL) {
tab_model(..., pred.labels = pred.labels, dv.labels = dv.labels, title = title, p.val = "satterthwaite",
show.se = T, show.ci = 0.95, show.df =T, show.stat = T, show.obs = T, show.ngroups = T,
string.se = "SE", string.est = "Estimate", string.stat = "t", col.order = c("est", "std.error", "beta", "df.error", "stat", "p"))
}
#1. Define bad participants (OK)
bad_participants <- c(4, 5, 13, 14, 25, 29, 48, 49, 75, 90, 96, 98)
# 2. Load questionnaire data (OK)
data.items <- read.csv2("/Users/faezehsadipour/Desktop/SE8ART/results_survey.csv")
# Define path and get filenames
path <- "/Users/faezehsadipour/Desktop/SE8ART/BEHAVIOUR"
files <- list.files(path = path, pattern = "SE8_HT[0-9]+\\.beh$", full.names = TRUE)
# Extract the numeric part from each file name
file_numbers <- as.numeric(sub("SE8_HT(\\d+)\\.beh$", "\\1", basename(files)))
# Optional: Clean invalid file names (just in case)
valid_indices <- !is.na(file_numbers)
files <- files[valid_indices]
file_numbers <- file_numbers[valid_indices]
data.orig <- bind_rows(lapply(seq_along(files), function(i) {
file <- files[i]
df <- read.table(file, header = TRUE) # or use read.csv if that's more appropriate
df$ID <- file_numbers[i] # Add participant ID from filename
df <- df %>%
mutate(across(-ID, ~ as.numeric(.))) # Ensure numeric type, excluding ID
return(df)
}))
# 4. Extract participant numbers
file_numbers <- as.numeric(sub("SE8_HT(\\d+)\\.beh$", "\\1", basename(files)))
# 6. Read and combine all files
data.orig <- bind_rows(lapply(seq_along(files), function(i) {
data.orig <- data.orig %>%
mutate(across(-ID, ~ as.numeric(.)))
dplyr::last_dplyr_warnings()
data.orig <- data.orig %>%
mutate(across(where(~ is.character(.) && !cur_column() %in% c("stimulus", "position")),
~ as.numeric(.)))
sum(is.na(data.orig$RT))
cols_to_convert <- setdiff(names(data.orig), "ID")
data.orig[cols_to_convert] <- lapply(data.orig[cols_to_convert], function(x) as.numeric(x))
# Read one file
file_data <- read.csv(files[i], header = FALSE, sep = "\t")
# Clean spaces
file_data <- file_data %>% mutate_all(~ str_squish(.))
# Add participant ID
file_data$ID <- file_numbers[i]
return(file_data)
}))
new_headers <- c(
"block", "trial", "position", "stimulus", "ITI",
"force_1", "force_2", "force_3", "force_4", "force_5", "force_6", "force_7", "force_8",
"RT_1", "RT_2", "RT_3", "RT_4", "RT_5", "RT_6", "RT_7", "RT_8",
"TTP_1", "TTP_2", "TTP_3", "TTP_4", "TTP_5", "TTP_6", "TTP_7", "TTP_8",
"firstresponse", "accuracy", "dummy", "RT", "RF", "TTP",
"Det_force_1", "Det_force_2", "Det_force_3", "Det_force_4", "Det_force_5", "Det_force_6", "Det_force_7", "Det_force_8",
"Det_RT_1", "Det_RT_2", "Det_RT_3", "Det_RT_4", "Det_RT_5", "Det_RT_6", "Det_RT_7", "Det_RT_8",
"Det_TTP_1", "Det_TTP_2", "Det_TTP_3", "Det_TTP_4", "Det_TTP_5", "Det_TTP_6", "Det_TTP_7", "Det_TTP_8",
"det_accu", "Det_RT", "DET_RF", "DET_TTP",
"ID"
)
if (ncol(data.orig) == length(new_headers)) {
colnames(data.orig) <- new_headers
}
data.orig <- data.orig %>%
mutate(across(-ID, ~ as.numeric(.)))
length(unique(data.orig$ID))
## [1] 91
if (ncol(data.orig) == length(new_headers)) {
colnames(data.orig) <- new_headers
} else {
stop("❌ Number of columns does not match expected structure.")
}
# === 6. Keep only blocks 9 to 15
data.orig <- data.orig %>%
filter(block > 8 & block <= 15)
# Convert both ID columns to the same type
data.orig <- data.orig %>%
mutate(ID = as.integer(ID))
data.items <- data.items %>%
mutate(ID = as.integer(ID))
data.orig$ID <- as.integer(data.orig$ID)
data.items$ID <- as.integer(data.items$ID)
#adding data items to data orig
data.orig <- left_join(data.orig, data.items, by = "ID")
#data_erp
path_erpagg <- "/Users/faezehsadipour/Desktop/SE8ART/Peaks.csv"
data.erpagg <- read.csv(path_erpagg, header = FALSE, sep = ";") %>%
mutate_all(~ str_squish(.))
new_headers <- c("ID", "Ne_Pcorrect_Selfeval", "Ne_Perror_Selfeval",
"Pe_Perror_Selfeval", "Pe_Pcorrect_Selfeval")
if (ncol(data.erpagg) == length(new_headers)) {
colnames(data.erpagg) <- new_headers
}
data.orig$ID <- as.integer(data.orig$ID)
data.erpagg$ID <- as.integer(data.erpagg$ID)
# View the cleaned data
#adding data erpagg to data orig
data.orig <- left_join(data.orig, data.erpagg, by = "ID")
data.orig <- data.orig %>%
relocate(ID, .before = everything())
data.orig <- data.orig %>%
mutate(
accuStr = case_when(
accuracy == 1 ~ "correct",
accuracy == 2 ~ "error",
accuracy == 3 ~ "error_timeout",
accuracy == 4 ~ "timeout",
TRUE ~ NA_character_
)
)
data.orig <- data.orig %>%
arrange(ID, trial) %>% # ensure trial order
mutate(
Type_Nplus = lead(accuStr) # next trial's accuracy string
)
data.orig <- data.orig %>%
arrange(ID, block, trial) %>%
group_by(ID) %>%
mutate(
trial_index = row_number(),
Type_Nplus = lead(accuStr) # assign next trial's accuracy
) %>%
ungroup()
data.orig <- data.orig %>%
mutate(
# Binary flags for current trial
error_b = ifelse(accuStr == "error", 1, 0),
correct_b = ifelse(accuStr == "correct", 1, 0),
error_timeout_b = ifelse(accuStr == "error_timeout", 1, 0),
timeout_b = ifelse(accuStr == "timeout", 1, 0),
too_slow_b = ifelse(accuStr %in% c("error_timeout", "timeout"), 1, 0),
# Binary flags for next trial (n+1)
error_nplus = ifelse(Type_Nplus == "error", 1, 0),
correct_nplus = ifelse(Type_Nplus == "correct", 1, 0),
error_timeout_nplus = ifelse(Type_Nplus == "error_timeout", 1, 0),
timeout_nplus = ifelse(Type_Nplus == "timeout", 1, 0),
too_slow_nplus = ifelse(Type_Nplus %in% c("error_timeout", "timeout"), 1, 0)
)
# Clean data set: include only correct and error trials (exclude too slow & missing)
data.main.practice <- data.orig %>%
filter(accuStr %in% c("correct", "error")) %>%
mutate(
accuStr = factor(accuStr, levels = c("error", "correct")),
) %>%
ungroup()
data.main <- data.orig %>%
filter(!RT %in% c("999999")) %>% # Remove trials with invalid RTs
filter(accuStr %in% c("error", "correct")) %>% # Keep only trials with valid accuracy labels
mutate(accuStr = factor(accuStr, levels = c("error", "correct"))) %>% # Make a factor (categorical variable)
ungroup()
# Prepare for post-error analyses: exclude trials with no next RT
data.main.postbeh <- data.main.practice %>%
arrange(ID, trial) %>% # ensure trial order per subject
mutate(RT_trial_nplus = lead(RT)) %>%
filter(!RT_trial_nplus %in% c(999999)) %>% # 999999 is numeric, not string
mutate(
accuStr = factor(accuStr),
) %>%
ungroup()
questionnaire_worry <- c("msf.wX1.","msf.wX2.", "msf.wX3.", "msf.wX4.", "msf.wX5.", "msf.wX6.", "msf.wX7.", "msf.wX8.", "msf.wX9.", "msf.wX10.", "msf.wX11.", "msf.wX12.")
questionnaire_opt <- c("sop1.oX1.", "sop2.pX1.")
data.pers.cen <- data.items %>%
mutate(
worry = rowMeans(across(all_of(questionnaire_worry)), na.rm = TRUE),
opt = rowMeans(across(all_of(questionnaire_opt)), na.rm = TRUE),
worry_cen = worry - mean(worry, na.rm = TRUE),
opt_cen = opt - mean(opt, na.rm = TRUE)
)
data.pers.cen$ID <- as.character(data.pers.cen$ID)
data.main.postbeh$ID <- as.character(data.main.postbeh$ID)
data.main.postbeh <- data.main.postbeh %>%
left_join(data.pers.cen %>% select(ID, opt, opt_cen, worry, worry_cen), by = "ID")
data.main$ID <- as.character(data.main$ID)
data.pers.cen$ID <- as.character(data.pers.cen$ID)
data.main <- data.main %>%
left_join(data.pers.cen %>% select(ID, opt, opt_cen, worry, worry_cen), by = "ID")
#mean
colnames(data.items)
## [1] "ID" "age" "gender" "msf.wX1." "msf.wX2." "msf.wX3."
## [7] "msf.wX4." "msf.wX5." "msf.wX6." "msf.wX7." "msf.wX8." "msf.wX9."
## [13] "msf.wX10." "msf.wX11." "msf.wX12." "sop1.oX1." "sop2.pX1."
questionnaire_worry <- c("msf.wX1.","msf.wX2.", "msf.wX3.", "msf.wX4.", "msf.wX5.", "msf.wX6.", "msf.wX7.", "msf.wX8.", "msf.wX9.", "msf.wX10.", "msf.wX11.", "msf.wX12.")
questionnaire_opt <- c("sop1.oX1.", "sop2.pX1.")
data.pers.mean <- data.items %>%
mutate(
worry = rowMeans(across(all_of(questionnaire_worry)), na.rm = TRUE),
opt = rowMeans(across(all_of(questionnaire_opt)), na.rm = TRUE))
#Personality: only (non centered)
selected_items <- data.items %>%
select(ID, age, gender, )
selected_cen <- data.pers.mean %>%
select(worry, opt)
data.pers.all <- cbind(selected_items, selected_cen)
#SDT-parameter
data.orig <- data.orig %>%
mutate(
EU = ifelse(accuracy == 2 & det_accu == 5, "Error Undetected", NA),
CU = ifelse(accuracy == 1 & det_accu == 6, "Correct Undetected", NA),
ED = ifelse(accuracy == 2 & det_accu == 6, "Error Detected", NA),
CD = ifelse(accuracy == 1 & det_accu == 5, "Correct Detected", NA)
)
data.orig <- data.orig %>%
mutate(
signal = factor(case_when(
!is.na(ED) ~ "ED",
!is.na(EU) ~ "EU",
!is.na(CU) ~ "CU",
!is.na(CD) ~ "CD",
TRUE ~ NA_character_
)),
hit = ifelse(!is.na(ED), 1, 0),
fa = ifelse(!is.na(CU), 1, 0),
miss = ifelse(!is.na(EU), 1, 0),
cr = ifelse(!is.na(CD), 1, 0)
) %>%
filter(!is.na(signal))
# Number of subjects
n_subj <- length(data.erpagg$ID)
# Repeat subject-level variables twice (for correct and error conditions)
ID <- rep(data.erpagg$ID, 2)
worry_cen <- rep(data.pers.cen$worry_cen, 2)
opt_cen <- rep(data.pers.cen$opt_cen, 2)
worry <- rep(data.pers.all$worry, 2)
opt <- rep(data.pers.mean$opt, 2)
# Create accuracy factor (correct=1, error=2), length = 2*n_subj
acc <- factor(c(rep(1, n_subj), rep(2, n_subj)), labels = c("correct", "error"))
contrasts(acc) <- contr.sum(2) * (-0.5) # sum contrast coding centered at 0
# Combine ERP amplitudes for Ne and Pe, concatenating correct and error values
Ne <- c(data.erpagg$Ne_Pcorrect_Selfeval, data.erpagg$Ne_Perror_Selfeval)
Pe <- c(data.erpagg$Pe_Pcorrect_Selfeval, data.erpagg$Pe_Perror_Selfeval)
# Create final data frame
matrix.mean.erp <- data.frame(
ID, acc, worry_cen, opt_cen, worry, opt, Ne, Pe
) %>%
mutate(
Ne = as.numeric(Ne),
Pe = as.numeric(Pe)
)
# Define standard error function (handle NA properly)
stderror <- function(x) {
sd(x, na.rm = TRUE) / sqrt(sum(!is.na(x)))
}
# Check that lengths match (should all be TRUE)
all_lengths_equal <- length(ID) == length(acc) && length(worry_cen) == length(acc) && length(Ne) == length(acc)
print(paste("All lengths equal:", all_lengths_equal))
## [1] "All lengths equal: TRUE"
matrix.mean.Ne3 <- matrix.mean.erp %>%
group_by(ID, acc) %>%
summarise(
m.Ne = mean(Ne, na.rm = TRUE),
se.Ne = stderror(Ne),
worry_cen = first(worry_cen),
opt_cen = first(opt_cen)
) %>%
ungroup()
# 5. Check structure
str(matrix.mean.erp)
## 'data.frame': 182 obs. of 8 variables:
## $ ID : int 1 2 3 6 7 8 9 10 11 12 ...
## $ acc : Factor w/ 2 levels "correct","error": 1 1 1 1 1 1 1 1 1 1 ...
## ..- attr(*, "contrasts")= num [1:2, 1] -0.5 0.5
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr [1:2] "correct" "error"
## .. .. ..$ : NULL
## $ worry_cen: num 1.697 -0.886 -0.22 0.364 -0.22 ...
## $ opt_cen : num -0.0934 -0.5934 -0.0934 -0.0934 -0.0934 ...
## $ worry : num 4.08 1.5 2.17 2.75 2.17 ...
## $ opt : num 4 3.5 4 4 4 4 4 3.5 3.5 4 ...
## $ Ne : num -12.8 -14.2 -7.9 -5.6 -18.4 ...
## $ Pe : num 18.6 6.89 20.84 25.95 18.45 ...
# clean
rm(ID, opt , worry, opt_cen, worry_cen, acc, Ne, Pe)
(prepare data frame for predicted values)
# Convert `accuStr` to a factor with proper levels
data.main$accuStr <- factor(data.main$accuStr, levels = unique(data.main$accuStr))
# Check if levels are now correctly assigned
levels(data.main$accuStr)
## [1] "correct" "error"
limits <- max(c(abs(min(data.pers.cen$worry_cen)), abs(min(data.pers.cen$opt_cen)), max(data.pers.cen$worry_cen), max(data.pers.cen$opt_cen)))
b <- seq(-0.5-limits, 0.5+limits, 0.1)
# Limits are already defined earlier
data.pred1 <- data.frame(
ID = rep(unique(data.main$ID), each = 2 * length(b) * length(b)),
accuStr = rep(levels(data.main$accuStr), each = length(b) * length(b)),
worry_cen = rep(b, each = length(b)),
opt_cen = rep(b, length(b))
)
data.pred2 <- data.frame(
ID = rep(unique(data.main$ID), each = 2 * length(b) * length(b)),
accuStr = rep(levels(data.main$accuStr), each = length(b) * length(b)),
worry_cen = rep(b, each = length(b)),
opt_cen = rep(b, length(b))
)
# Merge and clean
data.pred <- rbind(data.pred1, data.pred2) %>%
mutate(
accuNum = ifelse(accuStr == "correct", -0.5, 0.5),
accuStr = factor(accuStr)
)
# Clean up temporary variables
rm(data.pred1, data.pred2)
jnp1 <- list()
# Step 1: Convert to factor
data.main$accuStr <- factor(data.main$accuStr)
contrasts(data.main$accuStr) <- contr.sum(2) *(-0.5)
contrasts(data.main.postbeh$accuStr) <- contr.sum(2) *(-0.5)
# full model
predictors.all <- c("(Intercept)",
"Response Type",
"Optimism",
"Worry",
"Response Type x Optimism",
"Evaluation Type x Optimism",
"Response Type x Worry",
"Worry x Optimism",
"Response Type x Worry x Optimism")
# Model with accuracy only
predictors.acc <- c(
"(Intercept)",
"Response Type",
"Optimism",
"Worry",
"Response Type x Optimism",
"Response Type x Worry",
"Worry x Optimism",
"Response Type x Worry x Optimism"
)
# model only with personality variables
predictors.opt <- c("(Intercept)",
"Optimism",
"Worry",
"Worry x Optimism"
)
cat(
"Age:\n",
"Mean =", round(mean(data.pers.all$age, na.rm = TRUE), 2),
"+/-", round(sd(data.pers.all$age, na.rm = TRUE), 2), "\n",
"Min =", round(min(data.pers.all$age, na.rm = TRUE), 2), "\n",
"Max =", round(max(data.pers.all$age, na.rm = TRUE), 2)
)
## Age:
## Mean = 22.68 +/- 5.68
## Min = 18
## Max = 59
paste("female:", sum(data.pers.all$gender == 1), "male:", sum(data.pers.all$gender == 2),"diverse:",sum(data.pers.all$gender == 0))
## [1] "female: 77 male: 14 diverse: 0"
# Coefficient Oldfield
paste("right:", sum(data.erpagg$Latquo > 40),
"left:", sum(data.erpagg$Latquo < -40),
"ambidexter:", sum(data.erpagg$Latquo >= -40 & data.erpagg$Latquo <= 40))
## [1] "right: 0 left: 0 ambidexter: 0"
data.quest <- data.items %>%
mutate(
worry = rowMeans(as.matrix(select(., msf.wX3., msf.wX4., msf.wX5., msf.wX6.,
msf.wX7., msf.wX8., msf.wX9., msf.wX10.,
msf.wX11., msf.wX12.)), na.rm = TRUE),
opt = rowMeans(as.matrix(select(., sop1.oX1., sop2.pX1.)), na.rm = TRUE)
) %>%
ungroup()
# Optimism
data.quest <- data.items %>%
mutate(worry = mean(c(msf.wX3., msf.wX4., msf.wX5., msf.wX6., msf.wX7., msf.wX8., msf.wX9., msf.wX10., msf.wX11., msf.wX12. ), na.rm = T))%>%
mutate(opt = mean(c(sop1.oX1., sop2.pX1. ), na.rm = T)) %>%
ungroup()
# Cronbachs alpha (conflicting - psych and scales, ggplot2)
psych::alpha(subset(data.quest, select = c(msf.wX3., msf.wX4., msf.wX5., msf.wX6., msf.wX7., msf.wX8., msf.wX9., msf.wX10., msf.wX11., msf.wX12. )))
##
## Reliability analysis
## Call: psych::alpha(x = subset(data.quest, select = c(msf.wX3., msf.wX4.,
## msf.wX5., msf.wX6., msf.wX7., msf.wX8., msf.wX9., msf.wX10.,
## msf.wX11., msf.wX12.)))
##
## raw_alpha std.alpha G6(smc) average_r S/N ase mean sd median_r
## 0.93 0.94 0.94 0.6 15 0.01 2.4 0.95 0.61
##
## 95% confidence boundaries
## lower alpha upper
## Feldt 0.91 0.93 0.95
## Duhachek 0.92 0.93 0.95
##
## Reliability if an item is dropped:
## raw_alpha std.alpha G6(smc) average_r S/N alpha se var.r med.r
## msf.wX3. 0.93 0.93 0.94 0.61 14 0.011 0.0087 0.62
## msf.wX4. 0.93 0.93 0.94 0.61 14 0.011 0.0078 0.62
## msf.wX5. 0.93 0.93 0.94 0.61 14 0.011 0.0088 0.60
## msf.wX6. 0.93 0.93 0.94 0.60 13 0.011 0.0089 0.60
## msf.wX7. 0.93 0.93 0.93 0.59 13 0.011 0.0088 0.60
## msf.wX8. 0.93 0.93 0.94 0.61 14 0.011 0.0092 0.62
## msf.wX9. 0.92 0.93 0.93 0.58 13 0.012 0.0086 0.59
## msf.wX10. 0.92 0.93 0.93 0.58 12 0.012 0.0081 0.59
## msf.wX11. 0.93 0.93 0.94 0.61 14 0.011 0.0080 0.62
## msf.wX12. 0.93 0.93 0.94 0.60 13 0.011 0.0089 0.60
##
## Item statistics
## n raw.r std.r r.cor r.drop mean sd
## msf.wX3. 91 0.73 0.75 0.71 0.67 1.9 1.02
## msf.wX4. 91 0.77 0.77 0.74 0.71 3.3 1.12
## msf.wX5. 91 0.74 0.76 0.72 0.68 1.9 1.02
## msf.wX6. 91 0.81 0.80 0.78 0.75 2.5 1.32
## msf.wX7. 91 0.84 0.86 0.84 0.81 1.8 0.97
## msf.wX8. 91 0.77 0.76 0.72 0.70 2.9 1.42
## msf.wX9. 91 0.88 0.87 0.86 0.84 2.3 1.28
## msf.wX10. 91 0.88 0.88 0.87 0.84 2.6 1.30
## msf.wX11. 91 0.78 0.76 0.74 0.71 2.9 1.35
## msf.wX12. 91 0.79 0.80 0.77 0.74 2.0 1.06
##
## Non missing response frequency for each item
## 1 2 3 4 5 miss
## msf.wX3. 0.49 0.22 0.20 0.09 0.00 0
## msf.wX4. 0.04 0.21 0.29 0.30 0.16 0
## msf.wX5. 0.46 0.29 0.14 0.11 0.00 0
## msf.wX6. 0.29 0.27 0.19 0.15 0.10 0
## msf.wX7. 0.48 0.27 0.16 0.08 0.00 0
## msf.wX8. 0.22 0.20 0.16 0.25 0.16 0
## msf.wX9. 0.37 0.26 0.13 0.18 0.05 0
## msf.wX10. 0.27 0.23 0.16 0.27 0.05 0
## msf.wX11. 0.20 0.24 0.16 0.26 0.13 0
## msf.wX12. 0.41 0.30 0.20 0.08 0.02 0
psych::alpha(subset(data.quest, select = c(sop1.oX1., sop2.pX1. )))
## Warning in psych::alpha(subset(data.quest, select = c(sop1.oX1., sop2.pX1.))): Some items were negatively correlated with the first principal component and probably
## should be reversed.
## To do this, run the function again with the 'check.keys=TRUE' option
## Some items ( sop1.oX1. ) were negatively correlated with the first principal component and
## probably should be reversed.
## To do this, run the function again with the 'check.keys=TRUE' option
## Warning in sqrt(Vtc): NaNs produced
##
## Reliability analysis
## Call: psych::alpha(x = subset(data.quest, select = c(sop1.oX1., sop2.pX1.)))
##
## raw_alpha std.alpha G6(smc) average_r S/N ase mean sd median_r
## -2.8 -2.9 -0.59 -0.59 -0.74 0.77 4.1 0.57 -0.59
##
## 95% confidence boundaries
## lower alpha upper
## Feldt -4.69 -2.76 -1.48
## Duhachek -4.26 -2.76 -1.25
##
## Reliability if an item is dropped:
## raw_alpha std.alpha G6(smc) average_r S/N alpha se var.r med.r
## sop1.oX1. -0.49 -0.59 0.35 -0.59 -0.37 NA 0 -0.59
## sop2.pX1. -0.70 -0.59 0.35 -0.59 -0.37 NA 0 -0.59
##
## Item statistics
## n raw.r std.r r.cor r.drop mean sd
## sop1.oX1. 91 0.29 0.45 NaN -0.59 5.1 1.1
## sop2.pX1. 91 0.60 0.45 NaN -0.59 3.1 1.3
##
## Non missing response frequency for each item
## 1 2 3 4 5 6 7 miss
## sop1.oX1. 0.00 0.03 0.03 0.19 0.41 0.24 0.1 0
## sop2.pX1. 0.04 0.41 0.20 0.18 0.11 0.07 0.0 0
paste("worry", round(mean(data.pers.all$worry),2), "+/-", round(sd(data.pers.all$worry), 2), "min:",round(min(data.pers.all$worry),2), "max:" , round(max(data.pers.all$worry),2))
## [1] "worry 2.39 +/- 0.94 min: 1 max: 4.5"
paste("opt", round(mean(data.pers.all$opt),2), "+/-", round(sd(data.pers.all$opt), 2),"min:",round(min(data.pers.all$opt),2), "max:" , round(max(data.pers.all$opt),2))
## [1] "opt 4.09 +/- 0.57 min: 2 max: 6"
# matrix: ID, error rates, opt, worry
data.orig$ID <- as.character(data.orig$ID)
data.pers.cen$ID <- as.character(data.pers.cen$ID)
data.orig <- data.orig %>%
mutate(ID = as.integer(as.character(ID)))
data.pers.cen <- data.pers.cen %>%
mutate(ID = as.integer(as.character(ID)))
data.orig <- data.orig %>%
left_join(data.pers.cen %>% select(ID, opt, opt_cen, worry, worry_cen), by = "ID")
names(data.orig)
## [1] "ID" "block" "trial"
## [4] "position" "stimulus" "ITI"
## [7] "force_1" "force_2" "force_3"
## [10] "force_4" "force_5" "force_6"
## [13] "force_7" "force_8" "RT_1"
## [16] "RT_2" "RT_3" "RT_4"
## [19] "RT_5" "RT_6" "RT_7"
## [22] "RT_8" "TTP_1" "TTP_2"
## [25] "TTP_3" "TTP_4" "TTP_5"
## [28] "TTP_6" "TTP_7" "TTP_8"
## [31] "firstresponse" "accuracy" "dummy"
## [34] "RT" "RF" "TTP"
## [37] "Det_force_1" "Det_force_2" "Det_force_3"
## [40] "Det_force_4" "Det_force_5" "Det_force_6"
## [43] "Det_force_7" "Det_force_8" "Det_RT_1"
## [46] "Det_RT_2" "Det_RT_3" "Det_RT_4"
## [49] "Det_RT_5" "Det_RT_6" "Det_RT_7"
## [52] "Det_RT_8" "Det_TTP_1" "Det_TTP_2"
## [55] "Det_TTP_3" "Det_TTP_4" "Det_TTP_5"
## [58] "Det_TTP_6" "Det_TTP_7" "Det_TTP_8"
## [61] "det_accu" "Det_RT" "DET_RF"
## [64] "DET_TTP" "age" "gender"
## [67] "msf.wX1." "msf.wX2." "msf.wX3."
## [70] "msf.wX4." "msf.wX5." "msf.wX6."
## [73] "msf.wX7." "msf.wX8." "msf.wX9."
## [76] "msf.wX10." "msf.wX11." "msf.wX12."
## [79] "sop1.oX1." "sop2.pX1." "Ne_Pcorrect_Selfeval"
## [82] "Ne_Perror_Selfeval" "Pe_Perror_Selfeval" "Pe_Pcorrect_Selfeval"
## [85] "accuStr" "Type_Nplus" "trial_index"
## [88] "error_b" "correct_b" "error_timeout_b"
## [91] "timeout_b" "too_slow_b" "error_nplus"
## [94] "correct_nplus" "error_timeout_nplus" "timeout_nplus"
## [97] "too_slow_nplus" "EU" "CU"
## [100] "ED" "CD" "signal"
## [103] "hit" "fa" "miss"
## [106] "cr" "opt" "opt_cen"
## [109] "worry" "worry_cen"
# First: timeout summary
timeout_summary <- data.orig %>%
group_by(ID) %>%
summarise(
total_trials = n(),
timeout_abs = sum(RT == 999999.00, na.rm = TRUE),
timeout_perc = round(100 * timeout_abs / total_trials, 2)
)
sum(data.orig$RT == 999999.00, na.rm = TRUE) #to check for myself
## [1] 0
matrix.mean.errorrate <- data.orig %>%
group_by(ID) %>%
summarise(
error_abs = sum(accuStr == "error", na.rm = TRUE),
correct_abs = sum(accuStr == "correct", na.rm = TRUE),
total_trials = n(),
error_perc = round(100 * error_abs / total_trials, 2),
correct_perc = round(100 * correct_abs / total_trials, 2),
too_slow_abs = sum(too_slow_b),
too_slow_perc = mean(too_slow_b)*100,
worry_cen = mean(worry_cen),
opt_cen = mean(opt_cen)) %>%
ungroup()
#average.errorrate. <- matrix.mean.errorrate %>%
#group_by() %>%
#summarise(m.error_abs = mean(error_abs),
# average: error rates
##average.errorrate.EV <- matrix.mean.errorrate %>%
#group_by() %>%
#summarise(m.error_abs = mean(error_abs),
#se.error_abs = stderror(error_abs),
#m.error_perc = mean(error_perc),
#se.error_perc = stderror(error_perc),
#m.correct_abs = mean(correct_abs),
#se.correct_abs = stderror(correct_abs),
#m.correct_perc = mean(correct_perc),
#se.correct_perc = stderror(correct_perc),
#m.too_slow_abs = mean(too_slow_abs),
#se.too_slow_abs = stderror(too_slow_abs),
#m.too_slow_perc = mean(too_slow_perc),
#se.too_slow_perc = stderror(too_slow_perc),
#ps_cen = mean(ps_cen),
#cm_cen = mean(cm_cen)
# ) %>%
#ungroup()
# average: overall, error rates
se <- function(x) {
sd(x, na.rm = TRUE) / sqrt(length(na.omit(x)))
}
average.errorrate.oa <- matrix.mean.errorrate %>%
group_by() %>%
summarise(m.error_abs = mean(error_abs),
se.error_abs = stderror(error_abs),
m.error_perc = mean(error_perc),
se.error_perc = stderror(error_perc),
m.correct_abs = mean(correct_abs),
se.correct_abs = stderror(correct_abs),
m.correct_perc = mean(correct_perc),
se.correct_perc = stderror(correct_perc),
m.too_slow_abs = mean(too_slow_abs),
se.too_slow_abs = stderror(too_slow_abs),
m.too_slow_perc = mean(too_slow_perc),
se.too_slow_perc = stderror(too_slow_perc)
) %>%
ungroup()
paste("Error N:",round(average.errorrate.oa$m.error_abs,2), "+/-", round(average.errorrate.oa$se.error_abs,2), "Error rate: ", round(average.errorrate.oa$m.error_perc,2), "+/-", round(average.errorrate.oa$se.error_perc,2), "%")
## [1] "Error N: 27.37 +/- 1.73 Error rate: 7.75 +/- 0.54 %"
colnames(average.errorrate.oa)
## [1] "m.error_abs" "se.error_abs" "m.error_perc" "se.error_perc"
## [5] "m.correct_abs" "se.correct_abs" "m.correct_perc" "se.correct_perc"
## [9] "m.too_slow_abs" "se.too_slow_abs" "m.too_slow_perc" "se.too_slow_perc"
summary(average.errorrate.oa)
## m.error_abs se.error_abs m.error_perc se.error_perc
## Min. :27.37 Min. :1.733 Min. :7.751 Min. :0.5356
## 1st Qu.:27.37 1st Qu.:1.733 1st Qu.:7.751 1st Qu.:0.5356
## Median :27.37 Median :1.733 Median :7.751 Median :0.5356
## Mean :27.37 Mean :1.733 Mean :7.751 Mean :0.5356
## 3rd Qu.:27.37 3rd Qu.:1.733 3rd Qu.:7.751 3rd Qu.:0.5356
## Max. :27.37 Max. :1.733 Max. :7.751 Max. :0.5356
## m.correct_abs se.correct_abs m.correct_perc se.correct_perc
## Min. :338.2 Min. :5.297 Min. :92.25 Min. :0.5356
## 1st Qu.:338.2 1st Qu.:5.297 1st Qu.:92.25 1st Qu.:0.5356
## Median :338.2 Median :5.297 Median :92.25 Median :0.5356
## Mean :338.2 Mean :5.297 Mean :92.25 Mean :0.5356
## 3rd Qu.:338.2 3rd Qu.:5.297 3rd Qu.:92.25 3rd Qu.:0.5356
## Max. :338.2 Max. :5.297 Max. :92.25 Max. :0.5356
## m.too_slow_abs se.too_slow_abs m.too_slow_perc se.too_slow_perc
## Min. :0 Min. :0 Min. :0 Min. :0
## 1st Qu.:0 1st Qu.:0 1st Qu.:0 1st Qu.:0
## Median :0 Median :0 Median :0 Median :0
## Mean :0 Mean :0 Mean :0 Mean :0
## 3rd Qu.:0 3rd Qu.:0 3rd Qu.:0 3rd Qu.:0
## Max. :0 Max. :0 Max. :0 Max. :0
Error.lm <- lm(error_perc ~ opt_cen * worry_cen, data = matrix.mean.errorrate)
tab_model(Error.lm, dv.labels = "Error Rates [%]")
| Error Rates [%] | |||
|---|---|---|---|
| Predictors | Estimates | CI | p |
| (Intercept) | 7.67 | 6.53 – 8.81 | <0.001 |
| opt cen | 0.19 | -1.85 – 2.24 | 0.852 |
| worry cen | 0.25 | -0.98 – 1.48 | 0.684 |
| opt cen × worry cen | 0.45 | -1.56 – 2.47 | 0.655 |
| Observations | 91 | ||
| R2 / R2 adjusted | 0.006 / -0.029 | ||
check_collinearity(Error.lm)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## opt_cen 1.14 [1.03, 1.80] 1.07 0.88 [0.56, 0.98]
## worry_cen 1.14 [1.02, 1.81] 1.07 0.88 [0.55, 0.98]
## opt_cen:worry_cen 1.00 [1.00, Inf] 1.00 1.00 [0.00, 1.00]
# plot results
dum <- check_collinearity(Error.lm)
library(ggplot2)
ggplot(dum, aes(x = Term, y = VIF)) +
geom_col() +
geom_hline(yintercept = 5, linetype = "dashed", color = "red") + # Warning line at VIF = 5
theme_minimal() +
labs(title = "Variance Inflation Factors (VIF)",
y = "VIF Value",
x = "Predictors")
# Fig.mean distributions
matrix.mean.errorrate$group <- "All"
ggplot(matrix.mean.errorrate, aes(x=group, y=error_perc)) +
geom_violin(fill="#3df50a", color="#f20a19") +
geom_boxplot(width=0.2) +
geom_jitter(width=0.1) +
labs(x = "Participants", y = "Error Rate [%]") +
theme_classic()
### Statistical analyses
# Base matrix with both predictors
matrix.mean.SDT <- data.orig %>%
group_by(ID) %>%
summarise(
hit = mean(hit),
fa = mean(fa),
miss = mean(miss),
cr = mean(cr),
opt_cen = mean(opt_cen),
worry_cen = mean(worry_cen)
) %>%
ungroup() %>%
mutate(
dprime = qnorm(hit) - qnorm(fa),
bias = (-0.5) * (qnorm(hit) + qnorm(fa)),
dprime = replace(dprime, is.infinite(dprime), NA),
bias = replace(bias, is.infinite(bias), NA)
)
# 1. Main (Optimism-only)
matrix.mean.SDT.opt <- matrix.mean.SDT %>%
select(ID, hit, fa, miss, cr, opt_cen, dprime, bias)
# 2. Exploratory (Optimism + Worry)
matrix.mean.SDT.exploratory <- matrix.mean.SDT
average.SDT.opt <- matrix.mean.SDT.opt %>%
filter(!is.na(bias) & !is.na(dprime)) %>%
summarise(
m.bias = mean(bias),
se.bias = stderror(bias),
m.dprime = mean(dprime),
se.dprime = stderror(dprime)
)
paste("sensitivity d′:", round(average.SDT.opt$m.dprime, 2), "+/-", round(average.SDT.opt$se.dprime, 2))
## [1] "sensitivity d′: 0.67 +/- 0.05"
paste("bias c:", round(average.SDT.opt$m.bias, 2), "+/-", round(average.SDT.opt$se.bias, 2))
## [1] "bias c: 1.94 +/- 0.04"
# rating data
matrix.mean.certain <-data.main.postbeh %>%
group_by(ID, accuStr, ) %>%
summarise(det_accu = mean(det_accu),
opt = mean(opt),
worry = mean(worry)) %>%
ungroup()
# grand average : accuracy - RT
average.certain <- matrix.mean.certain %>%
group_by(accuStr) %>%
reframe(m.rating = mean(det_accu),
se.rating = stderror(det_accu)) %>%
ungroup()
paste("Rating: ", average.certain$accuStr, round(average.certain$m.rating,2), "+/-", round(average.certain$se.rating,2))
## [1] "Rating: error 5.34 +/- 0.05" "Rating: correct 4.52 +/- 0.05"
# linear model: bias, opt x worry
bias.lm <- lm(bias ~ opt_cen * worry_cen, data = matrix.mean.SDT)
# 3. Define predictor labels (if needed)
predictors <- c("(Intercept)", "opt_cen", "worry_cen", "opt_cen:worry_cen")
ggplot(dum, aes(x = Term, y = VIF)) +
geom_col() +
geom_hline(yintercept = 5, linetype = "dashed", color = "red") + # Warning line at VIF = 5
theme_minimal() +
labs(
title = "Variance Inflation Factors (VIF)",
y = "VIF Value",
x = "Predictors"
)
#### Collinearity bias
# no effect in LM
check_collinearity(bias.lm)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## opt_cen 1.15 [1.03, 1.89] 1.07 0.87 [0.53, 0.97]
## worry_cen 1.16 [1.03, 1.88] 1.08 0.87 [0.53, 0.97]
## opt_cen:worry_cen 1.00 [1.00, Inf] 1.00 1.00 [0.00, 1.00]
if (require("see")) {
ggplot(dum, aes(x = Term, y = VIF)) +
geom_col() +
geom_hline(yintercept = 5, linetype = "dashed", color = "red") + # Warning line at VIF = 5
theme_minimal() +
labs(
title = "Variance Inflation Factors (VIF)",
y = "VIF Value",
x = "Predictors"
)
}
# linear model: d', opt x worry (only for Eval applicable) ?????
dprime.lm <- lm(dprime ~ opt_cen * worry_cen, matrix.mean.SDT)
tab_model(dprime.lm, dv.labels = "dprime [%]")
| dprime [%] | |||
|---|---|---|---|
| Predictors | Estimates | CI | p |
| (Intercept) | 0.66 | 0.56 – 0.76 | <0.001 |
| opt cen | 0.04 | -0.13 – 0.21 | 0.667 |
| worry cen | -0.01 | -0.11 – 0.10 | 0.868 |
| opt cen × worry cen | 0.04 | -0.13 – 0.21 | 0.625 |
| Observations | 76 | ||
| R2 / R2 adjusted | 0.006 / -0.036 | ||
# no effect in LM
check_collinearity(dprime.lm)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## opt_cen 1.15 [1.03, 1.89] 1.07 0.87 [0.53, 0.97]
## worry_cen 1.16 [1.03, 1.88] 1.08 0.87 [0.53, 0.97]
## opt_cen:worry_cen 1.00 [1.00, Inf] 1.00 1.00 [0.00, 1.00]
# plot results
if (require("see")) {
dum <- check_collinearity(dprime.lm )
plot(dum)
}
matrix.mean.certain <- data.main.postbeh %>%
group_by(ID, accuStr) %>%
summarise(
det_accu = mean(det_accu, na.rm = TRUE),
opt_cen = first(opt_cen),
worry_cen = first(worry_cen)
) %>%
ungroup()
Rating0.lme <- lmer(det_accu ~ accuStr * opt_cen * worry_cen + (1| ID), matrix.mean.certain)
tab_model(Rating0.lme, dv.labels = "Certainty [1..4] 1 = certain, 4 = uncertain")
| Certainty [1..4] 1 = certain, 4 = uncertain | |||
|---|---|---|---|
| Predictors | Estimates | CI | p |
| (Intercept) | 4.93 | 4.85 – 5.01 | <0.001 |
| accuStr1 | -0.82 | -0.92 – -0.71 | <0.001 |
| opt cen | -0.11 | -0.26 – 0.04 | 0.141 |
| worry cen | 0.09 | 0.00 – 0.18 | 0.049 |
| accuStr1 × opt cen | 0.14 | -0.06 – 0.33 | 0.163 |
| accuStr1 × worry cen | -0.07 | -0.19 – 0.04 | 0.201 |
| opt cen × worry cen | -0.01 | -0.16 – 0.13 | 0.845 |
|
(accuStr1 × opt cen) × worry cen |
0.02 | -0.17 – 0.20 | 0.871 |
| Random Effects | |||
| σ2 | 0.12 | ||
| τ00 ID | 0.08 | ||
| ICC | 0.41 | ||
| N ID | 91 | ||
| Observations | 182 | ||
| Marginal R2 / Conditional R2 | 0.465 / 0.684 | ||
check_collinearity(Rating0.lme)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance
## accuStr 1.12 [1.03, 1.50] 1.06 0.89
## opt_cen 1.14 [1.04, 1.49] 1.07 0.88
## worry_cen 1.14 [1.04, 1.49] 1.07 0.88
## accuStr:opt_cen 1.14 [1.04, 1.49] 1.07 0.88
## accuStr:worry_cen 1.14 [1.04, 1.49] 1.07 0.88
## opt_cen:worry_cen 1.00 [1.00, 9.71e+12] 1.00 1.00
## accuStr:opt_cen:worry_cen 1.12 [1.03, 1.49] 1.06 0.89
## Tolerance 95% CI
## [0.67, 0.97]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.00, 1.00]
## [0.67, 0.97]
# plot results
if (require("see")) {
dum <- check_collinearity(Rating0.lme)
plot(dum)
}
###Figures (Appendix)
# Fig.mean distributions
ggplot(matrix.mean.SDT, aes(x = 0, y=dprime)) +
geom_violin(position=position_dodge(1)) +
geom_point(position=position_dodge(1))+
labs(x = "", y = "Sensitivity (d')") +
geom_boxplot(position=position_dodge(1),width = 0.2)+
theme_classic()
## Warning: Removed 15 rows containing non-finite outside the scale range
## (`stat_ydensity()`).
## Warning: Removed 15 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
## Warning: Removed 15 rows containing missing values or values outside the scale range
## (`geom_point()`).
# Fig.mean distributions
ggplot(matrix.mean.SDT, aes(x = 0, y=bias)) +
geom_violin(position=position_dodge(1)) +
geom_point(position=position_dodge(1))+
labs(x = "", y = "Bias (c)") +
geom_boxplot(position=position_dodge(1),width = 0.2)+
theme_classic()
## Warning: Removed 15 rows containing non-finite outside the scale range
## (`stat_ydensity()`).
## Warning: Removed 15 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
## Warning: Removed 15 rows containing missing values or values outside the scale range
## (`geom_point()`).
(Decision against median e.g. https://psyarxiv.com/3q5np/ )
# Summarise the data by ID and accuStr
matrix.mean.RT <- data.main %>%
group_by(ID, accuStr ) %>%
summarise(
mRT = mean(RT, na.rm = TRUE), # Calculate the mean of RT
seRT = stderror(RT), # Standard error of RT
opt_cen = mean(opt_cen, na.rm = TRUE),
worry_cen = mean(worry_cen, na.rm = TRUE)
) %>%
ungroup()
average.RT.ID <- data.main %>%
group_by(ID) %>%
summarise(m.RT = mean(RT),
se.RT = stderror(RT)) %>%
ungroup()
# grand average: experiment x accuracy - RT
average.RT.AccEV <- matrix.mean.RT %>%
group_by(accuStr, ) %>%
summarise(m.RT = mean(mRT),
se.RT.ae = sd(mRT)) %>%
ungroup()
# grand average : accuracy - RT
average.RT.Acc <- matrix.mean.RT %>%
group_by(accuStr) %>%
summarise(m.RT = mean(mRT),
se.RT = stderror(mRT)) %>%
ungroup()
# grand average : evaluation - RT
#average.RT.EV <- matrix.mean.RT %>%
#group_by() %>%
#summarise(m.RT = mean(mRT),
#se.RT = stderror(mRT)) %>%
#ungroup()
average.RT.oa <- matrix.mean.RT %>%
group_by() %>%
summarise(m.RT = mean(mRT),
se.RT = stderror(mRT)) %>%
ungroup()
paste("mean RT overall:", round(average.RT.oa$m.RT,2), "+/-",
round(average.RT.oa$se.RT,2), "ms")
## [1] "mean RT overall: 890.49 +/- 7.43 ms"
# Step 1: Set factor levels and contrasts
data.main.postbeh$accuStr <- factor(data.main.postbeh$accuStr, levels = c("error", "correct"))
contrasts(data.main.postbeh$accuStr) <- contr.sum(2) / 2 # sets: error = -0.5, correct = +0.5
# Step 2: Fit LME model with full 3-way interaction
RT_model1 <- lmer(RT ~ accuStr * opt_cen * worry_cen + (1 | ID), data = data.main.postbeh)
# Step 3: Simulated slope plot (3-way interaction)
RT_plot1 <- sim_slope_plot_3x(
model = RT_model1,
steps = 0.02,
thresholding = TRUE
)
## Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
## # Was:
## data %>% select(contix)
##
## # Now:
## data %>% select(all_of(contix))
##
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
## # Was:
## data %>% select(contiy)
##
## # Now:
## data %>% select(all_of(contiy))
##
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Step 4: Customize gradient fill and axes
RT_plot1 <- RT_plot1 +
scale_fill_gradient2(
limits = c(-150, 150),
low = muted("blue"),
mid = "white",
high = muted("red"),
name = "Accuracy Effect (error - correct)"
) +
labs(
title = "RT Interaction: Accuracy × Optimism × Worry",
x = "Optimism (centered)",
y = "Worry (centered)"
) +
theme_classic() +
theme(legend.position = "bottom")
## Scale for fill is already present.
## Adding another scale for fill, which will replace the existing scale.
# Step 5: Plot
cowplot::plot_grid(RT_plot1)
interact_plot(RT_model1, pred = "opt_cen", modx = "accuStr", mod2 = "worry_cen")
opt_range <- range(data.main.postbeh$opt_cen, na.rm = TRUE)
worry_range <- range(data.main.postbeh$worry_cen, na.rm = TRUE)
data.grid <- expand.grid(
opt_cen = seq(opt_range[1], opt_range[2], length.out = 50),
worry_cen = seq(worry_range[1], worry_range[2], length.out = 50),
accuStr = factor(c("error", "correct"), levels = c("error", "correct"))
)
#### LME - RT #the table was not loaded.note for Faz
# Accuracy x PSP x ECP
RT1.lme <- lmer(RT ~ accuStr * opt_cen * worry_cen + (accuStr | ID), data.main)
# binary - required for JN-procedure
RT1.lmeb <- lmer(RT ~ accuStr * opt_cen * worry_cen + (accuStr | ID), data.main)
names(predictors.all) <- colnames(RT1.lme@vcov_beta)[1:length(predictors.all)]
# Table of all effects
names(predictors.all) <- colnames(RT1.lme@vcov_beta)
library(broom.mixed)
tidy(RT1.lme, effects = "fixed")
## # A tibble: 8 × 7
## effect term estimate std.error statistic df p.value
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 fixed (Intercept) 884. 9.96 88.8 88.3 3.80e-88
## 2 fixed accuStr1 -2.33 10.2 -0.228 94.2 8.20e- 1
## 3 fixed opt_cen -17.1 17.8 -0.957 88.1 3.41e- 1
## 4 fixed worry_cen 13.1 10.7 1.22 88.2 2.25e- 1
## 5 fixed accuStr1:opt_cen -1.33 18.3 -0.0726 92.3 9.42e- 1
## 6 fixed accuStr1:worry_cen 5.33 11.0 0.486 93.6 6.28e- 1
## 7 fixed opt_cen:worry_cen 33.6 17.7 1.90 89.8 6.05e- 2
## 8 fixed accuStr1:opt_cen:worry_cen 19.7 18.4 1.07 98.0 2.87e- 1
tab_model(RT1.lme,
pred.labels = predictors.all,
dv.labels = "Response Time Effects")
| Response Time Effects | |||
|---|---|---|---|
| Predictors | Estimates | CI | p |
| (Intercept) | 884.01 | 864.49 – 903.52 | <0.001 |
| Response Type | -2.33 | -22.37 – 17.70 | 0.819 |
| Optimism | -17.05 | -51.99 – 17.89 | 0.339 |
| Worry | 13.06 | -7.91 – 34.04 | 0.222 |
| Response Type x Optimism | -1.33 | -37.13 – 34.48 | 0.942 |
|
Evaluation Type x Optimism |
5.33 | -16.18 – 26.83 | 0.627 |
| Response Type x Worry | 33.62 | -1.05 – 68.28 | 0.057 |
| Worry x Optimism | 19.67 | -16.38 – 55.71 | 0.285 |
| Random Effects | |||
| σ2 | 41380.23 | ||
| τ00 ID | 7588.01 | ||
| τ11 ID.accuStr1 | 6582.06 | ||
| ρ01 ID | 0.39 | ||
| ICC | 0.14 | ||
| N ID | 91 | ||
| Observations | 37143 | ||
| Marginal R2 / Conditional R2 | 0.007 / 0.149 | ||
tab_model(RT1.lme,
pred.labels = predictors.all,
dv.labels = "Response Time Effects")
| Response Time Effects | |||
|---|---|---|---|
| Predictors | Estimates | CI | p |
| (Intercept) | 884.01 | 864.49 – 903.52 | <0.001 |
| Response Type | -2.33 | -22.37 – 17.70 | 0.819 |
| Optimism | -17.05 | -51.99 – 17.89 | 0.339 |
| Worry | 13.06 | -7.91 – 34.04 | 0.222 |
| Response Type x Optimism | -1.33 | -37.13 – 34.48 | 0.942 |
|
Evaluation Type x Optimism |
5.33 | -16.18 – 26.83 | 0.627 |
| Response Type x Worry | 33.62 | -1.05 – 68.28 | 0.057 |
| Worry x Optimism | 19.67 | -16.38 – 55.71 | 0.285 |
| Random Effects | |||
| σ2 | 41380.23 | ||
| τ00 ID | 7588.01 | ||
| τ11 ID.accuStr1 | 6582.06 | ||
| ρ01 ID | 0.39 | ||
| ICC | 0.14 | ||
| N ID | 91 | ||
| Observations | 37143 | ||
| Marginal R2 / Conditional R2 | 0.007 / 0.149 | ||
predictor.labels <- c(
"(Intercept)" = "Baseline RT",
"accuStr1" = "Accuracy: Error vs Correct",
"opt_cen" = "Optimism (centered)",
"worry_cen" = "Worry (centered)",
"accuStr1:opt_cen" = "Accuracy × Optimism",
"accuStr1:worry_cen" = "Accuracy × Worry",
"opt_cen:worry_cen" = "Optimism × Worry",
"accuStr1:opt_cen:worry_cen" = "3-Way Interaction"
)
check_collinearity(RT1.lme )
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance
## accuStr 1.12 [1.11, 1.13] 1.06 0.89
## opt_cen 1.41 [1.39, 1.43] 1.19 0.71
## worry_cen 1.40 [1.38, 1.42] 1.18 0.71
## accuStr:opt_cen 1.41 [1.39, 1.43] 1.19 0.71
## accuStr:worry_cen 1.40 [1.38, 1.42] 1.18 0.71
## opt_cen:worry_cen 1.25 [1.23, 1.26] 1.12 0.80
## accuStr:opt_cen:worry_cen 1.38 [1.36, 1.39] 1.17 0.73
## Tolerance 95% CI
## [0.88, 0.90]
## [0.70, 0.72]
## [0.70, 0.72]
## [0.70, 0.72]
## [0.70, 0.72]
## [0.79, 0.81]
## [0.72, 0.74]
# plot results
if (require("see")) {
x <- check_collinearity(RT1.lme)
plot(x)
}
#### Post hoc - Acc
### Post hoc pairwise comparisons – Accuracy only
emmeans(RT1.lme, specs = "accuStr") %>% pairs()
## Note: D.f. calculations have been disabled because the number of observations exceeds 3000.
## To enable adjustments, add the argument 'pbkrtest.limit = 37143' (or larger)
## [or, globally, 'set emm_options(pbkrtest.limit = 37143)' or larger];
## but be warned that this may result in large computation time and memory use.
## Note: D.f. calculations have been disabled because the number of observations exceeds 3000.
## To enable adjustments, add the argument 'lmerTest.limit = 37143' (or larger)
## [or, globally, 'set emm_options(lmerTest.limit = 37143)' or larger];
## but be warned that this may result in large computation time and memory use.
## NOTE: Results may be misleading due to involvement in interactions
## contrast estimate SE df z.ratio p.value
## correct - error 2.38 10.2 Inf 0.233 0.8158
##
## Degrees-of-freedom method: asymptotic
emmeans(RT1.lme, specs = "accuStr", lmerTest.limit = 50000) %>% pairs()
## Note: D.f. calculations have been disabled because the number of observations exceeds 3000.
## To enable adjustments, add the argument 'pbkrtest.limit = 37143' (or larger)
## [or, globally, 'set emm_options(pbkrtest.limit = 37143)' or larger];
## but be warned that this may result in large computation time and memory use.
## NOTE: Results may be misleading due to involvement in interactions
## contrast estimate SE df t.ratio p.value
## correct - error 2.38 10.2 94.2 0.233 0.8163
##
## Degrees-of-freedom method: satterthwaite
#### Johnson-Neyman: opt_cen × Accuracy
# Map opt_cen back to post-behavioral dataset
data.main.postbeh$opt_cen <- data.main$opt_cen[match(data.main.postbeh$ID, data.main$ID)]
# Contrast-code accuracy manually (bypasses parsing issues)
data.main.postbeh$accuNum <- ifelse(data.main.postbeh$accuStr == "error", 0.5, -0.5)
# Refit model using numeric-coded accuracy
RT1.simple.num <- lmer(RT ~ accuNum * opt_cen + (1 | ID), data = data.main.postbeh)
# Generate predicted RTs (optional)
data.pred <- data.main.postbeh %>%
mutate(RTP = predict(RT1.simple.num, newdata = data.main.postbeh, allow.new.levels = TRUE))
# Johnson-Neyman interval (numeric output and plot)
RT1.jn <- interactions::johnson_neyman(
model = RT1.simple.num,
pred = "accuNum", # contrast-coded: -0.5 = correct, 0.5 = error
modx = "opt_cen", # moderator = optimism
plot = TRUE
)
# Summary
summary(RT1.simple.num)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: RT ~ accuNum * opt_cen + (1 | ID)
## Data: data.main.postbeh
##
## REML criterion at convergence: 501089.5
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -5.1488 -0.7296 -0.1222 0.6702 3.1894
##
## Random effects:
## Groups Name Variance Std.Dev.
## ID (Intercept) 6667 81.65
## Residual 41917 204.74
## Number of obs: 37143, groups: ID, 91
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 888.541 8.796 96.016 101.017 <2e-16 ***
## accuNum -1.379 4.082 37088.943 -0.338 0.735
## opt_cen -13.070 15.564 95.309 -0.840 0.403
## accuNum:opt_cen -3.069 6.971 37084.488 -0.440 0.660
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) accuNm opt_cn
## accuNum 0.196
## opt_cen -0.002 -0.008
## accNm:pt_cn -0.008 -0.034 0.187
print(RT1.jn)
## JOHNSON-NEYMAN INTERVAL
##
## The Johnson-Neyman interval could not be found. Is the p value for your
## interaction term below the specified alpha?
### Plot observed RT across optimism and accuracy (original factor-coded accuracy)
data.main.postbeh$accuStr <- factor(data.main.postbeh$accuStr, levels = c("correct", "error"))
plot_data <- data.main.postbeh %>%
group_by(accuStr, opt_cen) %>%
summarise(mean_RT = mean(RT, na.rm = TRUE), .groups = "drop")
ggplot(plot_data, aes(x = opt_cen, y = mean_RT, colour = accuStr, linetype = accuStr)) +
geom_line(linewidth = 1.2) +
theme_minimal() +
labs(
title = "Observed RT across Optimism and Accuracy",
x = "Optimism (centered)",
y = "Observed RT (ms)",
colour = "Accuracy",
linetype = "Accuracy"
)
### Johnson-Neyman-style shaded plot
yrange <- range(data.main.postbeh$RT, na.rm = TRUE)
jnp1$RT.opt <- data.main.postbeh %>%
group_by(accuStr, opt_cen, ID) %>%
summarise(RT = mean(RT), .groups = "drop") %>%
group_by(accuStr, opt_cen) %>%
summarise(RT = mean(RT), .groups = "drop") %>%
ggplot(aes(x = opt_cen, y = RT, colour = accuStr, linetype = accuStr)) +
geom_rect(
alpha = 0.2, fill = "lightgray", inherit.aes = FALSE,
ymin = -Inf, ymax = Inf,
xmin = min(data.main.postbeh$opt_cen),
xmax = min(RT1.jn$bounds["Lower"])
) +
geom_point(
data = data.main.postbeh %>% select(ID, opt_cen) %>% distinct(),
mapping = aes(x = opt_cen, y = yrange[1] + 0.01 * (yrange[2] - yrange[1])),
inherit.aes = FALSE, alpha = 0.2,
position = position_jitter(height = 0.01 * (yrange[2] - yrange[1]))
) +
geom_line(linewidth = 1.1) +
labs(
x = "Optimism (centered)",
y = "Observed Response Time [ms]",
colour = "Accuracy",
linetype = "Accuracy"
) +
scale_color_grey(start = 0, end = 0.2) +
scale_linetype_manual(values = c("solid", "longdash")) +
theme_classic()
# Display final plot
jnp1$RT.opt
# Get observed RT per condition and optimism level
plot_data <- data.main.postbeh %>%
group_by(accuStr, opt_cen) %>%
summarise(mean_RT = mean(RT, na.rm = TRUE), .groups = "drop")
# Johnson-Neyman bounds from RT1.jn
jn_lower <- RT1.jn$bounds["Lower"]
jn_upper <- RT1.jn$bounds["Upper"]
# Base plot
ggplot(plot_data, aes(x = opt_cen, y = mean_RT, colour = accuStr, linetype = accuStr)) +
# Shaded region showing significant interaction effect
geom_rect(
inherit.aes = FALSE,
xmin = jn_lower, xmax = jn_upper,
ymin = -Inf, ymax = Inf,
fill = "gray80", alpha = 0.4
) +
# Add actual RT lines
geom_line(linewidth = 1.2) +
# Add data points
geom_point(size = 2, alpha = 0.8) +
# Labels and styling
labs(
title = "Observed RT across Optimism and Accuracy",
subtitle = paste0("Shaded region = Johnson-Neyman interval [", round(jn_lower, 2), ", ", round(jn_upper, 2), "]"),
x = "Optimism (centered)",
y = "Observed RT (ms)",
colour = "Accuracy",
linetype = "Accuracy"
) +
scale_color_manual(values = c("red", "darkcyan")) +
scale_linetype_manual(values = c("solid", "dashed")) +
theme_minimal(base_size = 14)
## Warning: Removed 14 rows containing missing values or values outside the scale range
## (`geom_rect()`).
# Results Behaviour on Trial N+1
# Align ID types before joining
data.main.postbeh$ID <- as.character(data.main.postbeh$ID)
data.pers.cen$ID <- as.character(data.pers.cen$ID)
# Calculate RT on trial N+1
data.main.postbeh <- data.main.postbeh %>%
arrange(ID, block, trial_index) %>%
group_by(ID) %>%
mutate(RT_trial_nplus = lead(RT)) %>%
ungroup()
# Remove existing opt_cen or worry_cen columns (with extensions)
data.main.postbeh <- data.main.postbeh %>%
select(-matches("^opt_cen"), -matches("^worry_cen"))
data.main.postbeh <- data.main.postbeh %>%
left_join(
data.pers.cen %>% select(ID, opt_cen, worry_cen),
by = "ID"
)
# Compute mean RT on trial N+1 grouped by previous accuracy
matrix.mean.PES <- data.main.postbeh %>%
filter(RT_trial_nplus != 999999) %>%
group_by(ID, accuStr) %>%
summarise(
m.RTnplus = mean(RT_trial_nplus, na.rm = TRUE),
se.RTnplus = sd(RT_trial_nplus, na.rm = TRUE) / sqrt(n()),
opt_cen = dplyr::first(opt_cen),
worry_cen = dplyr::first(worry_cen),
.groups = "drop"
)
# Keep only participants who have both error & correct trials
valid_ids <- matrix.mean.PES %>%
group_by(ID) %>%
summarise(n = n()) %>%
filter(n == 2) %>%
pull(ID)
matrix.mean.PES <- matrix.mean.PES %>%
filter(ID %in% valid_ids)
# factor order
matrix.mean.PES$accuStr <- factor(matrix.mean.PES$accuStr, levels = c("error", "correct"))
# Step 7: Pivot to wide format and compute PES
PES_data <- matrix.mean.PES %>%
select(ID, accuStr, m.RTnplus, opt_cen, worry_cen) %>%
pivot_wider(names_from = accuStr, values_from = m.RTnplus) %>%
mutate(
PES = error - correct # Post-error slowing
)
# Model PES ~ optimism and worry
summary(lm(PES ~ opt_cen + worry_cen, data = PES_data))
##
## Call:
## lm(formula = PES ~ opt_cen + worry_cen, data = PES_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -206.393 -30.560 -1.623 34.235 211.620
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.116 6.783 0.607 0.5455
## opt_cen 5.456 12.826 0.425 0.6716
## worry_cen -12.910 7.710 -1.675 0.0976 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 64.71 on 88 degrees of freedom
## Multiple R-squared: 0.03118, Adjusted R-squared: 0.009161
## F-statistic: 1.416 on 2 and 88 DF, p-value: 0.2481
# Summary of RT by accuracy
average.PES.Acc <- matrix.mean.PES %>%
group_by(accuStr) %>%
summarise(
m.PES = mean(m.RTnplus, na.rm = TRUE),
se.PES = sd(m.RTnplus, na.rm = TRUE) / sqrt(n()),
.groups = "drop"
)
# Summary of PES overall
average.PES.EV <- PES_data %>%
summarise(
meanRT = mean(PES, na.rm = TRUE),
seRT = sd(PES, na.rm = TRUE) / sqrt(n())
)
# Print summary
cat(paste0("Post-error slowing:\n",
"Error RT = ", round(average.PES.Acc$m.PES[average.PES.Acc$accuStr == "error"], 0), " ± ", round(average.PES.Acc$se.PES[average.PES.Acc$accuStr == "error"], 2), " ms\n",
"Correct RT = ", round(average.PES.Acc$m.PES[average.PES.Acc$accuStr == "correct"], 0), " ± ", round(average.PES.Acc$se.PES[average.PES.Acc$accuStr == "correct"], 2), " ms\n",
"PES (error – correct) = ", round(average.PES.EV$meanRT, 0), " ± ", round(average.PES.EV$seRT, 2), " ms\n"))
## Post-error slowing:
## Error RT = 894 ± 10.47 ms
## Correct RT = 890 ± 8.63 ms
## PES (error – correct) = 4 ± 6.81 ms
# Visual Diagnostics ===
# 1. Violin Plot (RT on trial n+1 by previous accuracy)
PES_long <- matrix.mean.PES %>%
mutate(PES = m.RTnplus)
ggplot(PES_long, aes(x = accuStr, y = PES, color = accuStr)) +
geom_violin(position = position_dodge(1), fill = NA) +
geom_boxplot(position = position_dodge(1), width = 0.1, outlier.shape = NA) +
geom_point(position = position_jitterdodge(jitter.width = 0.1, dodge.width = 1)) +
# ✅ Add mean ± SE (confidence bands)
stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
stat_summary(fun.data = mean_se, geom = "errorbar", width = 0.1, color = "black") +
scale_color_manual(values = c("#f20a19", "#3df50a")) +
labs(
x = "Accuracy Type (accuStr)",
y = "RT on Trial N+1",
color = "Accuracy"
) +
theme_classic()
# 2. Histogram of participant-level PES
ggplot(PES_data, aes(x = PES)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
geom_vline(xintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Distribution of Post-Error Slowing (PES) across Participants",
x = "PES (RT after error – RT after correct)",
y = "Count") +
theme_minimal()
# 3. RT on Trial N+1 by Previous Accuracy (full distribution)
ggplot(data.main.postbeh, aes(x = RT_trial_nplus, fill = accuStr)) +
geom_histogram(position = "identity", alpha = 0.5, bins = 50) +
facet_wrap(~accuStr) +
labs(title = "RT on Trial N+1 by Accuracy of Previous Trial",
x = "RT (ms)", y = "Count") +
theme_minimal()
## Warning: Removed 91 rows containing non-finite outside the scale range
## (`stat_bin()`).
##1. Visualize RT on trial N+1 grouped by previous trial accuracy (accuStr)
# RT on trial N+1
data.main <- data.main %>%
arrange(ID, trial_index) %>%
group_by(ID) %>%
mutate(RT_trial_nplus = lead(RT)) %>%
ungroup()
# Calculate mean RT_n+1 for each participant × accuStr
matrix.mean.PES_long <- data.main %>%
filter(!is.na(RT_trial_nplus)) %>%
group_by(ID, accuStr) %>%
summarise(
PES = mean(RT_trial_nplus, na.rm = TRUE),
opt_cen = first(opt_cen),
worry_cen = first(worry_cen),
.groups = "drop"
)
# Plot violin for trial N+1 RT by accuracy on trial N
ggplot(matrix.mean.PES_long, aes(x = accuStr, y = PES, color = accuStr)) +
geom_violin(fill = NA) +
geom_boxplot(width = 0.1, outlier.shape = NA) +
geom_point(position = position_jitter(width = 0.1), alpha = 0.7) +
labs(x = "Previous Trial Accuracy (accuStr)", y = "RT on Trial N+1 [ms]") +
theme_classic()
##2. Compute a single PES score per participant for modeling
#For regression: lm(PES ~ opt_cen * worry_cen)
# Create label for trial type (N)
data.main <- data.main %>%
mutate(Type_Nplus = case_when(
accuStr == "error" ~ "post_error",
accuStr == "correct" ~ "post_correct",
TRUE ~ NA_character_
))
# Compute PES as RT_post-error – RT_post-correct
matrix.mean.PES <- data.main %>%
filter(!is.na(RT_trial_nplus)) %>%
group_by(ID, Type_Nplus) %>%
summarise(
mean_RT = mean(RT_trial_nplus, na.rm = TRUE),
opt_cen = first(opt_cen),
worry_cen = first(worry_cen),
.groups = "drop"
) %>%
pivot_wider(names_from = Type_Nplus, values_from = mean_RT) %>%
mutate(PES = post_error - post_correct)
# Fit regression model
model_pes <- lm(PES ~ opt_cen * worry_cen, data = matrix.mean.PES)
summary(model_pes)
##
## Call:
## lm(formula = PES ~ opt_cen * worry_cen, data = matrix.mean.PES)
##
## Residuals:
## Min 1Q Median 3Q Max
## -207.969 -30.655 -2.613 33.282 212.719
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.659 7.213 0.646 0.520
## opt_cen 5.273 12.920 0.408 0.684
## worry_cen -12.854 7.756 -1.657 0.101
## opt_cen:worry_cen -2.947 12.750 -0.231 0.818
##
## Residual standard error: 65.06 on 87 degrees of freedom
## Multiple R-squared: 0.03177, Adjusted R-squared: -0.001613
## F-statistic: 0.9517 on 3 and 87 DF, p-value: 0.4194
# Optional plots
ggplot(matrix.mean.PES, aes(x = opt_cen, y = PES)) +
geom_point() +
geom_smooth(method = "lm") +
labs(title = "PES as a Function of Optimism",
x = "Optimism (Centered)", y = "Post-Error Slowing (ms)") +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
ggplot(matrix.mean.PES, aes(x = worry_cen, y = PES)) +
geom_point() +
geom_smooth(method = "lm") +
labs(title = "PES as a Function of Worry",
x = "Worry (Centered)", y = "Post-Error Slowing (ms)") +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
ggplot(matrix.mean.PES_long, aes(x = accuStr, y = PES, color = accuStr)) +
geom_violin(fill = NA) +
geom_boxplot(width = 0.1, outlier.shape = NA) +
geom_point(position = position_jitter(width = 0.1), alpha = 0.7) +
labs(
x = "Previous Trial Accuracy (accuStr)",
y = "RT on Trial N+1 [ms]",
color = "accuStr"
) +
theme_classic()
check_collinearity(model_pes)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## opt_cen 1.14 [1.03, 1.80] 1.07 0.88 [0.56, 0.98]
## worry_cen 1.14 [1.02, 1.81] 1.07 0.88 [0.55, 0.98]
## opt_cen:worry_cen 1.00 [1.00, Inf] 1.00 1.00 [0.00, 1.00]
collinearity_result <- check_collinearity(model_pes)
print(collinearity_result)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## opt_cen 1.14 [1.03, 1.80] 1.07 0.88 [0.56, 0.98]
## worry_cen 1.14 [1.02, 1.81] 1.07 0.88 [0.55, 0.98]
## opt_cen:worry_cen 1.00 [1.00, Inf] 1.00 1.00 [0.00, 1.00]
# plot results
if (require("see")) {
x <- check_collinearity(model_pes)
plot(x)
}
model_pes_full <- lm(PES ~ opt_cen * worry_cen, data = matrix.mean.PES)
summary(model_pes_full)
##
## Call:
## lm(formula = PES ~ opt_cen * worry_cen, data = matrix.mean.PES)
##
## Residuals:
## Min 1Q Median 3Q Max
## -207.969 -30.655 -2.613 33.282 212.719
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.659 7.213 0.646 0.520
## opt_cen 5.273 12.920 0.408 0.684
## worry_cen -12.854 7.756 -1.657 0.101
## opt_cen:worry_cen -2.947 12.750 -0.231 0.818
##
## Residual standard error: 65.06 on 87 degrees of freedom
## Multiple R-squared: 0.03177, Adjusted R-squared: -0.001613
## F-statistic: 0.9517 on 3 and 87 DF, p-value: 0.4194
ggplot(matrix.mean.PES, aes(x = opt_cen, y = PES, color = worry_cen)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation:
## colour.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
colnames(PES_data)
## [1] "ID" "opt_cen" "worry_cen" "correct" "error" "PES"
matrix.mean.PES <- matrix.mean.PES %>%
mutate(accuStr = "delta")
ggplot(matrix.mean.PES, aes(x = "delta", y = PES)) +
geom_violin(fill = "skyblue", alpha = 0.3) +
geom_boxplot(width = 0.1, fill = "white") +
geom_point(position = position_jitter(width = 0.1), alpha = 0.7) +
stat_summary(fun = mean, geom = "crossbar", width = 0.3, color = "black") +
labs(x = "", y = "Post-Error Slowing (PES) [ms]") +
theme_classic()
# prepare table
matrix.mean.PEA <- data.main.postbeh %>%
group_by(ID, accuStr ) %>%
summarise(correct = mean(correct_nplus)*100,
opt_cen = mean(opt_cen),
worry_cen = mean(worry_cen)
) %>%
ungroup()
# Difference
matrix.delta.PEA <- matrix.mean.PEA %>%
group_by(ID) %>%
summarise(correct = diff(correct),
opt_cen = first(opt_cen),
worry_cen = first(worry_cen)) %>%
ungroup()
data.main.postbeh <- data.main.postbeh %>%
mutate(
accuNum = ifelse(accuStr == "correct", -0.5, 0.5),
)
matrix.mean.PEA.b <- data.main.postbeh %>%
group_by(ID, accuNum) %>%
summarise(
correct = mean(correct_nplus, na.rm = TRUE) * 100,
opt_cen = mean(opt_cen, na.rm = TRUE),
worry_cen = mean(worry_cen, na.rm = TRUE)
) %>%
ungroup()
PEA.c.lmeb <- lmer(
correct ~ accuNum * opt_cen * worry_cen + (1 | ID),
data = matrix.mean.PEA.b
)
# Define standard error function
stderror <- function(x) {
x <- x[!is.na(x)]
sd(x) / sqrt(length(x))
}
# Compute mean and SE by accuracy type
average.PEA.Acc <- matrix.mean.PEA %>%
group_by(accuStr) %>%
summarise(
m.correct = mean(correct, na.rm = TRUE),
se.correct = stderror(correct),
.groups = "drop"
)
tab_model(PEA.c.lmeb, dv.labels = "Post-Response Accuracy")
| Post-Response Accuracy | |||
|---|---|---|---|
| Predictors | Estimates | CI | p |
| (Intercept) | 84.76 | 82.41 – 87.11 | <0.001 |
| accuNum | 0.11 | -1.66 – 1.88 | 0.900 |
| opt cen | 0.20 | -4.01 – 4.41 | 0.926 |
| worry cen | -0.85 | -3.38 – 1.67 | 0.506 |
| accuNum × opt cen | -1.38 | -4.55 – 1.79 | 0.392 |
| accuNum × worry cen | 1.27 | -0.64 – 3.17 | 0.191 |
| opt cen × worry cen | -0.63 | -4.79 – 3.52 | 0.765 |
|
(accuNum × opt cen) × worry cen |
-0.54 | -3.67 – 2.59 | 0.736 |
| Random Effects | |||
| σ2 | 32.73 | ||
| τ00 ID | 99.04 | ||
| ICC | 0.75 | ||
| N ID | 91 | ||
| Observations | 182 | ||
| Marginal R2 / Conditional R2 | 0.008 / 0.754 | ||
paste0("Post response accuracy: ",
paste(average.PEA.Acc$accuStr,
round(average.PEA.Acc$m.correct, 2),
" ± ",
round(average.PEA.Acc$se.correct, 2),
"%",
collapse = "; "))
## [1] "Post response accuracy: correct 78.48 ± 3.9 %; error 84.71 ± 1.43 %"
PEA.c.lm <- lm(correct ~ accuStr * opt_cen * worry_cen, data = matrix.mean.PEA)
summary(PEA.c.lm)
##
## Call:
## lm(formula = correct ~ accuStr * opt_cen * worry_cen, data = matrix.mean.PEA)
##
## Residuals:
## Min 1Q Median 3Q Max
## -44.971 -7.044 3.175 9.335 16.353
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 78.969 3.810 20.726 <2e-16 ***
## accuStrerror 5.932 4.116 1.441 0.153
## opt_cen -10.350 15.466 -0.669 0.505
## worry_cen -4.944 3.502 -1.412 0.161
## accuStrerror:opt_cen 9.967 15.706 0.635 0.527
## accuStrerror:worry_cen 4.697 3.883 1.209 0.230
## opt_cen:worry_cen -15.270 15.126 -1.010 0.315
## accuStrerror:opt_cen:worry_cen 14.287 15.364 0.930 0.355
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 13.65 on 92 degrees of freedom
## (82 observations deleted due to missingness)
## Multiple R-squared: 0.0571, Adjusted R-squared: -0.01465
## F-statistic: 0.7958 on 7 and 92 DF, p-value: 0.5927
# binary - required for JN-procedure
PEA.c.lmeb <- lmer(correct ~ accuNum * opt_cen * worry_cen + (1 | ID), matrix.mean.PEA.b)
lm(correct ~ accuStr * opt_cen + accuStr * worry_cen + opt_cen * worry_cen, data = matrix.mean.PEA)
##
## Call:
## lm(formula = correct ~ accuStr * opt_cen + accuStr * worry_cen +
## opt_cen * worry_cen, data = matrix.mean.PEA)
##
## Coefficients:
## (Intercept) accuStrerror opt_cen
## 78.809 6.178 -4.827
## worry_cen accuStrerror:opt_cen accuStrerror:worry_cen
## -4.770 4.416 4.534
## opt_cen:worry_cen
## -1.421
lm(correct ~ accuStr + opt_cen + worry_cen, data = matrix.mean.PEA)
##
## Call:
## lm(formula = correct ~ accuStr + opt_cen + worry_cen, data = matrix.mean.PEA)
##
## Coefficients:
## (Intercept) accuStrerror opt_cen worry_cen
## 78.564101 6.139520 -0.002958 -1.091992
#Visualizing Interactions
interact_plot(
model = lm(correct ~ accuStr + opt_cen + worry_cen, data = matrix.mean.PEA),
pred = "opt_cen",
modx = "accuStr",
plot.points = TRUE
)
## Warning: opt_cen and accuStr are not included in an interaction with one another in
## the model.
#Scatter-plot
ggplot(matrix.mean.PEA, aes(x = opt_cen, y = correct)) +
geom_point() +
geom_smooth(method = "lm") +
labs(
title = "Accuracy vs. Optimism",
x = "Optimism (centered)",
y = "Post-response Accuracy (%)"
) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 82 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 82 rows containing missing values or values outside the scale range
## (`geom_point()`).
#JN Plot
johnson_neyman(
model = PEA.c.lmeb,
pred = "opt_cen",
modx = "accuNum",
mod.range = c(-0.5, 0.5)
)
## JOHNSON-NEYMAN INTERVAL
##
## The Johnson-Neyman interval could not be found. Is the p value for your
## interaction term below the specified alpha?
# Assuming matrix.mean.PEA.b contains 'opt_cen' and 'correct'
ggplot(matrix.mean.PEA.b, aes(x = opt_cen, y = correct)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", color = "steelblue", fill = "lightgray") +
labs(
title = "Effect of Optimism on Post-Response Accuracy",
x = "Optimism (centered)",
y = "Post-response Accuracy (%)"
) +
theme_classic()
## `geom_smooth()` using formula = 'y ~ x'
check_collinearity(PEA.c.lm)
## Model has interaction terms. VIFs might be inflated.
## Try to center the variables used for the interaction, or check
## multicollinearity among predictors of a model without interaction terms.
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## accuStr 1.03 [ 1.00, 24.40] 1.01 0.97 [0.04, 1.00]
##
## Moderate Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
## worry_cen 6.03 [ 4.42, 8.40] 2.46 0.17 [0.12, 0.23]
## accuStr:worry_cen 6.17 [ 4.52, 8.60] 2.48 0.16 [0.12, 0.22]
##
## High Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance
## opt_cen 38.09 [26.98, 53.96] 6.17 0.03
## accuStr:opt_cen 38.06 [26.95, 53.92] 6.17 0.03
## opt_cen:worry_cen 33.34 [23.63, 47.21] 5.77 0.03
## accuStr:opt_cen:worry_cen 33.26 [23.58, 47.10] 5.77 0.03
## Tolerance 95% CI
## [0.02, 0.04]
## [0.02, 0.04]
## [0.02, 0.04]
## [0.02, 0.04]
# plot results
if (require("see")) {
x <- check_collinearity(PEA.c.lm)
plot(x)
}
## Model has interaction terms. VIFs might be inflated.
## Try to center the variables used for the interaction, or check
## multicollinearity among predictors of a model without interaction terms.
#Newest code
# One-time reassignment of accuStr from accuNum
matrix.mean.PEA.b <- matrix.mean.PEA.b %>%
mutate(accuStr = factor(ifelse(accuNum == -0.5, "correct", "error")))
# Clean simple slope plot
ggplot(matrix.mean.PEA.b, aes(x = opt_cen, y = correct, color = accuStr, linetype = accuStr)) +
geom_point(alpha = 0.5) +
geom_smooth(method = "lm", se = TRUE, alpha = 0.1) +
# Optional JN range shading (adjust bounds if needed)
geom_rect(
inherit.aes = FALSE,
fill = "lightgray",
alpha = 0.2,
xmin = -0.2, xmax = 0.4,
ymin = -Inf, ymax = Inf
) +
labs(
title = "Simple Slopes: Optimism Predicting Accuracy by Previous Trial",
x = "Optimism (centered)",
y = "Post-Response Accuracy (%)",
color = "Previous Accuracy",
linetype = "Previous Accuracy"
) +
scale_color_grey(start = 0, end = 0.2) +
scale_linetype_manual(values = c("solid", "longdash")) +
theme_classic()
## `geom_smooth()` using formula = 'y ~ x'
# Run separate models for each condition
lm(correct ~ opt_cen, data = matrix.mean.PEA.b %>% filter(accuStr == "correct"))
##
## Call:
## lm(formula = correct ~ opt_cen, data = matrix.mean.PEA.b %>%
## filter(accuStr == "correct"))
##
## Coefficients:
## (Intercept) opt_cen
## 84.63992 0.04573
lm(correct ~ opt_cen, data = matrix.mean.PEA.b %>% filter(accuStr == "error"))
##
## Call:
## lm(formula = correct ~ opt_cen, data = matrix.mean.PEA.b %>%
## filter(accuStr == "error"))
##
## Coefficients:
## (Intercept) opt_cen
## 84.6546 -0.5729
###########
# Archived Code
# 1. Compute prediction grid
limits <- max(abs(c(
range(data.main$opt_cen, na.rm = TRUE),
range(data.main$worry_cen, na.rm = TRUE)
)))
b <- seq(-0.5 - limits, 0.5 + limits, by = 0.1)
data.pred <- expand.grid(
ID = unique(data.main$ID),
accuStr = c("correct", "error"),
worry_cen = 0, # hold constant
opt_cen = b
) %>%
mutate(
accuStr = factor(accuStr, levels = c("correct", "error")),
accuNum = ifelse(accuStr == "correct", -0.5, 0.5)
)
# 2. Predict using lmer model
data.pred$PEA_correct <- predict(PEA.c.lmeb, newdata = data.pred, re.form = NA)
# 4. Plot
matrix.mean.PEA.b <- matrix.mean.PEA.b %>%
mutate(
accuStr = factor(ifelse(accuNum == -0.5, "correct", "error"))
)
library(ggplot2)
ggplot(matrix.mean.PEA.b, aes(x = opt_cen, y = correct, color = accuStr, linetype = accuStr)) +
geom_smooth(method = "lm", se = TRUE, alpha = 0.1) +
labs(
x = "Optimism (centered)",
y = "Post-Response Accuracy (%)",
color = "Previous Accuracy",
linetype = "Previous Accuracy"
) +
scale_color_grey(start = 0, end = 0.2) +
scale_linetype_manual(values = c("solid", "longdash")) +
theme_classic()
## `geom_smooth()` using formula = 'y ~ x'
ggplot(matrix.mean.PEA.b, aes(x = opt_cen, y = correct, color = as.factor(accuNum))) +
geom_point() +
geom_smooth(method = "lm") +
labs(
title = "Post-response Accuracy by Optimism and Previous Accuracy",
x = "Optimism (centered)",
y = "Post-response Accuracy (%)",
color = "Previous Accuracy"
) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
# Clean version (run mutate only once)
matrix.mean.PEA.b <- matrix.mean.PEA.b %>%
mutate(accuStr = factor(ifelse(accuNum == -0.5, "correct", "error")))
# Plot
ggplot(matrix.mean.PEA.b, aes(x = opt_cen, y = correct, color = accuStr, linetype = accuStr)) +
geom_point(alpha = 0.5) +
geom_smooth(method = "lm", se = TRUE, alpha = 0.1) +
geom_rect(
inherit.aes = FALSE,
fill = "lightgray",
alpha = 0.2,
xmin = -0.2, xmax = 0.4, # Adjust this manually based on JN output
ymin = -Inf, ymax = Inf
) +
labs(
title = "Simple Slopes: Optimism Predicting Post-Response Accuracy",
x = "Optimism (centered)",
y = "Accuracy (%)",
color = "Previous Accuracy",
linetype = "Previous Accuracy"
) +
theme_classic()
## `geom_smooth()` using formula = 'y ~ x'
geom_rect(
inherit.aes = FALSE,
fill = "lightgray",
alpha = 0.2,
xmin = -0.2, xmax = 0.4,
ymin = -Inf, ymax = Inf
)
## geom_rect: linejoin = mitre, na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity
# Report simple slopes
lm(correct ~ opt_cen, data = matrix.mean.PEA.b %>% filter(accuStr == "correct"))
##
## Call:
## lm(formula = correct ~ opt_cen, data = matrix.mean.PEA.b %>%
## filter(accuStr == "correct"))
##
## Coefficients:
## (Intercept) opt_cen
## 84.63992 0.04573
lm(correct ~ opt_cen, data = matrix.mean.PEA.b %>% filter(accuStr == "error"))
##
## Call:
## lm(formula = correct ~ opt_cen, data = matrix.mean.PEA.b %>%
## filter(accuStr == "error"))
##
## Coefficients:
## (Intercept) opt_cen
## 84.6546 -0.5729
# Fig. RT interaction -- mean distributions
col4 <- c("#3df50a", "#f20a19") # green and red for correct/error
ggplot(matrix.mean.RT, aes(x = accuStr, y = mRT, color = accuStr)) +
geom_violin(position = position_dodge(1), fill = NA) +
geom_boxplot(position = position_dodge(1), width = 0.1, outlier.shape = NA) +
geom_point(position = position_jitterdodge(jitter.width = 0.1, dodge.width = 1), alpha = 0.7) +
scale_color_manual(values = col4) +
labs(
x = "Previous Trial Accuracy",
y = "Response Time [ms]",
color = "Accuracy"
) +
theme_classic()
##block_wise
data.correct <- data.main.postbeh %>%
filter(accuStr == "correct")
matrix.RT.block.correct <- data.correct %>%
group_by(ID, block) %>%
summarise(
m.RT = mean(RT, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(blockNEW = as.factor(block))
ggplot(matrix.RT.block.correct, aes(x = blockNEW, y = m.RT)) +
geom_violin(fill = "lightblue", alpha = 0.3) +
geom_boxplot(width = 0.1, outlier.shape = NA) +
geom_point(position = position_jitter(width = 0.1), alpha = 0.6) +
labs(
x = "Block",
y = "Response Time [ms]"
) +
theme_classic()
stderror <- function(x) {
x <- x[!is.na(x)]
sd(x) / sqrt(length(x))
}
# Grand average: Ne by accuracy
average.Ne4.Acc <- matrix.mean.erp %>%
group_by(acc) %>%
summarise(
m.Ne = mean(Ne, na.rm = TRUE),
se.Ne = stderror(Ne)
) %>%
ungroup()
# Grand average: overall Ne
average.Ne4.oa <- matrix.mean.erp %>%
summarise(
m.Ne = mean(Ne, na.rm = TRUE),
se.Ne = stderror(Ne)
)
# Print result
paste("Mean Ne by accuracy:",
paste(average.Ne4.Acc$acc, round(average.Ne4.Acc$m.Ne, 3), "+/-", round(average.Ne4.Acc$se.Ne, 3), "µV/cm²", collapse = "; "))
## [1] "Mean Ne by accuracy: correct -14.855 +/- 1.002 µV/cm²; error -24.793 +/- 1.793 µV/cm²"
Ne4.0.lme <- lmer(Ne ~ acc * opt_cen * worry_cen + (1 | ID), matrix.mean.erp)
tab_summary(Ne4.0.lme, pred.labels = predictors.all, dv.labels = "Ne/c Amplitude Effects")
| Ne/c Amplitude Effects | |||||
|---|---|---|---|---|---|
| Predictors | Estimate | SE | df | t | p |
| (Intercept) | -19.56 | 1.29 | 87.00 | -15.11 | <0.001 |
| acc1 | -9.43 | 1.65 | 87.00 | -5.72 | <0.001 |
| Optimism | -3.16 | 2.32 | 87.00 | -1.36 | 0.177 |
| Worry | 2.18 | 1.39 | 87.00 | 1.57 | 0.120 |
| acc1:opt_cen | -2.01 | 2.95 | 87.00 | -0.68 | 0.499 |
| acc1:worry_cen | 2.23 | 1.77 | 87.00 | 1.26 | 0.212 |
| Response Type x Worry | -1.44 | 2.29 | 87.00 | -0.63 | 0.529 |
| acc1:opt_cen:worry_cen | -2.77 | 2.91 | 87.00 | -0.95 | 0.345 |
| Random Effects | |||||
| σ2 | 110.50 | ||||
| τ00 ID | 81.03 | ||||
| ICC | 0.42 | ||||
| N ID | 91 | ||||
| Observations | 182 | ||||
| Marginal R2 / Conditional R2 | 0.142 / 0.505 | ||||
check_collinearity(Ne4.0.lme )
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance
## acc 1.12 [1.03, 1.50] 1.06 0.89
## opt_cen 1.14 [1.04, 1.49] 1.07 0.88
## worry_cen 1.14 [1.04, 1.49] 1.07 0.88
## acc:opt_cen 1.14 [1.04, 1.49] 1.07 0.88
## acc:worry_cen 1.14 [1.04, 1.49] 1.07 0.88
## opt_cen:worry_cen 1.00 [1.00, 9.71e+12] 1.00 1.00
## acc:opt_cen:worry_cen 1.12 [1.03, 1.49] 1.06 0.89
## Tolerance 95% CI
## [0.67, 0.97]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.00, 1.00]
## [0.67, 0.97]
# plot results
if (require("see")) {
x <- check_collinearity(Ne4.0.lme)
plot(x)
}
# --- POST HOC: 3-way Interaction Plot (Optimism × Worry × Accuracy Effect on Ne) ---
library(lme4)
Ne3.lme <- lmer(Ne ~ acc * opt_cen * worry_cen + (1 | ID), data = matrix.mean.erp)
# Generate interaction surface using lab-specific function (you should already have it)
ssp3 <- list(
Ne3 = sim_slope_plot_3x(Ne3.lme, steps = 0.05)
)
# Customize plot appearance
ssp3$Ne3 <- ssp3$Ne3 +
scale_fill_gradient2(
limits = c(-0.3, 0.3),
low = muted("blue"),
mid = "white",
high = muted("red"),
name = "Accuracy Effect"
) +
theme(legend.position = "bottom") +
labs(x = "Optimism (centered)", y = "Worry (centered)")
## Scale for fill is already present.
## Adding another scale for fill, which will replace the existing scale.
# Show the final plot
cowplot::plot_grid(ssp3$Ne3)
#Optimism × Accuracy interaction only (Ne amplitude)
Ne_opt.lme <- lmer(m.Ne ~ acc * opt_cen + (1 | ID), data = matrix.mean.Ne3)
interact_plot(Ne3.lme, pred = "opt_cen", modx = "acc",
interval = TRUE, plot.points = TRUE,
y.label = "Ne Amplitude (µV/cm²)",
x.label = "Optimism (centered)",
modx.labels = c("Correct", "Error")) +
theme_classic()
matrix.mean.Pe2 <- matrix.mean.erp %>%
group_by(ID) %>%
summarise(m.Pe = mean(Pe),
se.Pe = sd(Pe),
opt_cen =first(opt_cen),
worry_cen = first(worry_cen)) %>%
ungroup()
#grand average : accuracy
average.Pe.Acc <- matrix.mean.erp %>%
group_by(acc) %>%
summarise(m.Pe = mean(Pe),
se.Pe = stderror(Pe)) %>%
ungroup()
paste("mean Pe/c:", average.Pe.Acc$acc, round(average.Pe.Acc$m.Pe, 3), "+/-",round(average.Pe.Acc$se.Pe, 3), "µV/cm²")
## [1] "mean Pe/c: correct 14.231 +/- 1.57 µV/cm²"
## [2] "mean Pe/c: error 32.827 +/- 2.087 µV/cm²"
Pe4.lme <- lmer(Pe ~ acc * opt_cen * worry_cen + (1 | ID), matrix.mean.erp)
tab_summary(Pe4.lme, pred.labels = predictors.all, dv.labels = "Pe/c Amplitude Effects")
| Pe/c Amplitude Effects | |||||
|---|---|---|---|---|---|
| Predictors | Estimate | SE | df | t | p |
| (Intercept) | 23.44 | 1.65 | 87.00 | 14.17 | <0.001 |
| acc1 | 19.13 | 2.14 | 87.00 | 8.96 | <0.001 |
| Optimism | -0.88 | 2.96 | 87.00 | -0.30 | 0.766 |
| Worry | -1.75 | 1.78 | 87.00 | -0.99 | 0.327 |
| acc1:opt_cen | 2.21 | 3.83 | 87.00 | 0.58 | 0.566 |
| acc1:worry_cen | -2.50 | 2.30 | 87.00 | -1.09 | 0.280 |
| Response Type x Worry | 0.49 | 2.92 | 87.00 | 0.17 | 0.867 |
| acc1:opt_cen:worry_cen | -2.91 | 3.78 | 87.00 | -0.77 | 0.444 |
| Random Effects | |||||
| σ2 | 185.65 | ||||
| τ00 ID | 129.63 | ||||
| ICC | 0.41 | ||||
| N ID | 91 | ||||
| Observations | 182 | ||||
| Marginal R2 / Conditional R2 | 0.227 / 0.545 | ||||
check_collinearity(Pe4.lme)
## # Check for Multicollinearity
##
## Low Correlation
##
## Term VIF VIF 95% CI adj. VIF Tolerance
## acc 1.12 [1.03, 1.50] 1.06 0.89
## opt_cen 1.14 [1.04, 1.49] 1.07 0.88
## worry_cen 1.14 [1.04, 1.49] 1.07 0.88
## acc:opt_cen 1.14 [1.04, 1.49] 1.07 0.88
## acc:worry_cen 1.14 [1.04, 1.49] 1.07 0.88
## opt_cen:worry_cen 1.00 [1.00, 9.71e+12] 1.00 1.00
## acc:opt_cen:worry_cen 1.12 [1.03, 1.49] 1.06 0.89
## Tolerance 95% CI
## [0.67, 0.97]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.67, 0.96]
## [0.00, 1.00]
## [0.67, 0.97]
# plot results
if (require("see")) {
x <- check_collinearity(Pe4.lme)
plot(x)
}
# within interaction post hoc comparison
emmeans(Pe4.lme, specs = c("acc")) %>%
pairs()
## NOTE: Results may be misleading due to involvement in interactions
## contrast estimate SE df t.ratio p.value
## correct - error -19.1 2.14 87 -8.955 <.0001
##
## Degrees-of-freedom method: kenward-roger
Pe2.lm <- lm(m.Pe ~ opt_cen * worry_cen + (1 | ID), matrix.mean.Pe2)
tab_summary(Pe2.lm, pred.labels = predictors.opt, dv.labels = "Pe/c Amplitude Effects")
## Model matrix is rank deficient. Parameters `1 | IDTRUE` were not
## estimable.
## `ci_method` must be one of "residual", "wald", "normal", "profile",
## "boot", "uniroot", "betwithin" or "ml1". Using "wald" now.
| Pe/c Amplitude Effects | |||||
|---|---|---|---|---|---|
| Predictors | Estimate | SE | df | t | p |
| (Intercept) | 23.44 | 1.65 | 87.00 | 14.17 | <0.001 |
| Optimism | -0.88 | 2.96 | 87.00 | -0.30 | 0.766 |
| Worry | -1.75 | 1.78 | 87.00 | -0.99 | 0.327 |
| Worry x Optimism | 0.49 | 2.92 | 87.00 | 0.17 | 0.867 |
| Observations | 91 | ||||
| R2 / R2 adjusted | 0.017 / -0.017 | ||||
sspe2 <- list(Pe2= sim_slope_plot_2x(Pe2.lm, steps = 0.05, thresholding = TRUE, center = TRUE))
sspe2$Pe2 <- sspe2$Pe2 +
scale_fill_gradient2(limits = c(-0.3,0.3), low = muted("blue"), mid = "white", high = muted("red"), name = "Deviation from Mean Pe-Amplitue") +
theme(legend.position = "bottom") + labs(x = "Optimism (centered)", y = "Worry (centered)")
## Scale for fill is already present.
## Adding another scale for fill, which will replace the existing scale.
cowplot::plot_grid(sspe2$Pe2 )
# Required libraries
library(ggplot2)
# Plot Pe amplitude by optimism (centered)
ggplot(matrix.mean.Pe2, aes(x = opt_cen, y = m.Pe)) +
geom_point(color = "black", alpha = 0.6) +
geom_smooth(method = "lm", formula = y ~ poly(x, 2), se = TRUE, color = "blue") +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray") +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray") +
labs(
title = "Pe Amplitude by Optimism (Centered)",
x = "Optimism (centered)",
y = "Pe Amplitude (µV/cm²)"
) +
theme_classic()