library(data.table)
library(here)
library(magrittr)
library(ggplot2)
library(viridis)
library(binhf)
library(pwr)
library(knitr)
library(kableExtra)
library(sdamr)
library(gghalves)
library(lme4)
library(emmeans)
library(papeR)
library(ggtext)
library(MASS)
# Get directory of repository
base_path = here::here()

# Load pre-written functions
source_path = file.path(base_path, 'code', 'utils',
                        fsep = .Platform$file.sep)
source_files = list.files(source_path, pattern = "[.][rR]$",
                          full.names = TRUE, recursive = TRUE)
invisible(lapply(source_files, function(x) source(x)))

# Get plot colors/linetypes
custom_guides = Get_plot_guides()
# Load data
data = Load_data() %>%
  # Apply exclusion criteria (see R notebook 'data_quality.Rmd' for details)
  Apply_exclusion_criteria(., choice_based_exclusion = TRUE) %>%
  # Add bandit comparison type (i.e. 1v2, 2v3, 1v3, or low-mid, mid-high, low-high, respectively)
  Add_comp(.) %>%
  .[, run := as.factor(run)]

# Get number of participants (to adjust figure height for some figures)
n_participants = length(unique(data$participant_id))

Quick look at demographics

data_demo = data %>%
  # Rename group factors
  Prepare_data_for_plot(.) %>%
  # Summarize data
  .[, .(country_of_birth = unique(country_of_birth),
        curent_country_of_residence = unique(curent_country_of_residence),
        employment_status = unique(employment_status),
        nationality = unique(nationality),
        sex = unique(sex),
        age = unique(age)),
    by = c('participant_id', 'group')] %>%
  # Summarize separately for groups
  .[, .(min_age = min(age),
        max_age = max(age),
        mean_age = mean(age),
        sd_age = sd(age),
        n_male = length(which(sex == 'Male')),
        n_female = length(which(sex == 'Female'))),
    by = c('group')]

# Display table
knitr::kable(data_demo) %>%
  kableExtra::kable_styling()
group min_age max_age mean_age sd_age n_male n_female
Younger adults 19 30 24.37255 3.364288 25 26
Older adults 50 73 56.98039 6.081086 26 25

Overall performance

# Percentage of optimal choices
check_noc = data %>%
  # Add trial variable
  .[, trial := seq(.N),
    by = c('participant_id', 'run')] %>%
  # Select only free choices (no guided choices)
  .[trial_type == 'choice',] %>%
  # Set correct choice by selecting (on average) higher outcome bandit
  .[, correct_choice := if(option_left > option_right) 'left' else 'right',
    by = c('participant_id', 'run', 'trial')] %>%
  .[, correct := correct_choice == choice] %>%
  # Get percentage of correct choices (exclude timeouts from overall trials)
  .[, .(perc_correct = sum(as.numeric(correct), na.rm = TRUE) / length(which(!is.na(as.numeric(correct))))),
    by = c('participant_id', 'group', 'run', 'comp')]

Plot

data_plot = Prepare_data_for_plot(check_noc)
p_noc_diff_comp_line = ggplot(data = data_plot,
                              aes(x = comp,
                                  y = perc_correct,
                                  color = group,
                                  fill = group)) +
  geom_point(size = 0.2,
             position = position_jitterdodge(dodge.width = 0.3,
                                             jitter.width = 0.2,
                                             jitter.height = 0)) +
  geom_boxplot(width = 0.15,
               color = 'black',
               outlier.shape = NA,
               position = position_dodge(width = 0.3),
               show.legend = FALSE) +
  stat_summary(fun = 'mean',
               geom = 'point',
               na.rm = TRUE,
               shape = 23,
               fill = 'white',
               size = 1.5,
               stroke = 0.5,
               position = position_dodge(width = 0.3)) +
  scale_color_manual(values = custom_guides) +
  scale_fill_manual(values = custom_guides) +
  facet_grid( ~ run) +
  theme(strip.text.y = element_text(angle = 0),
        legend.position = 'none')
Neurocodify_plot(p_noc_diff_comp_line)

Stats

LMM correct choice probabilities (manuscript ll. 307)

# Assure data types
check_noc$participant_id = as.factor(check_noc$participant_id)
check_noc$group = as.factor(check_noc$group)
check_noc$run = as.factor(check_noc$run)
check_noc$comp = as.factor(check_noc$comp)
check_noc$perc_correct = as.numeric(check_noc$perc_correct)

# LME
# Model percent of correct choices by group, run, and bandit comparison
lme_noc = lme4::lmer(data = check_noc,
                     perc_correct ~ group * run * comp + (1 | participant_id))

# Display results
papeR::prettify(Anova(lme_noc))
##                        Chisq Df Pr(>Chisq)    
## 1          group   3.3922250  1      0.066   .
## 2            run  12.0611640  1      0.001 ***
## 3           comp 155.3317818  2     <0.001 ***
## 4      group:run   0.8738976  1       0.35    
## 5     group:comp   2.3804690  2      0.304    
## 6       run:comp   0.4529598  2      0.797    
## 7 group:run:comp   0.1747806  2      0.916

Main effect run

Manuscript ll. 308/310

emmeans::emmeans(lme_noc,
                 pairwise ~ run)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  run emmean      SE  df lower.CL upper.CL
##  1    0.848 0.00954 160    0.829    0.867
##  2    0.879 0.00954 160    0.860    0.898
## 
## Results are averaged over the levels of: group, comp 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast estimate      SE  df t.ratio p.value
##  1 - 2     -0.0307 0.00884 500  -3.473  0.0006
## 
## Results are averaged over the levels of: group, comp 
## Degrees-of-freedom method: kenward-roger

Main effect comp

Manuscript ll. 308/311

emmeans::emmeans(lme_noc,
                 pairwise ~ comp)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  comp emmean     SE  df lower.CL upper.CL
##  1v2   0.792 0.0105 226    0.771    0.813
##  2v3   0.872 0.0105 226    0.852    0.893
##  1v3   0.926 0.0105 226    0.905    0.947
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast  estimate     SE  df t.ratio p.value
##  1v2 - 2v3  -0.0803 0.0108 500  -7.420  <.0001
##  1v2 - 1v3  -0.1340 0.0108 500 -12.382  <.0001
##  2v3 - 1v3  -0.0537 0.0108 500  -4.962  <.0001
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger 
## P value adjustment: tukey method for comparing a family of 3 estimates

Robust regression (due to outliers)

Manuscript ll. 320-323

# Get correct choices
data_rr = data %>%
  .[, trial := seq(.N),
    by = c('participant_id', 'run')] %>%
  .[trial_type == 'choice', correct := if(option_left > option_right) 'left' else 'right',
    by = c('participant_id', 'run', 'trial')] %>%
  .[trial_type == 'forced', correct := forced,
    by = c('participant_id', 'run', 'trial')] %>%
  # Exclude time-out trials
  .[!is.na(outcome), correct_choice := choice == correct,
    by = c('participant_id', 'run', 'trial')]

# Average correct choices
data_rr_mean_correct = data_rr %>%
  # Only consider free choices
  .[trial_type == 'choice', ] %>%
  # Average over runs
  .[, .(mean_correct = mean(correct_choice, na.rm = TRUE),
        sd_correct = sd(correct_choice, na.rm = TRUE)),
    by = c('participant_id', 'group', 'run', 'comp')] %>%
  # Sort for easier checks
  .[order(rank(comp)),] %>%
  # Wide format to calculate differences
  data.table::dcast(participant_id + group + run ~ paste0('comp_', comp),
                    value.var = 'mean_correct') %>%
  # Get difference between critical comparisons
  .[, bandit_effect := comp_2v3 - comp_1v2] %>%
  # Average over runs
  .[, .(mean_bandit_effect = mean(bandit_effect, na.rm = TRUE)),
    by = c('participant_id', 'group')] %>%
  # Convert group to factor
  .[, ':='(group = as.factor(group),
           participant_id = as.factor(participant_id))]

Plot including outliers of OA

data_plot = Prepare_data_for_plot(data_rr_mean_correct) 

file = file.path(base_path,
                 'derivatives',
                 'figures',
                 'f_pc_diff.tsv',
                 fsep = .Platform$file.sep)
data.table::fwrite(data_plot,
                   file,
                   sep = '\t',
                   na = 'n/a',
                   row.names = FALSE)

nudge_with = 0.15

p = ggplot(data = data_plot,
           aes(x = group,
               y = mean_bandit_effect,
               color = group,
               fill = group)) +
  geom_hline(yintercept = 0,
             size = 0.5) +
  geom_point(position = sdamr::position_jitternudge(nudge.x = -nudge_with,
                                                    jitter.width = 0.05,
                                                    jitter.height = 0,
                                                    seed = 666),
             alpha = 0.5) +
  gghalves::geom_half_violin(side = 'r',
                             width = 0.3,
                             position = position_nudge(x = nudge_with)) +
  geom_boxplot(outlier.shape = NA,
               color = 'black',
               width = 0.1) +
  stat_summary(fun = 'mean',
               geom = 'point',
               na.rm = TRUE,
               shape = 23,
               inherit.aes = TRUE,
               fill = 'white',
               size = 1.5,
               stroke = 0.5,
               show.legend = FALSE) +
  scale_color_manual(values = custom_guides) +
  scale_fill_manual(values = custom_guides)
  
Neurocodify_plot(p)

First: Standard OLS

# Standard OLS
ols = lm(mean_bandit_effect ~ 1 + group,
         data = data_rr_mean_correct)
summary(ols)
## 
## Call:
## lm(formula = mean_bandit_effect ~ 1 + group, data = data_rr_mean_correct)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.59600 -0.08583 -0.00690  0.09075  0.38143 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   0.09675    0.02009   4.814 5.25e-06 ***
## groupyounger -0.03287    0.02842  -1.156     0.25    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1435 on 100 degrees of freedom
## Multiple R-squared:  0.0132, Adjusted R-squared:  0.00333 
## F-statistic: 1.337 on 1 and 100 DF,  p-value: 0.2502
Plot residuals, QQ, weighting, and leverage
# Plot residuals, QQ, weighting, and leverage
opar <- par(mfrow = c(2,2), oma = c(0, 0, 1.1, 0))
plot(ols, las = 1)

Display by cooks distance
# Display values with large cook's distance
cooks_d <- cooks.distance(ols)
r <- stdres(ols)
a <- cbind(data_rr_mean_correct, cooks_d, r)
# Conventional cut-off (https://stats.oarc.ucla.edu/r/dae/robust-regression/)
cutoff = 4/nrow(data_rr_mean_correct)
# Only absolute residuals matter
a = a %>%
  .[, ':='(abs_r = abs(r),
           cutoff = cooks_d > cutoff)] %>%
  .[order(rank(-abs_r))]

# Notice the participants beyond the cut-off
knitr::kable(a) %>%
  kableExtra::kable_styling()
participant_id group mean_bandit_effect cooks_d r abs_r cutoff
456LJD0 older -0.4992560 0.1759346 -4.1944555 4.1944555 TRUE
8TYYUVQ older -0.4062500 0.1253097 -3.5399109 3.5399109 TRUE
KV03W60 younger 0.4453125 0.0720598 2.6843948 2.6843948 TRUE
DEN93WZ younger 0.4408482 0.0703829 2.6529766 2.6529766 TRUE
NHNFAHE younger 0.3281250 0.0345837 1.8596685 1.8596685 FALSE
JZ2GTB5 older 0.3281250 0.0265160 1.6283736 1.6283736 FALSE
WTYN7I4 older 0.3281250 0.0265160 1.6283736 1.6283736 FALSE
7N1UCCZ younger -0.1484375 0.0223269 -1.4942182 1.4942182 FALSE
7JCF4GI older -0.1093750 0.0210425 -1.4506044 1.4506044 FALSE
19RK72K younger -0.1406250 0.0207140 -1.4392364 1.4392364 FALSE
YCSVF3T older 0.2890625 0.0183187 1.3534648 1.3534648 FALSE
ECXQVJB older 0.2831101 0.0172023 1.3115740 1.3115740 FALSE
MPK0QEJ older -0.0859375 0.0165292 -1.2856591 1.2856591 FALSE
ZDMRMMI younger -0.1090670 0.0148143 -1.2171417 1.2171417 FALSE
OBUV205 older -0.0703125 0.0138226 -1.1756956 1.1756956 FALSE
K5RJTBX younger -0.1015625 0.0135566 -1.1643277 1.1643277 FALSE
183G1TG younger 0.2200101 0.0120734 1.0987921 1.0987921 FALSE
2XOKAWI older 0.2500000 0.0116328 1.0785561 1.0785561 FALSE
CF9HLXT older 0.2479911 0.0113299 1.0644179 1.0644179 FALSE
7U0LI6K younger -0.0868376 0.0112508 -1.0606985 1.0606985 FALSE
2U0MIKQ older 0.2450397 0.0108920 1.0436470 1.0436470 FALSE
TA2VFRO younger 0.2109375 0.0107111 1.0349423 1.0349423 FALSE
VFPGR9H younger 0.2109375 0.0107111 1.0349423 1.0349423 FALSE
UR30LAQ older 0.2421875 0.0104770 1.0235743 1.0235743 FALSE
21UUM86 older -0.0468750 0.0102162 -1.0107504 1.0107504 FALSE
X1PTYRN older 0.2343750 0.0093817 0.9685926 0.9685926 FALSE
455CK00 younger 0.2010169 0.0093146 0.9651242 0.9651242 FALSE
HRIJPG9 younger 0.1953125 0.0085559 0.9249788 0.9249788 FALSE
FH7KBBM older 0.2265625 0.0083468 0.9136108 0.9136108 FALSE
DAYU2HR younger -0.0625000 0.0079107 -0.8894189 0.8894189 FALSE
UDZB7RM older 0.2212302 0.0076752 0.8760836 0.8760836 FALSE
1SLA8RA younger -0.0526714 0.0067281 -0.8202483 0.8202483 FALSE
FU302LK older 0.2109375 0.0064585 0.8036473 0.8036473 FALSE
WBBJKQ5 older 0.2109375 0.0064585 0.8036473 0.8036473 FALSE
UKHJGRA older -0.0156250 0.0062540 -0.7908234 0.7908234 FALSE
OT1K6AW younger -0.0468750 0.0060755 -0.7794554 0.7794554 FALSE
U3TL6HO younger -0.0468750 0.0060755 -0.7794554 0.7794554 FALSE
I2YZ80Y younger 0.1718750 0.0057765 0.7600336 0.7600336 FALSE
LKY4JXE younger 0.1718750 0.0057765 0.7600336 0.7600336 FALSE
EGSU9DJ older 0.2031250 0.0056050 0.7486656 0.7486656 FALSE
QXAIVE1 older -0.0078125 0.0054146 -0.7358416 0.7358416 FALSE
SZTW4BL younger -0.0390625 0.0052486 -0.7244737 0.7244737 FALSE
36OTGGG older 0.1935764 0.0046440 0.6814657 0.6814657 FALSE
7R0EXY3 older 0.0000000 0.0046357 -0.6808599 0.6808599 FALSE
M2O2QLQ older 0.0000000 0.0046357 -0.6808599 0.6808599 FALSE
VVBU17E older 0.0000000 0.0046357 -0.6808599 0.6808599 FALSE
OP2LLPO older 0.0002520 0.0046116 -0.6790863 0.6790863 FALSE
GNOOXA3 younger -0.0312500 0.0044822 -0.6694919 0.6694919 FALSE
JJQXFOC older 0.1875000 0.0040794 0.6387021 0.6387021 FALSE
O2QXZ5J older 0.1875000 0.0040794 0.6387021 0.6387021 FALSE
2RLPNB4 younger -0.0234375 0.0037762 -0.6145102 0.6145102 FALSE
7XSIESW younger -0.0234375 0.0037762 -0.6145102 0.6145102 FALSE
SBIDE6J younger -0.0234375 0.0037762 -0.6145102 0.6145102 FALSE
AR9QXWC younger 0.1484375 0.0035413 0.5950883 0.5950883 FALSE
DJZG44V older 0.0153770 0.0032792 -0.5726418 0.5726418 FALSE
SKJPMIA older 0.0156250 0.0032592 -0.5708964 0.5708964 FALSE
SQ3VKEM older 0.0156250 0.0032592 -0.5708964 0.5708964 FALSE
AXHL3JS younger -0.0156250 0.0031307 -0.5595284 0.5595284 FALSE
19VOGNI older 0.1749752 0.0030311 0.5505568 0.5505568 FALSE
1NU6KP5 younger 0.1406250 0.0029172 0.5401066 0.5401066 FALSE
MZA2D3L younger 0.1406250 0.0029172 0.5401066 0.5401066 FALSE
1I3T2KQ older 0.1718750 0.0027956 0.5287386 0.5287386 FALSE
9MMX6W0 older 0.1718750 0.0027956 0.5287386 0.5287386 FALSE
B17DS1U older 0.1718750 0.0027956 0.5287386 0.5287386 FALSE
HALMTNX older 0.1718750 0.0027956 0.5287386 0.5287386 FALSE
ZCT3VLY older 0.0234375 0.0026617 -0.5159146 0.5159146 FALSE
H1I9RHX younger -0.0078125 0.0025457 -0.5045467 0.5045467 FALSE
2HOJBMD younger 0.1328125 0.0023535 0.4851248 0.4851248 FALSE
M9UCUB3 older 0.0312500 0.0021246 -0.4609329 0.4609329 FALSE
P0WM0L3 younger 0.0000000 0.0020211 -0.4495649 0.4495649 FALSE
GANQASX younger 0.1250000 0.0018502 0.4301431 0.4301431 FALSE
F4HNQRD younger 0.0030802 0.0018309 -0.4278875 0.4278875 FALSE
HPIBR7U younger 0.0078125 0.0015570 -0.3945832 0.3945832 FALSE
QOR8ABQ older 0.1484375 0.0013235 0.3637934 0.3637934 FALSE
4XDD2QJ younger 0.0156250 0.0011533 -0.3396014 0.3396014 FALSE
8YE15DF younger 0.0156250 0.0011533 -0.3396014 0.3396014 FALSE
L666HVU older 0.0546875 0.0008761 -0.2959876 0.2959876 FALSE
GAZ3DHL younger 0.0219494 0.0008708 -0.2950924 0.2950924 FALSE
MB5H2ZE younger 0.0234375 0.0008101 -0.2846197 0.2846197 FALSE
YL3GFG7 younger 0.0234375 0.0008101 -0.2846197 0.2846197 FALSE
BUXFS2V older 0.0571677 0.0007758 -0.2785331 0.2785331 FALSE
09RI1ZH younger 0.1015625 0.0007033 0.2651978 0.2651978 FALSE
S3SBPDA older 0.1328125 0.0006443 0.2538299 0.2538299 FALSE
4BKAHC6 younger 0.0312500 0.0005273 -0.2296379 0.2296379 FALSE
9AKNFS1 younger 0.0312500 0.0005273 -0.2296379 0.2296379 FALSE
561VM94 younger 0.0937500 0.0004419 0.2102161 0.2102161 FALSE
GUCN9CE younger 0.0937500 0.0004419 0.2102161 0.2102161 FALSE
RB2ZSAB younger 0.0937500 0.0004419 0.2102161 0.2102161 FALSE
D9SQ75N older 0.0703125 0.0003460 -0.1860241 0.1860241 FALSE
LPPZXCB older 0.0703125 0.0003460 -0.1860241 0.1860241 FALSE
U2GSTLK younger 0.0390625 0.0003050 -0.1746562 0.1746562 FALSE
DY1X9JA older 0.1170594 0.0002044 0.1429650 0.1429650 FALSE
I59Q0I6 older 0.0781250 0.0001717 -0.1310424 0.1310424 FALSE
2TRM8AE younger 0.0468750 0.0001432 -0.1196744 0.1196744 FALSE
JERCLPI younger 0.0781250 0.0001005 0.1002526 0.1002526 FALSE
T34GEJ2 younger 0.0781250 0.0001005 0.1002526 0.1002526 FALSE
I9OJ4Z0 older 0.1093750 0.0000790 0.0888846 0.0888846 FALSE
L7PPJCL older 0.0859375 0.0000579 -0.0760606 0.0760606 FALSE
PZYUX9G younger 0.0726687 0.0000383 0.0618526 0.0618526 FALSE
40AOODQ older 0.1040427 0.0000264 0.0513574 0.0513574 FALSE
R0U8BHP older 0.0937500 0.0000044 -0.0210789 0.0210789 FALSE
IDJ2K9B younger 0.0625000 0.0000009 -0.0097109 0.0097109 FALSE

Robust Regression with Bisquare weights

Summary

# Robust regression (bisquare weights)
rr.bisquare = MASS::rlm(mean_bandit_effect ~ 1 + group,
                     data = data_rr_mean_correct,
                     psi = psi.bisquare)
res = summary(rr.bisquare)
res
## 
## Call: rlm(formula = mean_bandit_effect ~ 1 + group, data = data_rr_mean_correct, 
##     psi = psi.bisquare)
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.61822 -0.09084 -0.01434  0.08689  0.39249 
## 
## Coefficients:
##              Value   Std. Error t value
## (Intercept)   0.1190  0.0171     6.9571
## groupyounger -0.0661  0.0242    -2.7349
## 
## Residual standard error: 0.1302 on 100 degrees of freedom

Bisquare weights

# Look at weights created by RR (low weights = "outlier behavior")
bweights = data_rr_mean_correct %>%
  .[, ':='(resid = rr.bisquare$resid,
           weight = rr.bisquare$w)] %>%
  # Sort by weight
  .[order(rank(weight)),]

# Display weights by participant
knitr::kable(bweights) %>%
  kableExtra::kable_styling()
participant_id group mean_bandit_effect resid weight
456LJD0 older -0.4992560 -0.6182152 0.0000000
8TYYUVQ older -0.4062500 -0.5252092 0.0667396
KV03W60 younger 0.4453125 0.3924880 0.3431675
DEN93WZ younger 0.4408482 0.3880237 0.3542318
NHNFAHE younger 0.3281250 0.2753005 0.6339630
7JCF4GI older -0.1093750 -0.2283342 0.7393015
JZ2GTB5 older 0.3281250 0.2091658 0.7785545
WTYN7I4 older 0.3281250 0.2091658 0.7785545
MPK0QEJ older -0.0859375 -0.2048967 0.7869939
7N1UCCZ younger -0.1484375 -0.2012620 0.7940373
19RK72K younger -0.1406250 -0.1934495 0.8088826
OBUV205 older -0.0703125 -0.1892717 0.8166483
YCSVF3T older 0.2890625 0.1701033 0.8504408
183G1TG younger 0.2200101 0.1671856 0.8553410
21UUM86 older -0.0468750 -0.1658342 0.8575927
ECXQVJB older 0.2831101 0.1641509 0.8603366
ZDMRMMI younger -0.1090670 -0.1618915 0.8640269
TA2VFRO younger 0.2109375 0.1581130 0.8700819
VFPGR9H younger 0.2109375 0.1581130 0.8700819
K5RJTBX younger -0.1015625 -0.1543870 0.8759317
455CK00 younger 0.2010169 0.1481923 0.8853912
HRIJPG9 younger 0.1953125 0.1424880 0.8938013
7U0LI6K younger -0.0868376 -0.1396621 0.8978590
UKHJGRA older -0.0156250 -0.1345842 0.9049794
2XOKAWI older 0.2500000 0.1310408 0.9097805
CF9HLXT older 0.2479911 0.1290318 0.9124625
QXAIVE1 older -0.0078125 -0.1267717 0.9154544
2U0MIKQ older 0.2450397 0.1260805 0.9163345
UR30LAQ older 0.2421875 0.1232283 0.9199987
I2YZ80Y younger 0.1718750 0.1190505 0.9252366
LKY4JXE younger 0.1718750 0.1190505 0.9252366
7R0EXY3 older 0.0000000 -0.1189592 0.9253580
M2O2QLQ older 0.0000000 -0.1189592 0.9253580
VVBU17E older 0.0000000 -0.1189592 0.9253580
OP2LLPO older 0.0002520 -0.1187072 0.9256678
X1PTYRN older 0.2343750 0.1154158 0.9296409
DAYU2HR younger -0.0625000 -0.1153245 0.9297589
FH7KBBM older 0.2265625 0.1076033 0.9386973
1SLA8RA younger -0.0526714 -0.1054959 0.9410467
DJZG44V older 0.0153770 -0.1035822 0.9431433
SKJPMIA older 0.0156250 -0.1033342 0.9434113
SQ3VKEM older 0.0156250 -0.1033342 0.9434113
UDZB7RM older 0.2212302 0.1022709 0.9445375
OT1K6AW younger -0.0468750 -0.0996995 0.9472615
U3TL6HO younger -0.0468750 -0.0996995 0.9472615
AR9QXWC younger 0.1484375 0.0956130 0.9514439
ZCT3VLY older 0.0234375 -0.0955217 0.9515427
FU302LK older 0.2109375 0.0919783 0.9550163
WBBJKQ5 older 0.2109375 0.0919783 0.9550163
SZTW4BL younger -0.0390625 -0.0918870 0.9551116
1NU6KP5 younger 0.1406250 0.0878005 0.9589748
MZA2D3L younger 0.1406250 0.0878005 0.9589748
M9UCUB3 older 0.0312500 -0.0877092 0.9590659
EGSU9DJ older 0.2031250 0.0841658 0.9622624
GNOOXA3 younger -0.0312500 -0.0840745 0.9623499
2HOJBMD younger 0.1328125 0.0799880 0.9658903
2RLPNB4 younger -0.0234375 -0.0762620 0.9689693
7XSIESW younger -0.0234375 -0.0762620 0.9689693
SBIDE6J younger -0.0234375 -0.0762620 0.9689693
36OTGGG older 0.1935764 0.0746172 0.9702776
GANQASX younger 0.1250000 0.0721755 0.9721832
JJQXFOC older 0.1875000 0.0685408 0.9748914
O2QXZ5J older 0.1875000 0.0685408 0.9748914
AXHL3JS younger -0.0156250 -0.0684495 0.9749631
L666HVU older 0.0546875 -0.0642717 0.9779147
BUXFS2V older 0.0571677 -0.0617916 0.9795779
H1I9RHX younger -0.0078125 -0.0606370 0.9803253
19VOGNI older 0.1749752 0.0560160 0.9831932
1I3T2KQ older 0.1718750 0.0529158 0.9849950
9MMX6W0 older 0.1718750 0.0529158 0.9849950
B17DS1U older 0.1718750 0.0529158 0.9849950
HALMTNX older 0.1718750 0.0529158 0.9849950
P0WM0L3 younger 0.0000000 -0.0528245 0.9850506
F4HNQRD younger 0.0030802 -0.0497443 0.9867375
09RI1ZH younger 0.1015625 0.0487380 0.9872672
D9SQ75N older 0.0703125 -0.0486467 0.9873184
LPPZXCB older 0.0703125 -0.0486467 0.9873184
HPIBR7U younger 0.0078125 -0.0450120 0.9891343
561VM94 younger 0.0937500 0.0409255 0.9910136
GUCN9CE younger 0.0937500 0.0409255 0.9910136
RB2ZSAB younger 0.0937500 0.0409255 0.9910136
I59Q0I6 older 0.0781250 -0.0408342 0.9910567
4XDD2QJ younger 0.0156250 -0.0371995 0.9925723
8YE15DF younger 0.0156250 -0.0371995 0.9925723
L7PPJCL older 0.0859375 -0.0330217 0.9941474
GAZ3DHL younger 0.0219494 -0.0308751 0.9948803
QOR8ABQ older 0.1484375 0.0294783 0.9953302
MB5H2ZE younger 0.0234375 -0.0293870 0.9953613
YL3GFG7 younger 0.0234375 -0.0293870 0.9953613
JERCLPI younger 0.0781250 0.0253005 0.9965608
T34GEJ2 younger 0.0781250 0.0253005 0.9965608
R0U8BHP older 0.0937500 -0.0252092 0.9965875
4BKAHC6 younger 0.0312500 -0.0215745 0.9974985
9AKNFS1 younger 0.0312500 -0.0215745 0.9974985
PZYUX9G younger 0.0726687 0.0198441 0.9978835
40AOODQ older 0.1040427 -0.0149166 0.9988050
S3SBPDA older 0.1328125 0.0138533 0.9989671
U2GSTLK younger 0.0390625 -0.0137620 0.9989818
IDJ2K9B younger 0.0625000 0.0096755 0.9994967
I9OJ4Z0 older 0.1093750 -0.0095842 0.9995069
2TRM8AE younger 0.0468750 -0.0059495 0.9998097
DY1X9JA older 0.1170594 -0.0018998 0.9999807

t-distribution and t-value of age group effect

# Convert stat summary to data.table
out = data.table(res$coefficients)

# plot t-distribution
plot_t = data.table(x = seq(-5,5, by = 0.001),
                    y = dt(seq(-5,5, by = 0.001), df=res$df[2]))
p = ggplot(data = plot_t,
           aes(x = x,
               y = y)) +
  geom_path() +
  # Mark cut-offs
  geom_vline(xintercept = qt(c(.025, .975), df = res$df[2]),
             linetype = 'dashed') +
  # Mark t-value of group statistic
  geom_vline(xintercept = out$`t value`[2],
             color = 'red') +
  labs(x = 't-Value',
       y = 'Density',
       main = 't-Distribution for group effect') +
  scale_y_continuous(expand = c(0,0))
Neurocodify_plot(p) +
  theme(panel.grid = element_blank())

p-value of age group effect

Manuscript ll. 323

# Derive p-value from t-distribution and display
p_value = plot_t[x == round(out$`t value`[2], 3)]$y
p_value
## [1] 0.01041674

Reaction time (RT)

# Get log(RT)
check_rt = data %>%
  .[, trial := seq(.N),
    by = c('participant_id', 'run')] %>%
  # Exclude time-outs
  .[timeout == FALSE, ] %>%
  .[, log_rt := log(rt)] %>%
  # Long format for easier analysis
  data.table::melt(.,
                   id.vars = c('participant_id',
                               'group',
                               'run',
                               'trial',
                               'trial_type',
                               'comp'),
                   measure.vars = c('log_rt'))

# Resolve RT by bandit comparison
check_rt_mean = check_rt %>%
  .[variable == 'log_rt', ] %>%
  # Get mean log(rt) for each possible comparison
  .[, .(mean_value = mean(value)),
    by = c('participant_id', 'group', 'run', 'trial_type', 'comp')] %>%
  # Restrict to choice trials (ommitting forced choices)
  .[trial_type == 'choice',]

Stats

LMM log(RT), Manuscript ll. 324

# Assure data types
check_rt_mean$participant_id = as.factor(check_rt_mean$participant_id)
check_rt_mean$group = as.factor(check_rt_mean$group)
check_rt_mean$run = as.factor(check_rt_mean$run)
check_rt_mean$trial_type = as.factor(check_rt_mean$trial_type)
check_rt_mean$comp = as.factor(check_rt_mean$comp)
check_rt_mean$mean_value = as.numeric(check_rt_mean$mean_value)

# Stats model
lme_rt_comp = lme4::lmer(data = check_rt_mean,
                         mean_value ~ group * run * comp + (1|participant_id))
# Display
papeR::prettify(Anova(lme_rt_comp))
##                        Chisq Df Pr(>Chisq)    
## 1          group  31.3563220  1     <0.001 ***
## 2            run   2.2492368  1      0.134    
## 3           comp 457.8910537  2     <0.001 ***
## 4      group:run   2.4529957  1      0.117    
## 5     group:comp  16.3603001  2     <0.001 ***
## 6       run:comp   0.4553283  2      0.796    
## 7 group:run:comp   0.9012761  2      0.637

Main effect: Comp

Manuscript ll. 325

emmeans::emmeans(lme_rt_comp,
                 pairwise ~ comp)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  comp emmean     SE  df lower.CL upper.CL
##  1v2    6.68 0.0179 123     6.65     6.72
##  2v3    6.53 0.0179 123     6.49     6.56
##  1v3    6.49 0.0179 123     6.45     6.52
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast  estimate      SE  df t.ratio p.value
##  1v2 - 2v3   0.1581 0.00981 500  16.121  <.0001
##  1v2 - 1v3   0.1986 0.00981 500  20.247  <.0001
##  2v3 - 1v3   0.0405 0.00981 500   4.126  0.0001
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger 
## P value adjustment: tukey method for comparing a family of 3 estimates

Main effect: Age group

Manuscript ll. 328

emmeans::emmeans(lme_rt_comp,
                 pairwise ~ group)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  group   emmean    SE  df lower.CL upper.CL
##  older     6.66 0.024 100     6.61     6.71
##  younger   6.47 0.024 100     6.42     6.52
## 
## Results are averaged over the levels of: run, comp 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast        estimate     SE  df t.ratio p.value
##  older - younger     0.19 0.0339 100   5.600  <.0001
## 
## Results are averaged over the levels of: run, comp 
## Degrees-of-freedom method: kenward-roger

Interaction effect: Group x Comp

Manuscript ll. 328

em = emmeans::emmeans(lme_rt_comp,
                  pairwise ~ group | comp)
## NOTE: Results may be misleading due to involvement in interactions
em
## $emmeans
## comp = 1v2:
##  group   emmean     SE  df lower.CL upper.CL
##  older     6.80 0.0253 123     6.75     6.85
##  younger   6.57 0.0253 123     6.52     6.62
## 
## comp = 2v3:
##  group   emmean     SE  df lower.CL upper.CL
##  older     6.61 0.0253 123     6.56     6.66
##  younger   6.44 0.0253 123     6.39     6.49
## 
## comp = 1v3:
##  group   emmean     SE  df lower.CL upper.CL
##  older     6.57 0.0253 123     6.52     6.62
##  younger   6.40 0.0253 123     6.35     6.45
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
## comp = 1v2:
##  contrast        estimate     SE  df t.ratio p.value
##  older - younger    0.236 0.0358 123   6.589  <.0001
## 
## comp = 2v3:
##  contrast        estimate     SE  df t.ratio p.value
##  older - younger    0.164 0.0358 123   4.593  <.0001
## 
## comp = 1v3:
##  contrast        estimate     SE  df t.ratio p.value
##  older - younger    0.170 0.0358 123   4.752  <.0001
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger
# Accurate correction
summary(em, by = NULL, adjust = 'sidak')
## $emmeans
##  group   comp emmean     SE  df lower.CL upper.CL
##  older   1v2    6.80 0.0253 123     6.73     6.87
##  younger 1v2    6.57 0.0253 123     6.50     6.63
##  older   2v3    6.61 0.0253 123     6.54     6.68
##  younger 2v3    6.44 0.0253 123     6.38     6.51
##  older   1v3    6.57 0.0253 123     6.50     6.64
##  younger 1v3    6.40 0.0253 123     6.33     6.47
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## Conf-level adjustment: sidak method for 6 estimates 
## 
## $contrasts
##  contrast        comp estimate     SE  df t.ratio p.value
##  older - younger 1v2     0.236 0.0358 123   6.589  <.0001
##  older - younger 2v3     0.164 0.0358 123   4.593  <.0001
##  older - younger 1v3     0.170 0.0358 123   4.752  <.0001
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## P value adjustment: sidak method for 3 tests

Comparison of age differences in 1v2 with other bandit comps (manuscript ll. 333-334)

# Interaction (contrast of contrast, effect in younger - effect in older)
# https://cran.r-project.org/web/packages/emmeans/vignettes/interactions.html (Section: "Simple contrasts")
em_pairwise = emmeans::emmeans(lme_rt_comp,
                               pairwise ~ comp | group)
## NOTE: Results may be misleading due to involvement in interactions
emmeans::contrast(em_pairwise, 'consec', simple = 'group', combine = TRUE)
## $emmeans
## comp = 1v2:
##  contrast        estimate     SE  df t.ratio p.value
##  younger - older   -0.236 0.0358 123  -6.589  <.0001
## 
## comp = 2v3:
##  contrast        estimate     SE  df t.ratio p.value
##  younger - older   -0.164 0.0358 123  -4.593  <.0001
## 
## comp = 1v3:
##  contrast        estimate     SE  df t.ratio p.value
##  younger - older   -0.170 0.0358 123  -4.752  <.0001
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## 
## $contrasts
## contrast = 1v2 - 2v3:
##  contrast1       estimate     SE  df t.ratio p.value
##  younger - older -0.07137 0.0196 500  -3.639  0.0003
## 
## contrast = 1v2 - 1v3:
##  contrast1       estimate     SE  df t.ratio p.value
##  younger - older -0.06568 0.0196 500  -3.349  0.0009
## 
## contrast = 2v3 - 1v3:
##  contrast1       estimate     SE  df t.ratio p.value
##  younger - older  0.00569 0.0196 500   0.290  0.7720
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger

Plot

Saves to folder in datalad repository derivatives, datalad unlock folder figures in case it gives an access error

data_plot = Prepare_data_for_plot(check_rt_mean)

save_path = file.path(base_path,
                      'derivatives',
                      'figures',
                      'f_rt.tsv',
                      fsep = .Platform$file.sep)
data.table::fwrite(data_plot,
                   save_path,
                   sep = '\t',
                   row.names = FALSE,
                   na = 'n/a')

dodge_width = 0.3

p_rt_comp = ggplot(data = data_plot,
       aes(x = comp,
           y = mean_value,
           color = group,
           fill = group)) +
  geom_point(position = position_jitterdodge(dodge.width = dodge_width,
                                             jitter.width = dodge_width/2,
                                             jitter.height = 0,
                                             seed = 666)) +
  geom_boxplot(outlier.shape = NA,
               color = 'black',
               width = dodge_width,
               position = position_dodge(width = dodge_width)) +
  stat_summary(fun = 'mean',
               geom = 'point',
               na.rm = TRUE,
               shape = 23,
               inherit.aes = TRUE,
               fill = 'white',
               size = 1.5,
               stroke = 0.5,
               position = position_dodge(width = dodge_width),
               show.legend = FALSE) +
  scale_color_manual(values = custom_guides) +
  scale_fill_manual(values = custom_guides)
Neurocodify_plot(p_rt_comp)


Immediate influence of surprising outcomes

# Make sure we only include choices in 1v2 comps when calculating prob of choosing 2
data_ii = data %>%
  .[comp != '1v2' | trial_type != 'choice', correct_choice := NA,
    by = c('participant_id', 'run')] %>%
  # Get running averages
  .[, ':='(avg_1_running = Get_running_avg(choice_option = option_choice,
                                           choice_outcome = outcome,
                                           stim = 1),
           avg_2_running = Get_running_avg(choice_option = option_choice,
                                           choice_outcome = outcome,
                                           stim = 2),
           avg_3_running = Get_running_avg(choice_option = option_choice,
                                           choice_outcome = outcome,
                                           stim = 3)),
    by = c('participant_id', 'run')]

# Allocate data holding +-5 trials from rare outcome of bandit 2
window_data = data.table()

# Function to get data slice +-5 trials from rare outcome of bandit 2
Windowrize = function(data,
                      index_rare,
                      window_size){
  result = data[seq(max(index_rare - window_size + 1, 1),
                    index_rare + window_size), ]
  result$window_center = data$trial[index_rare]
  result$window_relative = seq(max(index_rare - window_size + 1, 1),
                               index_rare + window_size) - index_rare
  result[window_relative == 0]$correct_choice = NA
  
  result$window_center = as.factor(result$window_center)
  result$window_relative = as.factor(result$window_relative)
  return(result)
}

# i_id = '09RI1ZH'
# i_run = '1'

# For each participant & run
for(i_id in unique(data_ii$participant_id)){
  for(i_run in unique(data_ii$run)){
    
    # Select data
    temp_data = data_ii[participant_id == i_id &
                      run == i_run]
    
    # Get all trials where rare outcomes were obtained
    idx_chosen_rare_outcome = which(temp_data$is_rare == 1 &
                                      # CHANGE: I also allow rare outcomes that came from forced choices
                                      temp_data$trial_type %in% c('choice', 'forced') &
                                      temp_data$option_choice == 2)
   # For each of the rare-outcome trials
   for(rare_count in seq(1,length(idx_chosen_rare_outcome))){
    # Get data slice
     temp = Windowrize(data = temp_data,
                       index_rare = idx_chosen_rare_outcome[rare_count],
                       window_size = 3) %>%
       .[, window_relative := factor(window_relative, levels = unique(sort(window_relative)))]
     # Add count of the rare outcome
     temp$i_rare = rare_count
     # Fuse data for each participant & run
     window_data = rbind(window_data, temp)
   }
  }
}
# Eliminate windows which extended across trial boundaries (<1 or >240)
window_data = window_data %>%
  .[, relative_trial := as.numeric(as.character(window_center)) + as.numeric(as.character(window_relative))] %>%
  .[!(relative_trial < 1 | relative_trial > 240),] %>%
  # Sort by relative window
  .[order(rank(group), rank(participant_id), rank(run), rank(window_relative)),]

# Summarize data
window_data_run = window_data %>%
  # Get mean accuracy across all relative window positions (-2 to +3)
  .[, .(mean_accuracy = mean(correct_choice, na.rm = TRUE),
        n_data = sum(!is.na(correct_choice))),
    by = c('participant_id', 'group', 'run', 'window_relative')]

# Get mean across runs
window_data_participant = window_data_run %>%
  .[, .(accuracy = mean(mean_accuracy, na.rm = TRUE)),
    by = c('participant_id', 'group', 'window_relative')]
  
# Get age group specific mean and sd
window_data_group = window_data_participant %>%
  .[, .(mean_accuracy = mean(accuracy, na.rm = TRUE),
        sd_accuracy = sd(accuracy, na.rm = TRUE)),
    by = c('group', 'window_relative')] %>%
  .[order(rank(group), rank(window_relative)),]

Save windorized data to derivatives

Saves to folder in datalad repository derivatives, datalad unlock folder analysis in case it gives an access error

save_dir = file.path(base_path, 'derivatives', 'analysis',
                     fsep = .Platform$file.sep)
if(!dir.exists(save_dir)){
  dir.create(save_dir)
}
file = file.path(save_dir, 'windowrize.tsv', fsep = .Platform$file.sep)
data.table::fwrite(window_data, file = file, sep = '\t', na = 'n/a')

Check: Average N of rare outcomes in each run

avg_n_rare = window_data %>%
  # Eliminate windows which extended across trial boundaries (<1 or >240)
  .[, relative_trial := as.numeric(as.character(window_center)) + as.numeric(as.character(window_relative))] %>%
  .[!(relative_trial < 1 | relative_trial > 240),] %>%
  # Get number of rare outcomes collected
  .[, .(n_rare = max(i_rare)),
    by = c('participant_id', 'group', 'run')]

# Average rare outcomes per run
avg_n_rare_per_run = mean(avg_n_rare$n_rare)
avg_n_rare_per_run
## [1] 17.11765

Average number of free-choice low-mid trials immediately preceding or following a rare outcome

# Avg n_data pre and post rare outcome
window_data_run[window_relative %in% c('-1', '1')] %>%
  .[, .(mean_n_data = mean(n_data)),
    by = c('window_relative')]
##    window_relative mean_n_data
## 1:              -1    4.705882
## 2:               1    4.441176

Stats

Manuscript ll. 338

# LME for immediate before, and immediate after rare outcome
data_lme = window_data_run[window_relative %in% c('-1', '1')] %>%
  .[, group := as.factor(group)] %>%
  .[, participant_id := as.factor(participant_id)]

lme = lme4::lmer(data = data_lme,
                 mean_accuracy ~ window_relative * group * run + (1 | participant_id))

res = Anova(lme)
papeR::prettify(res)
##                                  Chisq Df Pr(>Chisq)    
## 1           window_relative 28.1600157  1     <0.001 ***
## 2                     group  0.8239153  1      0.364    
## 3                       run 12.4377360  1     <0.001 ***
## 4     window_relative:group  5.8443425  1      0.016   *
## 5       window_relative:run  0.3676692  1      0.544    
## 6                 group:run  2.0020112  1      0.157    
## 7 window_relative:group:run  0.1674534  1      0.682

Main effect: Window

Manuscript ll. 345

emmeans::emmeans(lme, pairwise ~ window_relative)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  window_relative emmean     SE  df lower.CL upper.CL
##  -1               0.822 0.0205 197    0.782    0.862
##  1                0.701 0.0205 198    0.660    0.741
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast estimate     SE  df t.ratio p.value
##  (-1) - 1    0.121 0.0228 299   5.314  <.0001
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger

Interaction effect: Window X group

Manuscript ll. 342-344

em = emmeans::emmeans(lme, pairwise ~ window_relative | group)
## NOTE: Results may be misleading due to involvement in interactions
em
## $emmeans
## group = older:
##  window_relative emmean     SE  df lower.CL upper.CL
##  -1               0.834 0.0289 197    0.777    0.891
##  1                0.658 0.0291 198    0.600    0.715
## 
## group = younger:
##  window_relative emmean     SE  df lower.CL upper.CL
##  -1               0.810 0.0289 197    0.753    0.867
##  1                0.744 0.0289 197    0.687    0.801
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
## group = older:
##  contrast estimate     SE  df t.ratio p.value
##  (-1) - 1   0.1766 0.0324 299   5.458  <.0001
## 
## group = younger:
##  contrast estimate     SE  df t.ratio p.value
##  (-1) - 1   0.0662 0.0323 299   2.052  0.0410
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger
# Accurate correction
summary(em, by = NULL, adjust = 'sidak')
## $emmeans
##  window_relative group   emmean     SE  df lower.CL upper.CL
##  -1              older    0.834 0.0289 197    0.761    0.907
##  1               older    0.658 0.0291 198    0.584    0.731
##  -1              younger  0.810 0.0289 197    0.737    0.883
##  1               younger  0.744 0.0289 197    0.671    0.816
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## Conf-level adjustment: sidak method for 4 estimates 
## 
## $contrasts
##  contrast group   estimate     SE  df t.ratio p.value
##  (-1) - 1 older     0.1766 0.0324 299   5.458  <.0001
##  (-1) - 1 younger   0.0662 0.0323 299   2.052  0.0804
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## P value adjustment: sidak method for 2 tests

Main effect: Run

Manuscript ll. 347-348

emmeans::emmeans(lme, pairwise ~ run)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  run emmean     SE  df lower.CL upper.CL
##  1    0.721 0.0205 197    0.681    0.761
##  2    0.802 0.0205 198    0.761    0.842
## 
## Results are averaged over the levels of: window_relative, group 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast estimate     SE  df t.ratio p.value
##  1 - 2     -0.0807 0.0228 299  -3.530  0.0005
## 
## Results are averaged over the levels of: window_relative, group 
## Degrees-of-freedom method: kenward-roger

Plot

data_plot = Prepare_data_for_plot(window_data_participant) %>%
  .[, window_relative := as.numeric(as.character(window_relative))] %>%
  .[!window_relative == 0, ]

save_path = file.path(base_path,
                      'derivatives',
                      'figures',
                      'f_rie.tsv',
                      fsep = .Platform$file.sep)
data.table::fwrite(data_plot,
                   save_path,
                   sep = '\t',
                   row.names = FALSE,
                   na = 'n/a')

data_plot_mean = data_plot %>%
  .[, .(accuracy = mean(accuracy),
        sem_accuracy = sd(accuracy) / sqrt(.N)),
    by = c('group', 'window_relative')]

save_path = file.path(base_path,
                      'derivatives',
                      'figures',
                      'f_rie_mean.tsv',
                      fsep = .Platform$file.sep)
data.table::fwrite(data_plot_mean,
                   save_path,
                   sep = '\t',
                   row.names = FALSE,
                   na = 'n/a')

dodge_width = 0.3

p = ggplot(data = data_plot,
       aes(x = window_relative,
           y = accuracy,
           fill = group,
           color = group,
           group = interaction(window_relative, group))) +
  geom_point(position = position_jitterdodge(dodge.width = dodge_width,
                                             jitter.width = 0.1,
                                             jitter.height = 0,
                                             seed = 666),
             size = 0.5,
             alpha = 0.5) +
  geom_vline(xintercept = 0,
             linetype = 'dashed') +
  geom_path(data = data_plot_mean,
             size = 1,
             position = position_dodge(dodge_width),
            aes(group = group)) +
  geom_errorbar(data = data_plot_mean,
                  aes(x = window_relative,
                      y = accuracy,
                      ymin = accuracy - sem_accuracy,
                      ymax = accuracy + sem_accuracy),
                  size = 0.5,
                width = dodge_width/2,
                  position = position_dodge(dodge_width),
                color = 'black') +
  geom_point(data = data_plot_mean,
                  aes(x = window_relative,
                      y = accuracy),
                  size = 3,
                  position = position_dodge(dodge_width),
             shape = 21,
             color = 'black') +
  scale_fill_manual(values = custom_guides) +
  scale_color_manual(values = custom_guides) +
  scale_x_continuous(breaks = seq(min(data_plot$window_relative),
                                max(data_plot$window_relative))) +
  scale_y_continuous(limits = c(0,1)) +
  ggtext::geom_richtext(x = 0,
                        y = 0.1,
                        label = 'Rare outcome in Mid arm',
                        fill = 'white',
                        color = 'black',
                        angle = 90,
                        size = 3,
                        hjust = 0) +
  labs(y = 'p(Choice Mid | LM)') +
  theme(legend.position = 'top')
  
Neurocodify_plot(p) +
  theme(panel.grid = element_blank())

Immediate influence of events between 20th and 40th percentile

Same analysis, but for low outcomes not from the lower mode of the mid distribution

window_data_normal = data.table()
# For each participant & run
for(i_id in unique(data_ii$participant_id)){
  for(i_run in unique(data_ii$run)){
    
    # Select data
    temp_data = data_ii[participant_id == i_id &
                      run == i_run]
    
    # Get percentiles of second bandit for upper and lower bound
    perc = temp_data[comp %in% c('1v2', '2v3')] %>%
      .[, sample_2 := c(reward_stim_1, reward_stim_2)[which(c(option_left, option_right) == 2)],
        by = c('trial')]
    # Set boundaries to 20th-40th percentile of outcomes
    lb = quantile(perc$sample_2, prob = 0.2)
    ub = quantile(perc$sample_2, prob = 0.4)
    
    temp_data = temp_data %>%
      # Get if outcome is between specified percentiles
      .[, is_p20to40 := FALSE] %>%
      .[outcome >= lb & outcome <= ub, is_p20to40 := TRUE]
    
    # Get all trials where rare outcomes were obtained
    idx_chosen_normal_outcome = which(temp_data$is_rare == 0 &
                                        temp_data$is_p20to40 == TRUE &
                                        temp_data$trial_type %in% c('choice', 'forced') &
                                        temp_data$option_choice == 2)
   # For each of the rare-outcome trials
   for(normal_count in seq(1,length(idx_chosen_normal_outcome))){
    # Get data slice
     temp = Windowrize(data = temp_data,
                       index_rare = idx_chosen_normal_outcome[normal_count],
                       window_size = 3)
     # Add count of the rare outcome
     temp$i_rare = normal_count
     # Fuse data for each participant & run
     window_data_normal = rbind(window_data_normal, temp)
   }
  }
}

window_data_normal$window_relative = as.numeric(as.character(window_data_normal$window_relative))
window_data_normal$window_relative = factor(window_data_normal$window_relative,
                                            levels = sort(unique(window_data_normal$window_relative)))

# Summarize data
window_data_run_normal = window_data_normal %>%
  # Eliminate windows which extended across trial boundaries (<1 or >240)
  .[, relative_trial := as.numeric(as.character(window_center)) + as.numeric(as.character(window_relative))] %>%
  .[!(relative_trial < 1 | relative_trial > 240),] %>%
  # Sort by relative window
  .[order(rank(group), rank(participant_id), rank(run), rank(window_relative)),] %>%
  # Get mean accuracy across all relative window positions (-2 to +3)
  .[, .(mean_accuracy = mean(correct_choice, na.rm = TRUE),
        n_data = sum(!is.na(correct_choice))),
    by = c('participant_id', 'group', 'run', 'window_relative')]

# Get mean across runs
window_data_participant_normal = window_data_run_normal %>%
  .[, .(accuracy = mean(mean_accuracy, na.rm = TRUE)),
    by = c('participant_id', 'group', 'window_relative')]

Stats

Manuscript ll. 352-356

data_lme = window_data_run_normal[window_relative %in% c('-1', '1')] %>%
  .[, group := as.factor(group)] %>%
  .[, participant_id := as.factor(participant_id)]

lme = lme4::lmer(data = data_lme,
                 mean_accuracy ~ window_relative * group * run + (1 | participant_id))
res = Anova(lme)

# Display
papeR::prettify(res)
##                                    Chisq Df Pr(>Chisq)    
## 1           window_relative 5.408902e-01  1      0.462    
## 2                     group 9.681182e-04  1      0.975    
## 3                       run 1.022101e+01  1      0.001  **
## 4     window_relative:group 4.615595e-02  1       0.83    
## 5       window_relative:run 5.051385e-02  1      0.822    
## 6                 group:run 2.029842e-01  1      0.652    
## 7 window_relative:group:run 3.066521e-01  1       0.58

Plot

data_plot = Prepare_data_for_plot(window_data_participant_normal) %>%
  .[, window_relative := as.numeric(as.character(window_relative))] %>%
  .[!window_relative == 0, ]

save_path = file.path(base_path,
                      'derivatives',
                      'figures',
                      'f_ric.tsv',
                      fsep = .Platform$file.sep)
data.table::fwrite(data_plot,
                   save_path,
                   sep = '\t',
                   row.names = FALSE,
                   na = 'n/a')

data_plot_mean = data_plot %>%
  .[, .(accuracy = mean(accuracy),
        sem_accuracy = sd(accuracy) / sqrt(.N)),
    by = c('group', 'window_relative')]

save_path = file.path(base_path,
                      'derivatives',
                      'figures',
                      'f_ric_mean.tsv',
                      fsep = .Platform$file.sep)
data.table::fwrite(data_plot_mean,
                   save_path,
                   sep = '\t',
                   row.names = FALSE,
                   na = 'n/a')

dodge_width = 0.3

p = ggplot(data = data_plot,
       aes(x = window_relative,
           y = accuracy,
           fill = group,
           color = group,
           group = interaction(window_relative, group))) +
  geom_point(position = position_jitterdodge(dodge.width = dodge_width,
                                             jitter.width = 0.1,
                                             jitter.height = 0,
                                             seed = 666),
             size = 0.5,
             alpha = 0.5) +
  # geom_line(aes(group = participant_id),
  #           alpha = 0.1,
  #           size = 0.5) +
  # geom_boxplot(color = 'black',
  #              outlier.shape = NA,
  #              width = dodge_width,
  #              position = position_dodge(width = dodge_width)) +
  geom_vline(xintercept = 0,
             linetype = 'dashed') +
  geom_path(data = data_plot_mean,
             size = 1,
             position = position_dodge(dodge_width),
            aes(group = group)) +
  geom_errorbar(data = data_plot_mean,
                  aes(x = window_relative,
                      y = accuracy,
                      ymin = accuracy - sem_accuracy,
                      ymax = accuracy + sem_accuracy),
                  size = 0.5,
                width = dodge_width/2,
                  position = position_dodge(dodge_width),
                color = 'black') +
  geom_point(data = data_plot_mean,
                  aes(x = window_relative,
                      y = accuracy),
                  size = 3,
                  position = position_dodge(dodge_width),
             shape = 21,
             color = 'black') +
  scale_fill_manual(values = custom_guides) +
  scale_color_manual(values = custom_guides) +
  scale_x_continuous(breaks = seq(min(data_plot$window_relative),
                                max(data_plot$window_relative))) +
  scale_y_continuous(limits = c(0,1)) +
  ggtext::geom_richtext(x = 0,
                        y = 0.1,
                        label = '20-40 percentile outcome in Mid arm',
                        fill = 'white',
                        color = 'black',
                        angle = 90,
                        size = 3,
                        hjust = 0) +
  labs(y = 'p(Choice Mid | LM)') +
  theme(legend.position = 'top')
  
Neurocodify_plot(p) +
  theme(panel.grid = element_blank())


Risk aversion (proportion of M choices)

Manuscript ll. 348-349

check_risk = data %>%
  Add_comp() %>%
  # Only take free choices
  .[trial_type == 'choice',] %>%
  .[comp != '1v3',] %>%
  .[, .(n_overall = .N,
        n_choice_2 = length(which(option_choice == 2))),
    by = c('participant_id', 'group', 'run', 'trial_type')] %>%
  .[, perc_choice_2 := n_choice_2 / n_overall * 100]

Stats

Manuscript ll. 348-349

# Assure data types
check_risk$participant_id = as.factor(check_risk$participant_id)
check_risk$group = as.factor(check_risk$group)
check_risk$run = as.factor(check_risk$run)
check_risk$trial_type = as.factor(check_risk$trial_type)
check_risk$n_overall = as.numeric(check_risk$n_overall)
check_risk$n_choice_2 = as.numeric(check_risk$n_choice_2)
check_risk$perc_choice_2 = as.numeric(check_risk$perc_choice_2)

# LME
lme_risk = lme4::lmer(data = check_risk,
                      perc_choice_2 ~ group * run + (1|participant_id))
res = Anova(lme_risk)

# Display
papeR::prettify(res)
##                 Chisq Df Pr(>Chisq)    
## 1     group 1.3633208  1      0.243    
## 2       run 0.1544457  1      0.694    
## 3 group:run 0.1544457  1      0.694

Plot

data_plot = Prepare_data_for_plot(check_risk)

dodge_width = 0.3

p_risk = ggplot(data = data_plot,
                aes(x = group,
                    y = perc_choice_2,
                    color = group,
                    fill = group)) +
    geom_point(position = position_jitterdodge(dodge.width = dodge_width,
                                             jitter.width = dodge_width/2,
                                             jitter.height = 0,
                                             seed = 666)) +
  geom_boxplot(outlier.shape = NA,
               color = 'black',
               width = dodge_width,
               position = position_dodge(width = dodge_width)) +
  stat_summary(fun = 'mean',
               geom = 'point',
               na.rm = TRUE,
               shape = 23,
               inherit.aes = TRUE,
               fill = 'white',
               size = 1.5,
               stroke = 0.5,
               position = position_dodge(width = dodge_width),
               show.legend = FALSE) +
  scale_color_manual(values = custom_guides) +
  scale_fill_manual(values = custom_guides)
Neurocodify_plot(p_risk)


Estimation

# State melt columns (to align data types to avoid warnings)
measure_cols = c('est_1_reward',
                 'est_1_range',
                 'avg_1_running',
                 'est_2_reward',
                 'est_2_range',
                 'avg_2_running',
                 'est_3_reward',
                 'est_3_range',
                 'avg_3_running')

# Get estimation data
check_est = data %>%
  # Exclude: estimation specific
  Apply_exclusion_criteria(., choice_based_exclusion = FALSE) %>%
  # Get running average of chosen rewards
  .[, ':='(avg_1_running = Get_running_avg(choice_option = option_choice,
                                     choice_outcome = outcome,
                                     stim = 1),
           avg_2_running = Get_running_avg(choice_option = option_choice,
                                     choice_outcome = outcome,
                                     stim = 2),
           avg_3_running = Get_running_avg(choice_option = option_choice,
                                     choice_outcome = outcome,
                                     stim = 3)),
    by = c('participant_id', 'run')] %>%
  # Column to mark guided choices of mid bandit that produce a rare outcome
  .[, forced_rare := as.numeric(as.logical(is_rare) & trial_type == 'forced' & (comp == '1v2' | comp == '2v3'))] %>%
  .[!is.na(est_1_reward),] %>%
  # Get count for estimation trials
  .[, est_trial := seq(.N), by = c('participant_id', 'run')] %>%
  # Unify data types of measure columns
  .[, (measure_cols) := lapply(.SD, as.double), .SDcols = measure_cols] %>%
  data.table::melt(.,
                   id.vars = c('participant_id',
                               'group',
                               'run',
                               'est_trial',
                               'forced_rare'),
                   measure.vars = measure_cols) %>%
  # Isolate bandit from variable name to use as variable
  .[, est_stim := substr(variable, 5, 5)] %>%
  # Isolate type of value (participants estimate of reward, running average, participants estimate of range/dispersion)
  .[, type := substr(variable, 7, 9)] %>%
  .[type == 'rew', type := 'reward'] %>%
  .[type == 'ran', type := 'range'] %>%
  .[type == 'run', type := 'r_avg']


# Merge true means with estimation
check_est_diff = check_est %>%
  # Wide format to compare true running avg reward and estimate
  data.table::dcast(., participant_id + group + run + est_trial + forced_rare + est_stim ~ type,
                    value.var = 'value') %>%
  # Get difference between estimation and true mean
  .[, diff_from_true := reward - r_avg]

# Get mean estimation accuracy across estimation trials
check_mean_est_diff = check_est_diff %>%
  .[, half := rep(x = c(1,2), each = (max(est_trial)/2)),
    by = c('participant_id', 'group', 'run', 'est_stim')] %>%
  .[, .(mean_diff_from_true = mean(diff_from_true, na.rm = TRUE)),
    by = c('participant_id', 'group', 'run', 'half', 'est_stim')]

Actually different from zero?

Manuscript ll. 362-364

data_test = check_mean_est_diff %>%
  Prepare_data_for_plot(.) %>%
  # Only select second half of trials in each run
  .[half == 2,] %>%
  # Average over runs
  .[, .(mean_diff_from_true = mean(mean_diff_from_true)),
    by = c('participant_id', 'group', 'half', 'est_stim')] %>%
  # Test against 0
  .[, .(estimate = t.test(mean_diff_from_true, mu = 0)$estimate,
        t = t.test(mean_diff_from_true, mu = 0)$statistic,
        df = t.test(mean_diff_from_true, mu = 0)$parameter,
        p = t.test(mean_diff_from_true, mu = 0)$p.value),
    by = c('est_stim', 'group')] %>%
  .[, p_adj_holm := p.adjust(p, method = 'holm')]

knitr::kable(data_test) %>%
  kableExtra::kable_styling()
est_stim group estimate t df p p_adj_holm
Low Younger adults 0.0981497 0.0907161 48 0.9280957 1.0000000
Mid Younger adults -1.9106764 -1.5615204 48 0.1249707 0.6248536
High Younger adults -2.2964143 -2.4371539 48 0.0185599 0.1113594
Low Older adults 0.7572430 0.4777203 49 0.6349736 1.0000000
Mid Older adults -2.5662014 -1.5468204 49 0.1283415 0.6248536
High Older adults -0.7311222 -0.6678440 49 0.5073667 1.0000000

Difference between real distance (running average) and estimated distance

check_est_dist = check_est %>%
  # Put back into long format to calculate distance between estimates
  data.table::dcast(., participant_id + group + run + est_trial + forced_rare ~ paste0(type, '_', est_stim),
                    value.var = 'value') %>%
  # Get distance between critical estimates
  .[, ':='(dist_est_2m1 = reward_2 - reward_1,
           dist_est_3m2 = reward_3 - reward_2)] %>%
  # Get difference of running averages of critical comparisons
  .[, ':='(dist_ravg_2m1 = r_avg_2 - r_avg_1,
           dist_ravg_3m2 = r_avg_3 - r_avg_2)] %>%
  # Add first and second half of run variable
  .[, half := rep(x = c(1,2), each = (max(est_trial)/2)),
    by = c('participant_id', 'group', 'run')] %>%
  .[, half := as.factor(half)] %>%
  # Get difference between real distance and estimated distance (Estimate - Real, EmR)
  .[, ':='(diff_EmR_2m1 = dist_est_2m1 - dist_ravg_2m1,
           diff_EmR_3m2 = dist_est_3m2 - dist_ravg_3m2)] %>%
  # Long format
  data.table::melt(id.vars = c('participant_id', 'group', 'run', 'half', 'est_trial'),
                   measure.vars = c('diff_EmR_2m1',
                                    'diff_EmR_3m2')) %>%
  # Average values
  .[, .(mean_value = mean(value, na.rm = TRUE)),
    by = c('participant_id', 'group', 'run', 'half', 'variable')]

Stats

Manuscript ll. 372-374

# Assure data types
check_est_dist$participant_id = as.factor(check_est_dist$participant_id)
check_est_dist$group = as.factor(check_est_dist$group)
check_est_dist$run = as.factor(check_est_dist$run)
check_est_dist$half = as.factor(check_est_dist$half)
check_est_dist$variable = as.factor(check_est_dist$variable)
check_est_dist$mean_value = as.numeric(check_est_dist$mean_value)

lme_diff_EmR = lme4::lmer(data = check_est_dist[half == '2'],
                          mean_value ~ group * run * variable + (1 | participant_id))
## boundary (singular) fit: see help('isSingular')
papeR::prettify(Anova(lme_diff_EmR))
##                             Chisq Df Pr(>Chisq)    
## 1              group  0.017272043  1      0.895    
## 2                run  0.074886352  1      0.784    
## 3           variable 20.935734495  1     <0.001 ***
## 4          group:run  0.001756923  1      0.967    
## 5     group:variable  4.206102240  1       0.04   *
## 6       run:variable  0.013519938  1      0.907    
## 7 group:run:variable  0.428313592  1      0.513

Main effect: Variable

emmeans::emmeans(lme_diff_EmR,
                 pairwise ~ variable)
## NOTE: Results may be misleading due to involvement in interactions
## $emmeans
##  variable     emmean    SE  df lower.CL upper.CL
##  diff_EmR_2m1  -2.80 0.644 283  -4.0715    -1.54
##  diff_EmR_3m2   1.33 0.644 283   0.0591     2.59
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast                    estimate   SE  df t.ratio p.value
##  diff_EmR_2m1 - diff_EmR_3m2    -4.13 0.91 277  -4.538  <.0001
## 
## Results are averaged over the levels of: group, run 
## Degrees-of-freedom method: kenward-roger

Interaction effect: Group X Variable

Manuscript ll. 371-373 (see diff_EmR_2m1 in each age group; read variable name diff_EmR_2m1 as “difference_Estimation-minus-Real_bandit2-minus-bandit1”)

em = emmeans::emmeans(lme_diff_EmR,
                 pairwise ~ variable | group)
## NOTE: Results may be misleading due to involvement in interactions
# Accurate correction
summary(em, by = NULL, adjust = 'sidak')
## $emmeans
##  variable     group   emmean    SE  df lower.CL upper.CL
##  diff_EmR_2m1 older   -3.678 0.901 281  -5.9358   -1.420
##  diff_EmR_3m2 older    2.319 0.901 281   0.0612    4.577
##  diff_EmR_2m1 younger -1.931 0.920 285  -4.2370    0.375
##  diff_EmR_3m2 younger  0.333 0.920 285  -1.9729    2.639
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## Confidence level used: 0.95 
## Conf-level adjustment: sidak method for 4 estimates 
## 
## $contrasts
##  contrast                    group   estimate   SE  df t.ratio p.value
##  diff_EmR_2m1 - diff_EmR_3m2 older      -6.00 1.27 277  -4.709  <.0001
##  diff_EmR_2m1 - diff_EmR_3m2 younger    -2.26 1.30 277  -1.741  0.1588
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## P value adjustment: sidak method for 2 tests

Comparison of low vs. mid underestimation between age groups; Manuscript ll. 373

# Interaction (contrast of contrast, effect in younger - effect in older)
# https://cran.r-project.org/web/packages/emmeans/vignettes/interactions.html (Section: "Simple contrasts")
em_pairwise = emmeans::emmeans(lme_diff_EmR,
                               pairwise ~ variable | group)
## NOTE: Results may be misleading due to involvement in interactions
emmeans::contrast(em_pairwise, 'consec', simple = 'group', combine = TRUE)
## $emmeans
## variable = diff_EmR_2m1:
##  contrast        estimate   SE  df t.ratio p.value
##  younger - older     1.75 1.29 283   1.357  0.1759
## 
## variable = diff_EmR_3m2:
##  contrast        estimate   SE  df t.ratio p.value
##  younger - older    -1.99 1.29 283  -1.543  0.1240
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger 
## 
## $contrasts
## contrast = diff_EmR_2m1 - diff_EmR_3m2:
##  contrast1       estimate   SE  df t.ratio p.value
##  younger - older     3.73 1.82 277   2.051  0.0412
## 
## Results are averaged over the levels of: run 
## Degrees-of-freedom method: kenward-roger

Plot

data_plot = Prepare_data_for_plot(check_est_dist[half == '2'])

save_path = file.path(base_path,
                      'derivatives',
                      'figures',
                      'f_ed.tsv',
                      fsep = .Platform$file.sep)
data.table::fwrite(data_plot,
                   save_path,
                   sep = '\t',
                   row.names = FALSE,
                   na = 'n/a')

dodge_width = 0.2

p_diff_EmR = ggplot(data = data_plot,
       aes(x = variable,
           y = mean_value,
           color = group,
           fill = group)) +
  geom_hline(yintercept = 0,
             linetype = 'solid',
             size = 0.5) +
  geom_point(position = position_jitterdodge(dodge.width = dodge_width,
                                             jitter.width = dodge_width/2,
                                             jitter.height = 0,
                                             seed = 666)) +
  geom_boxplot(color = 'black',
               outlier.shape = NA,
               position = position_dodge(width = dodge_width),
               width = dodge_width) +
  stat_summary(fun = 'mean',
               geom = 'point',
               na.rm = TRUE,
               shape = 23,
               inherit.aes = TRUE,
               fill = 'white',
               size = 1.5,
               stroke = 0.5,
               show.legend = FALSE,
               position = position_dodge(width = dodge_width)) +
  scale_color_manual(values = custom_guides) +
  scale_fill_manual(values = custom_guides)
Neurocodify_plot(p_diff_EmR)

data_plot = Prepare_data_for_plot(check_est_dist[half == '2'])

dodge_width = 0.2

p_diff_EmR = ggplot(data = data_plot,
       aes(x = variable,
           y = mean_value,
           color = group,
           fill = group)) +
  geom_hline(yintercept = 0,
             linetype = 'solid',
             size = 0.5) +
  geom_point(position = position_jitterdodge(dodge.width = dodge_width,
                                             jitter.width = dodge_width/2,
                                             jitter.height = 0,
                                             seed = 666)) +
  geom_boxplot(color = 'black',
               outlier.shape = NA,
               position = position_dodge(width = dodge_width),
               width = dodge_width) +
  stat_summary(fun = 'mean',
               geom = 'point',
               na.rm = TRUE,
               shape = 23,
               inherit.aes = TRUE,
               fill = 'white',
               size = 1.5,
               stroke = 0.5,
               show.legend = FALSE,
               position = position_dodge(width = dodge_width)) +
  scale_color_manual(values = custom_guides) +
  scale_fill_manual(values = custom_guides) +
  facet_grid(~group)
Neurocodify_plot(p_diff_EmR)