Please refer to the other companion html files (S3, ART accuracy, and S4, ART rts) for more detailed description of the task and pre-processing procedures
With preregistered analyses we have provided evidence for the two-way interaction GVS by Reward. On the contrary, we failed to provide evidence for the three-way GVS by Validity by Reward.
In this document we ran secondary analyses and robustness checks in order to collect converging statistical evidence around the same family of tests of interest. Basically, we aim to respond to the same questions we attempted to respond to with the main inferential approach, which was based on mixed linear regression models. After this first step, we will look for data-driven and post-hoc results in a separate section.
The analyses presented in this document, with the exception of Bayesian ANOVAs, have not been pre-registered, thus they ought to be interpreted with caution.
It’s sometimes good to clean the current environment to avoid conflicts. You can do it with rm(list=ls())
(but be sure everything is properly saved for future use).
In order to run this script we need a few packages available on CRAN. You might need to install them first, e.g. by typing install.packages("BayesFactor")
in the console.
#list packages
packages= c("psych", "plyr", "magrittr", "tidyverse", "BayesFactor", "lme4", "multcomp",
"gridExtra", "ez" , "lsmeans", "retimes")
#load them
lapply(packages, require, character.only= T)
For package afex
I’m using the developer version on github (you will need to install devtools
).
#devtools::install_github("singmann/afex@master")
library(afex)
For reproducibility, here’s details of the system on which the script was tested and a seed for random numbers:
set.seed(1)
sessionInfo()
## R version 3.4.2 (2017-09-28)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 15063)
##
## Matrix products: default
##
## locale:
## [1] LC_COLLATE=English_United Kingdom.1252
## [2] LC_CTYPE=English_United Kingdom.1252
## [3] LC_MONETARY=English_United Kingdom.1252
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United Kingdom.1252
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] afex_0.18-0 retimes_0.1-2 lsmeans_2.27-2
## [4] estimability_1.2 ez_4.4-0 gridExtra_2.3
## [7] multcomp_1.4-7 TH.data_1.0-8 MASS_7.3-47
## [10] survival_2.41-3 mvtnorm_1.0-6 lme4_1.1-14
## [13] BayesFactor_0.9.12-2 Matrix_1.2-11 coda_0.19-1
## [16] dplyr_0.7.4 purrr_0.2.3 readr_1.1.1
## [19] tidyr_0.7.1 tibble_1.3.4 ggplot2_2.2.1
## [22] tidyverse_1.1.1 magrittr_1.5 plyr_1.8.4
## [25] psych_1.7.8 printr_0.1
##
## loaded via a namespace (and not attached):
## [1] nlme_3.1-131 pbkrtest_0.4-7 lubridate_1.6.0
## [4] RColorBrewer_1.1-2 httr_1.3.1 rprojroot_1.2
## [7] tools_3.4.2 backports_1.1.1 R6_2.2.2
## [10] rpart_4.1-11 Hmisc_4.0-3 lazyeval_0.2.0
## [13] mgcv_1.8-20 colorspace_1.3-2 nnet_7.3-12
## [16] mnormt_1.5-5 compiler_3.4.2 rvest_0.3.2
## [19] quantreg_5.33 htmlTable_1.9 SparseM_1.77
## [22] xml2_1.1.1 sandwich_2.4-0 checkmate_1.8.4
## [25] scales_0.5.0 pbapply_1.3-3 stringr_1.2.0
## [28] digest_0.6.12 foreign_0.8-69 minqa_1.2.4
## [31] rmarkdown_1.7 base64enc_0.1-3 pkgconfig_2.0.1
## [34] htmltools_0.3.6 htmlwidgets_0.9 rlang_0.1.2
## [37] readxl_1.0.0 bindr_0.1 zoo_1.8-0
## [40] jsonlite_1.5 gtools_3.5.0 acepack_1.4.1
## [43] car_2.1-5 modeltools_0.2-21 Formula_1.2-2
## [46] Rcpp_0.12.13 munsell_0.4.3 stringi_1.1.5
## [49] yaml_2.1.14 grid_3.4.2 parallel_3.4.2
## [52] forcats_0.2.0 lattice_0.20-35 haven_1.1.0
## [55] splines_3.4.2 hms_0.3 knitr_1.17
## [58] reshape2_1.4.2 codetools_0.2-15 stats4_3.4.2
## [61] glue_1.1.1 evaluate_0.10.1 latticeExtra_0.6-28
## [64] data.table_1.10.4 modelr_0.1.1 nloptr_1.0.4
## [67] MatrixModels_0.4-1 cellranger_1.1.0 gtable_0.2.0
## [70] assertthat_0.2.0 coin_1.2-1 xtable_1.8-2
## [73] broom_0.4.2 lmerTest_2.0-33 bindrcpp_0.2
## [76] cluster_2.0.6
Thanks to the function retrieved here, not displayed, the following hyperlinks download the Rdata files:
That can be loaded then with:
load("ART data.RData")
Now all relevant variables are stored in the ART
data.frame, that you can navigate and explore with the usual commands, e.g. str(ART)
.
You can do the same for SVV and EST data frames, that we will use at the end of this file for correlations.
load("SVV data.RData")
Then the questionnaire:
load("EST data.RData")
We now have all the data produced by this study in the R environment!
This is a minimal (shared by all plots) list of graphic attributes for ggplot:
#ggplot defaults
commonTheme = list(theme_bw(),
theme(text = element_text(size=18, face="bold"),
axis.text = element_text(size=16, face="bold", color= "black"),
plot.margin = unit(c(1,1,1,1), "cm")))
Then (ONLY SHOWN IN THE BOTTOM PART OF THIS SCRIPT) we have a convenient summarising function that produces means and within subjects SEM as in Morey (2008). I retrieved it here: link
Finally, this is a convenience function to plot various results:
my_plot= function(DF, dv= c("correct", "response_time"), withinvars, betweenvars= NULL,
idvar= "Subject",
individual_data= c("none", "p", "l", "violin", "bp"),
plot.gain= T){
#match arguments
individual_data= match.arg(individual_data)
dv= match.arg(dv)
#concatenate all relevant variables
allvars= c(withinvars, betweenvars)
#summarise data according to grouping variables
Xs= ddply(DF, c(allvars, idvar), summarise, y= mean(get(dv)))
#set axis label
if(dv=="correct")(ylab= "Accuracy") else (ylab= "Reaction times (ms)")
#if Validity is involved, summarise the deltas
#code everything as "gain", so it changes for rts and accuracy (positive diff= gain)
if("Validity" %in% allvars & plot.gain== T){
print("Validity is involved: plotting deltas")
if(dv== "correct"){
X.temp= subset(Xs, Validity== "Valid")
X.temp$y= X.temp$y - subset(Xs, Validity== "Invalid")$y} else {
X.temp= subset(Xs, Validity== "Invalid")
X.temp$y= X.temp$y - subset(Xs, Validity== "Valid")$y}
Xs= X.temp[, !names(X.temp) %in% "Validity"]
withinvars= withinvars[!withinvars %in% "Validity"]
allvars= allvars[!allvars %in% "Validity"]
if(dv=="correct")(ylab= "Accuracy (gain)") else (ylab= "Reaction times (gain, ms)")
}
Xs[, idvar]= as.factor(Xs[, idvar])
#obtain summary statistics
X= summarySEwithin(Xs, measurevar = "y",
betweenvars = betweenvars,
withinvars = withinvars, idvar = idvar)
#finally depict!
p= ggplot(X, aes(y= y, x= get(allvars[1]), fill= get(allvars[1]))) + commonTheme
#if it's a delta plot line @ 0
if(grepl("gain", ylab))(p= p + geom_hline(yintercept = 0, linetype= "dashed", size= 1.1,
color= ifelse(individual_data=="l", "darkgray", "gray")))
#if data distribution is asked for
if(individual_data=="l"){
p= p + geom_line(data = Xs, aes(x= get(allvars[1]), y= y, group= get(idvar)),
size= 1.2, color= "dark gray", alpha= 0.5)}
if(individual_data=="p"){
pd= position_dodge(0.5)
p= p + geom_point(data = Xs, aes(x= get(allvars[1]), y= y, group= get(idvar)),
position = pd, size= 3, shape= 21, color= "black", alpha= 0.3)}
if(individual_data=="violin"){
p= p + geom_violin(data = Xs, aes(x= get(allvars[1]), y= y), alpha= 0.3)}
if(individual_data=="bp"){
p= p + geom_boxplot(data = Xs, aes(x= get(allvars[1]), y= y), alpha= 0.3,
outlier.size = 3.5)}
#plot summary statistics
if(individual_data=="none"){
p= p + geom_errorbar(data=X, aes(ymin= y-se, ymax= y+se),
size= 1.5, width= .2, colour= "black") +
geom_point(size= 6, stroke= 2, shape= 21, color= "black")
}
p= p + labs(y= ylab, x= allvars[1]) + guides(fill=F)
#for many variables at once, wrapping
if(length(allvars)>1) (p= p + facet_wrap(allvars[2:length(allvars)]))
#final plot
return(p)
}
Preprocessing procedures are identical to those preregistered. Please refer to the main html file for the ART task for more detailed information.
A bunch of variables is to declare (e.g., numbers into factors). Then, for the sake of clarity, levels are renamed when helpful.
#declare
ART$Subject= as.factor(ART$Subject)
if(sum(levels(as.factor(ART$Reward))== c("0", "2", "10"))==3){
ART$Reward= factor(ART$Reward, labels = c("None", "Small", "High"))} else
(warning("levels unordered? Check"))
if(sum(levels(as.factor(ART$GVS))== c("A", "B", "C"))==3){
ART$GVS= factor(ART$GVS, labels = c("SHAM", "Left-Anodal", "Right-Anodal"))
ART$GVS= relevel(ART$GVS, "Left-Anodal")} else (warning("levels unordered? Check"))
We must drop a few subjects ( :( ). Reasons are in the comments.
#subject 1 to replace for a bug in the script, my fault, :(
ART= ART[!ART$Subject== 1, ] #replaced by subject 51
#subject 10 didn't show up after first session, >:(
ART= ART[!ART$Subject== 10, ] #replaced by subject 60
ART$Subject= factor(ART$Subject)
We create a “Block” variable.
#insert variable "block"
ART$Block= ifelse(ART$Trial< 145, 1,
ifelse(ART$Trial> 288, 3, 2)) %>% as.factor
A valid response was defined as being correct, slower than 100ms, and faster than 500ms.
#exclusion criteria applies to valid responses
ART$Valid_Response= ifelse(ART$correct== 1 &
ART$response_time> 100 &
ART$response_time< 500, 1, 0)
We can save a separate dataframe including incorrect responses, and a second one with only valid ones, this way we will work with reaction times for valid responses only:
#save data for follow-up analyses and plots (before excluding wrong responses)
ART.accuracy= ART
#exclude bad responses
ART= ART[ART$Valid_Response==1, ]
We can start performing our robustness checks!
We can start with a simple ANOVA. First we use ddply()
to collapse and summarise data, then we use the function ezANOVA
to run the analysis.
EZ= ddply(ART, c("GVS", "Reward", "Validity", "Side", "Block", "Subject"),
summarise, dv= mean(response_time)) %>%
ezANOVA(dv= .(dv), wid = .(Subject), within = c("GVS", "Reward", "Validity", "Side", "Block"),
detailed=T, type = 2)
## Warning: Converting "Validity" to factor for ANOVA.
## Warning: Converting "Side" to factor for ANOVA.
We apply Greenhouse-Geisser correction where necessary and then correct for multiple tested factors as outlined in the paper and main analysis.
#tests needing sphericity corrections:
EZ$`Mauchly's Test for Sphericity`$Effect[EZ$`Mauchly's Test for Sphericity`$p <0.05]
## [1] "Reward" "GVS:Block"
## [3] "Reward:Side:Block" "GVS:Reward:Validity:Side:Block"
#apply Greenhouse-Geisser correction if necessary
EZ$ANOVA$p[EZ$ANOVA$Effect %in% EZ$`Mauchly's Test for Sphericity`$Effect[EZ$`Mauchly's Test for Sphericity`$p <0.05]] <-
EZ$`Sphericity Corrections`$`p[GG]`[EZ$`Sphericity Corrections`$Effect %in% EZ$`Mauchly's Test for Sphericity`$Effect[EZ$`Mauchly's Test for Sphericity`$p <0.05]]
#simplify
EZ= EZ$ANOVA
#corrections for multiple testing
interesting_tests= c("Reward", "GVS:Reward", "Reward:Validity", "GVS:Reward:Validity")
EZ$p_fdr= NA
#family of tests of interest
EZ[EZ$Effect %in% interesting_tests, ]$p_fdr=
p.adjust(EZ[EZ$Effect %in% interesting_tests, ]$p, "fdr")
#second family of tests
EZ[!EZ$Effect %in% interesting_tests, ]$p_fdr=
p.adjust(EZ[!EZ$Effect %in% interesting_tests, ]$p, "fdr")
The ez
package by default returns the generalized eta squared. Because more established we switch to the partial eta squared.
EZ$pes= EZ$SSn/(EZ$SSn + EZ$SSd)
Keeping in mind that our alpha threshold was set to 0.043 due to multiple longitudinal data peeks, here’s the results:
EZ$p_fdr_sig= ifelse(EZ$p_fdr<0.043, "*", "")
EZ
Effect | DFn | DFd | SSn | SSd | F | p | p<.05 | ges | p_fdr | pes | p_fdr_sig |
---|---|---|---|---|---|---|---|---|---|---|---|
(Intercept) | 1 | 29 | 4.762113e+08 | 594620.44 | 2.322512e+04 | 0.0000000 | * | 0.9965437 | 0.0000000 | 0.9987529 | * |
GVS | 2 | 58 | 4.250960e+03 | 140258.74 | 8.789316e-01 | 0.4206831 | 0.0025672 | 0.6361232 | 0.0294164 | ||
Reward | 2 | 58 | 3.395922e+04 | 29072.05 | 3.387505e+01 | 0.0000000 | * | 0.0201470 | 0.0000000 | 0.5387678 | * |
Validity | 1 | 29 | 1.849090e+04 | 52013.06 | 1.030964e+01 | 0.0032276 | * | 0.0110717 | 0.0380368 | 0.2622675 | * |
Side | 1 | 29 | 1.185474e+04 | 35334.74 | 9.729450e+00 | 0.0040754 | * | 0.0071265 | 0.0380368 | 0.2512158 | * |
Block | 2 | 58 | 2.452040e+03 | 55498.32 | 1.281285e+00 | 0.2854218 | 0.0014824 | 0.5708436 | 0.0423128 | ||
GVS:Reward | 4 | 116 | 4.496883e+03 | 42647.03 | 3.057882e+00 | 0.0194956 | * | 0.0027153 | 0.0389912 | 0.0953863 | * |
GVS:Validity | 2 | 58 | 4.583726e+02 | 23056.33 | 5.765360e-01 | 0.5650280 | 0.0002775 | 0.7533707 | 0.0194930 | ||
Reward:Validity | 2 | 58 | 7.272197e+02 | 16703.55 | 1.262568e+00 | 0.2905857 | 0.0004401 | 0.3147213 | 0.0417205 | ||
GVS:Side | 2 | 58 | 9.443915e+02 | 18473.66 | 1.482508e+00 | 0.2355438 | 0.0005715 | 0.5073251 | 0.0486347 | ||
Reward:Side | 2 | 58 | 2.377573e+02 | 21682.52 | 3.179963e-01 | 0.7288659 | 0.0001439 | 0.9276475 | 0.0108465 | ||
Validity:Side | 1 | 29 | 1.395363e+03 | 17929.69 | 2.256902e+00 | 0.1438319 | 0.0008441 | 0.3661174 | 0.0722049 | ||
GVS:Block | 4 | 116 | 5.711355e+03 | 80464.28 | 2.058420e+00 | 0.1067697 | 0.0034461 | 0.3321724 | 0.0662757 | ||
Reward:Block | 4 | 116 | 2.492950e+03 | 28385.08 | 2.546956e+00 | 0.0430628 | * | 0.0015071 | 0.2079359 | 0.0807354 | |
Validity:Block | 2 | 58 | 2.023590e+03 | 19677.22 | 2.982338e+00 | 0.0584986 | 0.0012237 | 0.2339946 | 0.0932495 | ||
Side:Block | 2 | 58 | 5.205320e+01 | 15658.45 | 9.640430e-02 | 0.9082420 | 0.0000315 | 0.9684346 | 0.0033133 | ||
GVS:Reward:Validity | 4 | 116 | 7.809296e+02 | 18875.69 | 1.199795e+00 | 0.3147213 | 0.0004726 | 0.3147213 | 0.0397286 | ||
GVS:Reward:Side | 4 | 116 | 8.196145e+02 | 25109.10 | 9.466218e-01 | 0.4397611 | 0.0004960 | 0.6361232 | 0.0316103 | ||
GVS:Validity:Side | 2 | 58 | 4.315930e+02 | 13897.00 | 9.006401e-01 | 0.4119152 | 0.0002612 | 0.6361232 | 0.0301211 | ||
Reward:Validity:Side | 2 | 58 | 1.168359e+03 | 16056.72 | 2.110170e+00 | 0.1304286 | 0.0007069 | 0.3652000 | 0.0678290 | ||
GVS:Reward:Block | 8 | 232 | 3.919349e+03 | 47187.79 | 2.408698e+00 | 0.0162671 | * | 0.0023674 | 0.1138700 | 0.0766889 | |
GVS:Validity:Block | 4 | 116 | 2.733088e+02 | 26198.27 | 3.025374e-01 | 0.8757543 | 0.0001655 | 0.9684346 | 0.0103246 | ||
Reward:Validity:Block | 4 | 116 | 1.212970e+02 | 25732.66 | 1.366984e-01 | 0.9684346 | 0.0000734 | 0.9684346 | 0.0046916 | ||
GVS:Side:Block | 4 | 116 | 8.273514e+02 | 26054.91 | 9.208703e-01 | 0.4543737 | 0.0005007 | 0.6361232 | 0.0307769 | ||
Reward:Side:Block | 4 | 116 | 1.174776e+03 | 32852.39 | 1.037017e+00 | 0.3815907 | 0.0007108 | 0.6361232 | 0.0345246 | ||
Validity:Side:Block | 2 | 58 | 6.537220e+01 | 16617.86 | 1.140817e-01 | 0.8923848 | 0.0000396 | 0.9684346 | 0.0039184 | ||
GVS:Reward:Validity:Side | 4 | 116 | 2.051720e+03 | 23566.13 | 2.524805e+00 | 0.0445577 | * | 0.0012407 | 0.2079359 | 0.0800895 | |
GVS:Reward:Validity:Block | 8 | 232 | 6.297078e+02 | 44612.58 | 4.093358e-01 | 0.9146021 | 0.0003811 | 0.9684346 | 0.0139186 | ||
GVS:Reward:Side:Block | 8 | 232 | 2.242286e+03 | 45999.03 | 1.413645e+00 | 0.1914967 | 0.0013558 | 0.4468256 | 0.0464806 | ||
GVS:Validity:Side:Block | 4 | 116 | 1.206655e+02 | 18987.58 | 1.842942e-01 | 0.9461592 | 0.0000731 | 0.9684346 | 0.0063148 | ||
Reward:Validity:Side:Block | 4 | 116 | 2.243450e+03 | 32332.90 | 2.012193e+00 | 0.0972983 | 0.0013565 | 0.3321724 | 0.0648839 | ||
GVS:Reward:Validity:Side:Block | 8 | 232 | 1.707765e+03 | 46059.64 | 1.075240e+00 | 0.3788624 | 0.0010329 | 0.6361232 | 0.0357517 |
The GVS by Reward interaction is thus confirmed (p=0.019, p_fdr=0.039). There was no three-way interaction.
As a reminder, here’s the graphical depiction of the two-way:
my_plot(ART, dv <- "response_time", withinvars = c("Reward", "GVS"),
individual_data = "none")
Our secondary analysis was based on a Bayesian ANOVA, as implemented in the BayesFactor
package (see for example here). We will use objective prior to avoid some degree of freedom.We use a bottom-up approach which mimics our type 2 test and limits computational time.
DF= ART %>%
ddply(c("GVS", "Validity", "Reward", "Block", "Side", "Subject"),
summarise, dv= mean(response_time)) %>%
mutate(Validity= as.factor(Validity)) %>%
mutate(Side= as.factor(Side))
anovaBF(dv ~ GVS*Reward*Validity*Block*Side + Subject, DF,
whichRandom="Subject", whichModels= "bottom", progress= F)
...the model below... | ...is preferred by... |
---|
The two-way is not confirmed here instead, we only have convincing evidence for the three main effects of Reward, Validity, and Side. We can have a sensitivity check for the two-way, since results partially depend on the specific prior we choose:
sapply(seq(0.1, 1, by= 0.1), function(x)
extractBF(anovaBF(dv ~ GVS:Reward + Subject, DF, rscaleFixed= x,
whichRandom="Subject", whichModels= "bottom", progress= F))["GVS:Reward + Subject", "bf"])
## [1] 3.18432916 1.37310979 0.55360380 0.24968592 0.12363663 0.06589923
## [7] 0.03857219 0.02333978 0.01526622 0.01030670
The Bayes Factor was consistently below 1 with priors >= 0.3. The exceptions were for a prior scaled width of 0.1 (BF>3) or 0.2 (BF= 1.38). In my understanding it is not unusual: it peaks in correspondence of the effect size of the data (pes= ~ 0.1). It is generally quite consistent across different priors, the order of magnitude should not change much, but in this case there’s some ambiguity left…
We can do the same for the three-way GVS by Reward by Validity:
sapply(seq(0.1, 1, by= 0.1), function(x)
extractBF(anovaBF(dv ~ GVS:Reward:Validity + Subject, DF, rscaleFixed= x,
whichRandom="Subject", whichModels= "bottom", progress= F))["GVS:Validity:Reward + Subject", "bf"])
## [1] 0.1931508313 0.0500838163 0.0168206772 0.0068245489 0.0032257959
## [6] 0.0016706283 0.0009565629 0.0005837157 0.0003684057 0.0002505582
The results in this case were more consistent, and even with the narrowest prior (r= 0.1) the BF was supporting the null (BF= 0.19).
How many subjects show an effect of reward that is more pronounced (steep) for the SHAM condition with respect to the Right-Anodal condition? RTs are now predicted by the number of points rewarded for each response (0, 2, 10); b coefficients are extracted for each Subject and GVS condition. We now proceed in calculating the differences between slopes. B coefficients are first multiplied by -1, which is more intuitive. Then the coefficients for Right-Anodal are subtracted from those in the SHAM condition. At this point large values indicate larger abolishment of reward sensitivity, negative values an enhancement instead.
slopes= ddply(ART, c("Reward", "GVS", "Subject"),
summarise, RT= mean(response_time)) %>%
ddply(c("GVS", "Subject"), summarise,
b= lm(RT ~ c(0, 2, 10))$coefficients[2]) %>%
with(lapply(levels(Subject), function(x){
return((b[Subject==x & GVS=="SHAM"]*(-1)) - (b[Subject==x & GVS=="Right-Anodal"])*(-1))})) %>%
unlist
sum(slopes>0)
## [1] 22
The effect was thus there for 22 subjects over 30 (73.3%).
boxplot(slopes)
t.test(slopes)
##
## One Sample t-test
##
## data: slopes
## t = 3.0365, df = 29, p-value = 0.00502
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## 0.216610 1.110461
## sample estimates:
## mean of x
## 0.6635356
The 95% CI of the indices we computed do not include 0. We will use this index for correlations of interest in exploratory analyses.
We now assess data distribution with greater details. We set a response limit to 500 ms. As a result, we now have a truncated distribution. The following plot helps understanding if something is happening beyond this limit, with respect to the Reward by GVS interaction that we have described. (We also had a lower bound to 100 ms but anticipations were very uncommon).
p_full= ART.accuracy %>% filter(correct==1) %>% #exclude wrong responses only
ggplot(aes(x= response_time, fill= Reward)) +
commonTheme + xlab("Response Time (ms)") + xlim(c(0, 800)) +
geom_vline(xintercept= c(100, 500), linetype= "dashed", size= 1.1, color= "darkgray") +
geom_density(alpha= 0.3) +
facet_wrap("GVS", nrow= 3)
p_zoom_centre= ART.accuracy %>% filter(correct==1) %>% #exclude wrong responses only
ggplot(aes(x= response_time, fill= Reward)) +
commonTheme + xlab("Response Time (ms)") + xlim(c(0, 800)) +
geom_vline(xintercept= c(100, 500), linetype= "dashed", size= 1.1, color= "darkgray") +
geom_density(alpha= 0.3) + coord_cartesian(xlim= c(350, 400), ylim= c(0.005, 0.008))+
facet_wrap("GVS", nrow= 3) + guides(fill= F)
p_zoom_tail= ART.accuracy %>% filter(correct==1) %>% #exclude wrong responses only
ggplot(aes(x= response_time, fill= Reward)) +
commonTheme + xlab("Response Time (ms)") + xlim(c(0, 800)) +
geom_vline(xintercept= c(100, 500), linetype= "dashed", size= 1.1, color= "darkgray") +
geom_density(alpha= 0.3) + coord_cartesian(xlim= c(500, 650), ylim= c(0, 0.002))+
facet_wrap("GVS", nrow= 3) + guides(fill= F)
grid.arrange(p_full, p_zoom_centre, p_zoom_tail, layout_matrix = rbind(c(1,1), c(2,3)))
Here we can see that high rewards are associated with: smaller latencies; higher peaks for fast RTs; smaller “long tails” of the distribution. This is fairly abolished for the Right-Anodal GVS condition (bottom). At least visually not much seems to be going on after our cut off (500 ms, gray dashed line). Specifically, a residual effect of Reward (larger density of RTs following no rewards or small rewards with respect to high rewards) seems to be present for all GVS conditions. The distribution can be described by three parameters: mu, reflecting the mean of the gaussian part of the distribution, sigma, reflecting its amplitude (or standard deviation), and tau, reflecting the exponential component of the long tail and thus “residual” effects. We can calculate the three indices with the package retimes
and submit them to three ANOVAs. We will do it only for GVS and Reward factors (more observations yield more precise estimates).
DF= ART.accuracy %>% filter(correct==1) %>%
ddply(c("GVS", "Reward", "Subject"), summarise, mu= mexgauss(response_time)[1],
sigma= mexgauss(response_time)[2],
tau= mexgauss(response_time)[3])
#results
#mu
with(DF, tapply(mu, list(Reward, GVS), mean))
Left-Anodal | SHAM | Right-Anodal | |
---|---|---|---|
None | 356.2487 | 356.2280 | 353.1263 |
Small | 351.9080 | 354.7869 | 353.9909 |
High | 351.7638 | 344.6082 | 350.6483 |
#sigma
with(DF, tapply(sigma, list(Reward, GVS), mean))
Left-Anodal | SHAM | Right-Anodal | |
---|---|---|---|
None | 42.88395 | 44.66523 | 43.67564 |
Small | 39.97114 | 44.24642 | 44.33242 |
High | 42.98603 | 42.27451 | 43.41478 |
#tau
with(DF, tapply(tau, list(Reward, GVS), mean))
Left-Anodal | SHAM | Right-Anodal | |
---|---|---|---|
None | 42.11099 | 39.32095 | 42.08372 |
Small | 43.56226 | 37.88330 | 39.12253 |
High | 35.19182 | 38.40608 | 37.53430 |
#mu
ezANOVA(DF, .(mu), wid= .(Subject), within= c("GVS", "Reward"), type= 2)
## $ANOVA
## Effect DFn DFd F p p<.05 ges
## 2 GVS 2 58 0.1217468 0.8855981505 0.0008050604
## 3 Reward 2 58 10.4370612 0.0001343724 * 0.0159213159
## 4 GVS:Reward 4 116 2.6160531 0.0387085152 * 0.0097063143
##
## $`Mauchly's Test for Sphericity`
## Effect W p p<.05
## 2 GVS 0.8552253 0.111976623
## 3 Reward 0.6541973 0.002629798 *
## 4 GVS:Reward 0.8335650 0.835458429
##
## $`Sphericity Corrections`
## Effect GGe p[GG] p[GG]<.05 HFe p[HF]
## 2 GVS 0.8735343 0.8594669522 0.9248924 0.8708217107
## 3 Reward 0.7430510 0.0006653983 * 0.7738463 0.0005488448
## 4 GVS:Reward 0.9181949 0.0436797442 * 1.0678569 0.0387085152
## p[HF]<.05
## 2
## 3 *
## 4 *
#sigma
ezANOVA(DF, .(sigma), wid= .(Subject), within= c("GVS", "Reward"), type= 2)
## $ANOVA
## Effect DFn DFd F p p<.05 ges
## 2 GVS 2 58 0.5286642 0.5922045 0.005967015
## 3 Reward 2 58 0.4432447 0.6441063 0.001370816
## 4 GVS:Reward 4 116 1.3593005 0.2523990 0.007221980
##
## $`Mauchly's Test for Sphericity`
## Effect W p p<.05
## 2 GVS 0.9540365 0.51749946
## 3 Reward 0.9426039 0.43712842
## 4 GVS:Reward 0.5073998 0.02908939 *
##
## $`Sphericity Corrections`
## Effect GGe p[GG] p[GG]<.05 HFe p[HF] p[HF]<.05
## 2 GVS 0.9560563 0.5843385 1.0219213 0.5922045
## 3 Reward 0.9457194 0.6332514 1.0097025 0.6441063
## 4 GVS:Reward 0.7438499 0.2607817 0.8382644 0.2579425
#tau
ezANOVA(DF, .(tau), wid= .(Subject), within= c("GVS", "Reward"), type= 2)
## $ANOVA
## Effect DFn DFd F p p<.05 ges
## 2 GVS 2 58 0.4291027 0.653143114 0.004094088
## 3 Reward 2 58 6.8736681 0.002094325 * 0.024027791
## 4 GVS:Reward 4 116 2.0100810 0.097607620 0.020605192
##
## $`Mauchly's Test for Sphericity`
## Effect W p p<.05
## 2 GVS 0.8068438 0.04955103 *
## 3 Reward 0.8772292 0.15980243
## 4 GVS:Reward 0.8884916 0.95405985
##
## $`Sphericity Corrections`
## Effect GGe p[GG] p[GG]<.05 HFe p[HF]
## 2 GVS 0.8381133 0.618291065 0.8836041 0.628716108
## 3 Reward 0.8906538 0.003153231 * 0.9449246 0.002573091
## 4 GVS:Reward 0.9431881 0.101985345 1.1018103 0.097607620
## p[HF]<.05
## 2
## 3 *
## 4
Most of the differences (GVS by Reward interaction) seem thus driven by a displacement in the mu parameter rather than tau - residual - processing. The tau parameter though appears to present indeed the reverse pattern, suggesting the time-pressure was relevant.
During each session, and from the first to the last one, participants on average gradually improved. We sought to check whether somehow this has a role in the effects we reported (for example being only or particularly evident for the first session, where variability was greater).
We now have to connect each GVS session with its order.
#ART$datetime #this variables includes session dates and time
ART$datetime[1] #e.g.
## [1] "07/10/17 10:12:59"
#initialise Session variable
ART$SessionNb= NA
#only retain the dates:
ART$day= as.Date(ART$datetime, format= "%m/%d/%Y %H:%M:%S")
#now summarise for each subject and GVS session
tapply(ART$day, list(ART$Subject, ART$GVS), function(x) as.character(x[1]))
Left-Anodal | SHAM | Right-Anodal | |
---|---|---|---|
2 | 0017-06-28 | 0017-06-14 | 0017-06-22 |
3 | 0017-06-14 | 0017-06-16 | 0017-06-22 |
4 | 0017-06-14 | 0017-06-23 | 0017-06-16 |
5 | 0017-06-30 | 0017-06-29 | 0017-06-28 |
6 | 0017-07-07 | 0017-07-21 | 0017-06-29 |
7 | 0017-06-29 | 0017-06-22 | 0017-06-30 |
8 | 0017-06-30 | 0017-06-22 | 0017-06-23 |
9 | 0017-07-03 | 0017-07-07 | 0017-07-12 |
11 | 0017-07-19 | 0017-07-10 | 0017-07-07 |
12 | 0017-07-12 | 0017-07-20 | 0017-07-10 |
13 | 0017-07-20 | 0017-07-12 | 0017-07-21 |
14 | 0017-07-21 | 0017-07-19 | 0017-07-20 |
15 | 0017-08-21 | 0017-08-23 | 0017-08-25 |
16 | 0017-08-23 | 0017-08-31 | 0017-08-30 |
17 | 0017-09-06 | 0017-08-30 | 0017-08-25 |
18 | 0017-08-30 | 0017-08-31 | 0017-08-28 |
19 | 0017-08-30 | 0017-08-28 | 0017-09-04 |
20 | 0017-09-07 | 0017-08-31 | 0017-09-06 |
21 | 0017-09-04 | 0017-09-06 | 0017-09-07 |
22 | 0017-09-25 | 0017-10-02 | 0017-09-27 |
23 | 0017-10-05 | 0017-10-04 | 0017-09-27 |
24 | 0017-10-04 | 0017-10-05 | 0017-09-27 |
25 | 0017-10-12 | 0017-10-11 | 0017-10-13 |
26 | 0017-10-13 | 0017-10-11 | 0017-10-12 |
27 | 0017-10-11 | 0017-10-13 | 0017-10-18 |
28 | 0017-10-16 | 0017-10-20 | 0017-10-18 |
29 | 0017-10-23 | 0017-10-20 | 0017-10-16 |
30 | 0017-10-20 | 0017-10-23 | 0017-10-18 |
51 | 0017-10-05 | 0017-09-27 | 0017-10-06 |
60 | 0017-10-02 | 0017-10-06 | 0017-10-04 |
The table above reports the dates in which we tested subjects with the relative GVS session.
#for every subject, check the order of sessions/days and assign value to SessionNb
for(i in levels(ART$Subject)){
ordered= sort(levels(as.factor(ART$day[ART$Subject== i])))
ART$SessionNb[ART$Subject== i & ART$day== ordered[1]]<- 1
ART$SessionNb[ART$Subject== i & ART$day== ordered[2]]<- 2
ART$SessionNb[ART$Subject== i & ART$day== ordered[3]]<- 3
}
#now summarise and double check with laboratory log
tapply(ART$GVS, list(ART$Subject, ART$SessionNb), function(x) as.character(x[1]))
1 | 2 | 3 | |
---|---|---|---|
2 | SHAM | Right-Anodal | Left-Anodal |
3 | Left-Anodal | SHAM | Right-Anodal |
4 | Left-Anodal | Right-Anodal | SHAM |
5 | Right-Anodal | SHAM | Left-Anodal |
6 | Right-Anodal | Left-Anodal | SHAM |
7 | SHAM | Left-Anodal | Right-Anodal |
8 | SHAM | Right-Anodal | Left-Anodal |
9 | Left-Anodal | SHAM | Right-Anodal |
11 | Right-Anodal | SHAM | Left-Anodal |
12 | Right-Anodal | Left-Anodal | SHAM |
13 | SHAM | Left-Anodal | Right-Anodal |
14 | SHAM | Right-Anodal | Left-Anodal |
15 | Left-Anodal | SHAM | Right-Anodal |
16 | Left-Anodal | Right-Anodal | SHAM |
17 | Right-Anodal | SHAM | Left-Anodal |
18 | Right-Anodal | Left-Anodal | SHAM |
19 | SHAM | Left-Anodal | Right-Anodal |
20 | SHAM | Right-Anodal | Left-Anodal |
21 | Left-Anodal | SHAM | Right-Anodal |
22 | Left-Anodal | Right-Anodal | SHAM |
23 | Right-Anodal | SHAM | Left-Anodal |
24 | Right-Anodal | Left-Anodal | SHAM |
25 | SHAM | Left-Anodal | Right-Anodal |
26 | SHAM | Right-Anodal | Left-Anodal |
27 | Left-Anodal | SHAM | Right-Anodal |
28 | Left-Anodal | Right-Anodal | SHAM |
29 | Right-Anodal | SHAM | Left-Anodal |
30 | Right-Anodal | Left-Anodal | SHAM |
51 | SHAM | Left-Anodal | Right-Anodal |
60 | Left-Anodal | Right-Anodal | SHAM |
The table above reports the Order of GVS sessions for each subject. You can spot a pattern (e.g. for the first column, i.e. session 1) which is only interrupted by drop outs (i.e. subjects 1 and 10). It matches the counterbalancing strategy that we adopted.
The small, gradual increase in accuracy is also mirrored by reaction times, gradually faster across sessions:
with(ART, tapply(response_time, list(SessionNb, Subject), mean)) %>% rowMeans()
## 1 2 3
## 386.9252 382.5694 380.3050
Now we create a between-subject factor that refers to the specific counterbalancing order of GVS sessions.
ART$GVS_Order= NA
for(i in levels(ART$Subject)){
ART$GVS_Order[ART$Subject== i]=
paste(ART$GVS[ART$Subject== i & ART$SessionNb== 1][1],
ART$GVS[ART$Subject== i & ART$SessionNb== 2][1],
ART$GVS[ART$Subject== i & ART$SessionNb== 3][1])
}
ART$GVS_Order= as.factor(ART$GVS_Order)
levels(ART$GVS_Order)
## [1] "Left-Anodal Right-Anodal SHAM" "Left-Anodal SHAM Right-Anodal"
## [3] "Right-Anodal Left-Anodal SHAM" "Right-Anodal SHAM Left-Anodal"
## [5] "SHAM Left-Anodal Right-Anodal" "SHAM Right-Anodal Left-Anodal"
We can now summarise data by collapsing everything in a dataframe, then run ANOVAs. Of course now GVS_Order is a between-subject variable that leaves only a few participants per cell, so not very powerful…
ART %>%
mutate(GVS_Order= factor(GVS_Order)) %>%
ddply(c("GVS", "GVS_Order", "Reward", "Subject"),
summarise, dv= mean(response_time)) %>%
ezANOVA(dv= .(dv), wid = .(Subject), within = c("GVS", "Reward"), between = .(GVS_Order),
detailed=T, type = 2)
## $ANOVA
## Effect DFn DFd SSn SSd F
## 1 (Intercept) 1 24 3.967485e+07 37657.693 2.528557e+04
## 2 GVS_Order 5 24 1.207264e+04 37657.693 1.538827e+00
## 3 GVS 2 48 3.125049e+02 9019.739 8.315227e-01
## 5 Reward 2 48 2.821254e+03 2101.406 3.222133e+01
## 4 GVS_Order:GVS 10 48 2.635796e+03 9019.739 1.402681e+00
## 6 GVS_Order:Reward 10 48 2.538851e+02 2101.406 5.799206e-01
## 7 GVS:Reward 4 96 3.746108e+02 3023.324 2.973767e+00
## 8 GVS_Order:GVS:Reward 20 96 5.541377e+02 3023.324 8.797805e-01
## p p<.05 ges
## 1 8.523519e-38 * 0.998696035
## 2 2.153563e-01 0.189004695
## 3 4.415596e-01 0.005996487
## 5 1.341046e-09 * 0.051649164
## 4 2.079395e-01 0.048418345
## 6 8.221152e-01 0.004877149
## 7 2.310578e-02 * 0.007179647
## 8 6.122005e-01 0.010583975
##
## $`Mauchly's Test for Sphericity`
## Effect W p p<.05
## 3 GVS 0.9290687 0.42909172
## 4 GVS_Order:GVS 0.9290687 0.42909172
## 5 Reward 0.7795250 0.05702238
## 6 GVS_Order:Reward 0.7795250 0.05702238
## 7 GVS:Reward 0.6502134 0.38095807
## 8 GVS_Order:GVS:Reward 0.6502134 0.38095807
##
## $`Sphericity Corrections`
## Effect GGe p[GG] p[GG]<.05 HFe
## 3 GVS 0.9337667 4.347910e-01 1.0095652
## 4 GVS_Order:GVS 0.9337667 2.138618e-01 1.0095652
## 5 Reward 0.8193531 3.004341e-08 * 0.8713193
## 6 GVS_Order:Reward 0.8193531 7.915819e-01 0.8713193
## 7 GVS:Reward 0.8428075 3.117597e-02 * 0.9971602
## 8 GVS_Order:GVS:Reward 0.8428075 5.980285e-01 0.9971602
## p[HF] p[HF]<.05
## 3 4.415596e-01
## 4 2.079395e-01
## 5 1.226864e-08 *
## 6 8.010671e-01
## 7 2.323044e-02 *
## 8 6.119598e-01
We confirmed the effects of Reward and GVS by Reward. The GVS_Order had no effect, nor alone nor in interaction. It’s true that this factor has 6 levels, which leaves a total of 5 subjects per cell…
We can plot the two-way separately for SessionNb (for each session GVS will be a between groups variable):
The first session:
my_plot(ART[ART$SessionNb== 1,], dv <- "response_time", withinvars = "Reward", betweenvars= "GVS")
The effect of Reward appears large in SHAM, but very less so in both GVS conditions (especially Right-Anodal). The second session:
my_plot(ART[ART$SessionNb== 2,], dv <- "response_time", withinvars = "Reward", betweenvars= "GVS")
Presented less clear differences between conditions. Finally, the last session:
my_plot(ART[ART$SessionNb== 3,], dv <- "response_time", withinvars = "Reward", betweenvars= "GVS")
Present a pattern more similar to the first one: the effect of Reward seems reduced in both GVS conditions, Right-Anodal in first place.
To summarise: at least visually, the decrease in the effect of Reward appears there for the first and last sessions, less so for the second one (just remember that the three facets in each graph depict three different groups of subjects in this case)… This would speak against novelty/practice effects to drive entirely the observed results.
After collecting encouraging and converging statistical evidence for the two-way GVS by Reward interaction, as opposed to the three-way, we can cope with other questions. This part is completely data-driven, be warned.
It is possible that the Side of the cue, instead of that of the target, had an important role. We ran a further ANOVA with Cue_Side instead of Target_side.
We need to code it first:
ART$Cue_Side= NA
ART$Cue_Side= ifelse(ART$Validity== "Valid", ART$Side,
ifelse(ART$Side== "Left", "Right", "Left"))
#rename
ART$Cue_Side= ifelse(ART$Cue_Side== "Left", "Left Cue", "Right Cue")
table(ART$Side, ART$Cue_Side, ART$Validity)
Var1 | Var2 | Var3 | Freq |
---|---|---|---|
Left | Left Cue | Invalid | 0 |
Valid | 8553 | ||
Right Cue | Invalid | 8624 | |
Valid | 0 | ||
Right | Left Cue | Invalid | 8694 |
Valid | 0 | ||
Right Cue | Invalid | 0 | |
Valid | 8718 |
We can now compute the ANOVA.
EZ= ddply(ART, c("GVS", "Validity", "Reward", "Block", "Cue_Side", "Subject"),
summarise, dv= mean(response_time)) %>%
ezANOVA(dv= .(dv), wid = .(Subject), within = c("GVS", "Validity", "Reward", "Block", "Cue_Side"),
detailed=T, type = 2)
## Warning: Converting "Validity" to factor for ANOVA.
## Warning: Converting "Cue_Side" to factor for ANOVA.
We apply Greenhouse-Geisser correction where necessary and then correct for multiple tested factors (similar to what was preregistered).
#tests needing sphericity corrections:
EZ$`Mauchly's Test for Sphericity`$Effect[EZ$`Mauchly's Test for Sphericity`$p <0.05]
## [1] "Reward" "GVS:Block"
## [3] "GVS:Reward:Block:Cue_Side" "Validity:Reward:Block:Cue_Side"
#apply Greenhouse-Geisser correction if necessary
EZ$ANOVA$p[EZ$ANOVA$Effect %in% EZ$`Mauchly's Test for Sphericity`$Effect[EZ$`Mauchly's Test for Sphericity`$p <0.05]] <-
EZ$`Sphericity Corrections`$`p[GG]`[EZ$`Sphericity Corrections`$Effect %in% EZ$`Mauchly's Test for Sphericity`$Effect[EZ$`Mauchly's Test for Sphericity`$p <0.05]]
#simplify
EZ= EZ$ANOVA
#corrections for multiple testing
interesting_tests= c("Reward", "GVS:Reward", "Validity:Reward", "GVS:Validity:Reward")
EZ$p_fdr= NA
#family of tests of interest
EZ[EZ$Effect %in% interesting_tests, ]$p_fdr=
p.adjust(EZ[EZ$Effect %in% interesting_tests, ]$p, "fdr")
#second family of tests
EZ[!EZ$Effect %in% interesting_tests, ]$p_fdr=
p.adjust(EZ[!EZ$Effect %in% interesting_tests, ]$p, "fdr")
The ez
package by default returns the generalized eta squared. Because more established we switch to the partial eta squared.
EZ$pes= EZ$SSn/(EZ$SSn + EZ$SSd)
Keeping in mind that our alpha threshold was set to 0.043 due to multiple longitudinal data peeks, here’s the results:
EZ$p_fdr_sig= ifelse(EZ$p_fdr<0.043, "*", "")
EZ
Effect | DFn | DFd | SSn | SSd | F | p | p<.05 | ges | p_fdr | pes | p_fdr_sig |
---|---|---|---|---|---|---|---|---|---|---|---|
(Intercept) | 1 | 29 | 4.762113e+08 | 594620.44 | 2.322512e+04 | 0.0000000 | * | 0.9965437 | 0.0000000 | 0.9987529 | * |
GVS | 2 | 58 | 4.250960e+03 | 140258.74 | 8.789316e-01 | 0.4206831 | 0.0025672 | 0.6361232 | 0.0294164 | ||
Validity | 1 | 29 | 1.849090e+04 | 52013.06 | 1.030964e+01 | 0.0032276 | * | 0.0110717 | 0.0380368 | 0.2622675 | * |
Reward | 2 | 58 | 3.395922e+04 | 29072.05 | 3.387505e+01 | 0.0000000 | * | 0.0201470 | 0.0000000 | 0.5387678 | * |
Block | 2 | 58 | 2.452040e+03 | 55498.32 | 1.281285e+00 | 0.2854218 | 0.0014824 | 0.5708436 | 0.0423128 | ||
Cue_Side | 1 | 29 | 1.395363e+03 | 17929.69 | 2.256902e+00 | 0.1438319 | 0.0008441 | 0.3661174 | 0.0722049 | ||
GVS:Validity | 2 | 58 | 4.583726e+02 | 23056.33 | 5.765360e-01 | 0.5650280 | 0.0002775 | 0.7533707 | 0.0194930 | ||
GVS:Reward | 4 | 116 | 4.496883e+03 | 42647.03 | 3.057882e+00 | 0.0194956 | * | 0.0027153 | 0.0389912 | 0.0953863 | * |
Validity:Reward | 2 | 58 | 7.272197e+02 | 16703.55 | 1.262568e+00 | 0.2905857 | 0.0004401 | 0.3147213 | 0.0417205 | ||
GVS:Block | 4 | 116 | 5.711355e+03 | 80464.28 | 2.058420e+00 | 0.1067697 | 0.0034461 | 0.3321724 | 0.0662757 | ||
Validity:Block | 2 | 58 | 2.023590e+03 | 19677.22 | 2.982338e+00 | 0.0584986 | 0.0012237 | 0.2339946 | 0.0932495 | ||
Reward:Block | 4 | 116 | 2.492950e+03 | 28385.08 | 2.546956e+00 | 0.0430628 | * | 0.0015071 | 0.2079359 | 0.0807354 | |
GVS:Cue_Side | 2 | 58 | 4.315930e+02 | 13897.00 | 9.006401e-01 | 0.4119152 | 0.0002612 | 0.6361232 | 0.0301211 | ||
Validity:Cue_Side | 1 | 29 | 1.185474e+04 | 35334.74 | 9.729450e+00 | 0.0040754 | * | 0.0071265 | 0.0380368 | 0.2512158 | * |
Reward:Cue_Side | 2 | 58 | 1.168359e+03 | 16056.72 | 2.110170e+00 | 0.1304286 | 0.0007069 | 0.3652000 | 0.0678290 | ||
Block:Cue_Side | 2 | 58 | 6.537220e+01 | 16617.86 | 1.140817e-01 | 0.8923848 | 0.0000396 | 0.9684346 | 0.0039184 | ||
GVS:Validity:Reward | 4 | 116 | 7.809296e+02 | 18875.69 | 1.199795e+00 | 0.3147213 | 0.0004726 | 0.3147213 | 0.0397286 | ||
GVS:Validity:Block | 4 | 116 | 2.733088e+02 | 26198.27 | 3.025374e-01 | 0.8757543 | 0.0001655 | 0.9684346 | 0.0103246 | ||
GVS:Reward:Block | 8 | 232 | 3.919349e+03 | 47187.79 | 2.408698e+00 | 0.0162671 | * | 0.0023674 | 0.1138700 | 0.0766889 | |
Validity:Reward:Block | 4 | 116 | 1.212970e+02 | 25732.66 | 1.366984e-01 | 0.9684346 | 0.0000734 | 0.9684346 | 0.0046916 | ||
GVS:Validity:Cue_Side | 2 | 58 | 9.443915e+02 | 18473.66 | 1.482508e+00 | 0.2355438 | 0.0005715 | 0.5073251 | 0.0486347 | ||
GVS:Reward:Cue_Side | 4 | 116 | 2.051720e+03 | 23566.13 | 2.524805e+00 | 0.0445577 | * | 0.0012407 | 0.2079359 | 0.0800895 | |
Validity:Reward:Cue_Side | 2 | 58 | 2.377573e+02 | 21682.52 | 3.179963e-01 | 0.7288659 | 0.0001439 | 0.9276475 | 0.0108465 | ||
GVS:Block:Cue_Side | 4 | 116 | 1.206655e+02 | 18987.58 | 1.842942e-01 | 0.9461592 | 0.0000731 | 0.9684346 | 0.0063148 | ||
Validity:Block:Cue_Side | 2 | 58 | 5.205320e+01 | 15658.45 | 9.640430e-02 | 0.9082420 | 0.0000315 | 0.9684346 | 0.0033133 | ||
Reward:Block:Cue_Side | 4 | 116 | 2.243450e+03 | 32332.90 | 2.012193e+00 | 0.0972983 | 0.0013565 | 0.3321724 | 0.0648839 | ||
GVS:Validity:Reward:Block | 8 | 232 | 6.297078e+02 | 44612.58 | 4.093358e-01 | 0.9146021 | 0.0003811 | 0.9684346 | 0.0139186 | ||
GVS:Validity:Reward:Cue_Side | 4 | 116 | 8.196145e+02 | 25109.10 | 9.466218e-01 | 0.4397611 | 0.0004960 | 0.6361232 | 0.0316103 | ||
GVS:Validity:Block:Cue_Side | 4 | 116 | 8.273514e+02 | 26054.91 | 9.208703e-01 | 0.4543737 | 0.0005007 | 0.6361232 | 0.0307769 | ||
GVS:Reward:Block:Cue_Side | 8 | 232 | 1.707765e+03 | 46059.64 | 1.075240e+00 | 0.3788624 | 0.0010329 | 0.6361232 | 0.0357517 | ||
Validity:Reward:Block:Cue_Side | 4 | 116 | 1.174776e+03 | 32852.39 | 1.037017e+00 | 0.3815907 | 0.0007108 | 0.6361232 | 0.0345246 | ||
GVS:Validity:Reward:Block:Cue_Side | 8 | 232 | 2.242286e+03 | 45999.03 | 1.413645e+00 | 0.1914967 | 0.0013558 | 0.4468256 | 0.0464806 |
Results are similar, though Validity and Cue_Side now interact. Let’s depict it:
my_plot(ART, dv <- "response_time", withinvars = c("Validity", "Cue_Side"),
individual_data = "none")
## [1] "Validity is involved: plotting deltas"
## Automatically converting the following non-factors to factors: Cue_Side
The IOR is stronger following left-sided cues. I wonder whether this is related to the main effect of target Side (faster RTs for right-sided targets). Here’s the values:
with(ART, tapply(response_time, list(Validity, Cue_Side), mean))
Left Cue | Right Cue | |
---|---|---|
Invalid | 378.1478 | 383.1535 |
Valid | 386.4758 | 383.8956 |
There is an interaction of Cue_side with GVS and Reward, at the uncorrected level:
my_plot(ART, dv <- "response_time", withinvars = c("Reward", "Cue_Side", "GVS"),
individual_data = "none")
## Automatically converting the following non-factors to factors: Cue_Side
It seems that the reduction of reward-related boost in performance is more evident in Right-Anodal GVS when cues appear on the Left side…
We promised a Bayesian counterpart:
DF= ART %>%
ddply(c("GVS", "Validity", "Reward", "Block", "Cue_Side", "Subject"),
summarise, dv= mean(response_time)) %>%
mutate(Validity= as.factor(Validity)) %>%
mutate(Cue_Side= as.factor(Cue_Side))
sapply(seq(0.1, 1, by= 0.1), function(x)
extractBF(anovaBF(dv ~ GVS:Reward:Cue_Side + Subject, DF, rscaleFixed= x,
whichRandom="Subject", whichModels= "bottom", progress= F))["GVS:Reward:Cue_Side + Subject", "bf"])
## [1] 0.481549420 0.178695393 0.072420227 0.032553075 0.016183058
## [6] 0.008808577 0.005076783 0.003151238 0.002043715 0.001374324
The BF remains consistently below 1…
We reported that the effect of Reward is less pronounced for Right-Anodal stimulation with respect to the SHAM. The first question thus concerns a possible correlation between this and the effectiveness of GVS measured with an established perceptual task (i.e. SVV). We load and pre-process SVV data as in the relative file (please refer to S1: SVV file for detailed information).
#declare
SVV$Subject= as.factor(SVV$Subject)
SVV$GVS= factor(SVV$GVS, labels = c("SHAM", "Left-Anodal", "Right-Anodal"))
SVV$GVS= relevel(SVV$GVS, "Left-Anodal")
#drop subjects
#subject 1 to replace for a bug in the ART script, my fault, :(
SVV= SVV[!SVV$Subject== 1, ] #replaced by subject 51
#subject 10 didn't show up after first session, >:(
SVV= SVV[!SVV$Subject== 10, ] #replaced by subject 60
SVV$Subject= factor(SVV$Subject)
#trim
#create a variables containing normalized values, for each subject
SVV$scaledFO= 0
for (i in levels(SVV$Subject)) {
SVV$scaledFO[SVV$Subject== i]= scale(SVV$FinalOrientation[SVV$Subject== i])
}
#outlier if more than 2.5 away from the mean
SVV$Outlier= ifelse(abs(SVV$scaledFO)>2.5, 1, 0)
SVV= SVV[SVV$Outlier==0,]
SVV data are summarised in the companion file. Here we store the difference between SVV orientation between the Right-Anodal condition and the SHAM:
x= with(SVV, tapply(FinalOrientation, list(subject_nr, GVS), mean))
x= x[,3]- x[,2]
DF.correlations= data.frame(SVV_effects= x)
Larger values ideally indicate a larger perceptual GVS effect.
Now we summarise the effect of Reward, mostly seen for the maximum Reward amount against the null reward condition. Please refer to the relative Supplementary file to access all the details and information.
Calculating the slope of Reward for each Subject and GVS should account for the small Reward as well.
y= ddply(ART, c("Reward", "GVS", "Subject"),
summarise, RT= mean(response_time)) %>%
ddply(c("GVS", "Subject"),
summarise, b= lm(RT ~ c(0, 2, 10))$coefficients[2])
RTs are now predicted by the number of points rewarded for each response (0, 2, 10); b coefficients are extracted for each Subject and GVS condition. We now proceed in calculating the differences between slopes. B coefficients are first multiplied by -1, which is more intuitive. Then the coefficients for Right-Anodal are subtracted from those in the SHAM condition. At this point large values indicate larger abolishment of reward sensitivity, small values an enhancement instead. (We did this before when computing the variable slopes
).
#same as raw difference
#calculates delta/abolishment
y= unlist(lapply(levels(y$Subject), function(x){
return((y$b[y$Subject==x & y$GVS=="SHAM"]*(-1)) - (y$b[y$Subject==x & y$GVS=="Right-Anodal"])*(-1))
}))
The number of subjects showing a slope that was more negative (steep) for SHAM with respect to Right-Anodal (pointing to enhanced sensitivity to Reward) is, as seen before:
sum(y>0)
## [1] 22
We add the slopes to our data frame:
DF.correlations= data.frame(DF.correlations, GVS_Reward= y)
Third question: is the decrease in sensitivity to rewards related to the decrease of attentional capture (as shown by the tendency for inhibition of return to be enhanced with large rewards in SHAM but less in right-Anodal condition)?
Similarly to what we did before, we now compute the slopes for the validity costs (deltas) for each Subject and GVS condition.
#possibly a bit unelegant... Sorry...
x= {}
for(i in levels(ART$Subject)){
foo= ART[ART$Subject==i,] %>% with(tapply(response_time, list(Reward, Validity, GVS), mean))
foo= foo[, 1,]- foo[, 2,]
foo= apply(foo, 2, function(d) lm(d ~ c(0, 2, 10))$coefficients[2])
#we now have coefficients for each GVS condition
foo= foo[2]*(-1) - foo[3]*(-1)
names(foo)= NULL
x= c(x, foo)
}
X now indexes the amount of abolishment of attentional capture by high rewards due to the Right-Anodal condition as compared to SHAM. It seems less consistent than the effects of GVS on Reward, because shown by:
sum(x>0)
## [1] 18
Subjects (60%). A few extra checks:
boxplot(x)
t.test(x)
##
## One Sample t-test
##
## data: x
## t = 1.1891, df = 29, p-value = 0.244
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## -0.2303410 0.8701817
## sample estimates:
## mean of x
## 0.3199204
And finally we add these slopes to our data frame:
DF.correlations= data.frame(DF.correlations, GVS_Validity_Reward= x)
Now we want to check whether unspecific effects, such as faster/slower reaction times in one condition, have a role (perhaps faster RTs, for example, are more close to the floor and hamper cognitive effects).
y= with(ART, tapply(response_time, list(Subject, GVS), mean))
y= y[,3]- y[,2]
#add to data frame
DF.correlations= data.frame(DF.correlations, GVS= y)
Positive values indicate that Right-Anodal condition was faster than SHAM, the opposite for negative values.
Finally, we check for correlations with subject’s illusions of body rotation, which was the only item proven (barely) significant (please refer to the relative supplementary file, S2, for detailed information about questionnaires).
#PREPROCESSING
FraToEngQuestions= list("I felt my body moving", "I felt my body moving",
"I felt my body rotating", "I felt my head leaning",
"I felt the visual scene moving", "I felt vertigo",
"I felt nausea", "I felt my body or one part of it changing size",
"I felt itching", "I felt burning",
"I felt uncomfortable", "I was focused on the task",
"I felt motivated in accomplishing the task", "I felt motivated by rewards")
FraToEngQuestions= setNames(FraToEngQuestions, unique(EST$Question))
#check
names(FraToEngQuestions) %in% EST$Question
## [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#now substitute
EST$Eng_Question= unlist(FraToEngQuestions[EST$Question])
#same for responses left / right
FraToEngLeft= list("Left", "Downward", "Backward", "Not at all")
FraToEngRight= list("Right", "Upward", "Forward", "Very much")
FraToEngLeft= setNames(FraToEngLeft, unique(EST$RespLeft))
FraToEngRight= setNames(FraToEngRight, unique(EST$RespRight))
EST$Eng_Left= unlist(FraToEngLeft[EST$RespLeft])
EST$Eng_Right= unlist(FraToEngRight[EST$RespRight])
#declare
EST$Subject= as.factor(EST$Subject)
if(sum(levels(as.factor(EST$GVS))== c("A", "B", "C"))==3){
EST$GVS= factor(EST$GVS, labels = c("SHAM", "Left-Anodal", "Right-Anodal"))
EST$GVS= relevel(EST$GVS, "Left-Anodal")} else (warning("levels unordered? Check"))
#subject 1 to replace for a bug in the script, my fault, :(
EST= EST[!EST$Subject== 1, ] #replaced with n. 51
#subject 10 didn't show up after first session, >:(
EST= EST[!EST$Subject== 10, ] #replaced with n. 60
EST$Subject= factor(EST$Subject)
#for the three similar questions...
EST$Quest_Nb= 0
interaction= interaction(EST$Subject, EST$GVS)
invisible(sapply(levels(interaction(EST$Subject, EST$GVS)), function(x){
EST$Quest_Nb[interaction== x] <<- 1:15
}))
#half-length of the segment is 350 pixels
EST$X_Dev= EST$X_Resp/3.50
We can now summarise:
x= with(EST[EST$Quest_Nb==4,], tapply(X_Dev, list(Subject, GVS), mean))
x= x[,3]- x[,2]
#add to data frame
DF.correlations= data.frame(DF.correlations, GVS_Rotation= x)
Positive values indicate that Right-Anodal was associated with subjective feeling of clockwise body motion.
We can finally explore correlations, let’s prepare the functions:
#function to extract interesting information/parameters/coefficients
lm_parameters = function(y, x, data) {
m= cor.test(data[,names(data)==y], data[,names(data)==x])
DF= data.frame(y= data[,names(data)==y], x= data[,names(data)==x])
#BFs for regressions with only one IV==BFs for correlations
#Note that default objective priors differ from those of ANOVAs
bf= extractBF(regressionBF(y ~ x, data = DF, progress = F))$bf
(l= list(R = format(m$estimate, digits = 2),
P = format(m$p.value, digits = 2),
bf = format(bf, digits = 2)))
}
#adjust color according to strength of evidence
col.critic<-function(x,trasparency= 0.3, direction="large is better"){
if (direction=="small.better")(
col<-rgb(x,1-x,0,trasparency)
) else (
col<-rgb(1-x,x,0,trasparency)
)
return(col)
}
my_corplot= function(x, y, xlab, ylab, Cook_omit= T, data= DF.correlations){
DF= data.frame(y= data[,names(data)==y], x= data[,names(data)==x])
#in case of outliers
if(Cook_omit) {
DF= DF[!cooks.distance(lm(y ~ x, DF)) > 4/nrow(DF),]
print(paste("Influential observations omitted:", nrow(DF), "Subjects included"))
}
color= col.critic(abs(as.numeric(
lm_parameters("y", "x", DF)$R)))
p= ggplot(DF, aes(x= x, y= y)) + commonTheme +
geom_point(shape= 21, size= 4, stroke= 2, fill= color) +
geom_smooth(method= lm, color= "black", fill=color) +
ggtitle(paste0("R= ", lm_parameters("y", "x", DF)$R,
", p= ", lm_parameters("y", "x", DF)$P,
", BF= ", lm_parameters("y", "x", DF)$bf)) +
ylab(ylab) + xlab(xlab)
return(p)
}
We associate each variable and label:
corr_var= list("SVV_effects"= "GVS perceptual effects (SVV, °)",
"GVS_Reward"= "GVS effects on Reward (ms)",
"GVS_Validity_Reward"= "GVS effects on Attentional Capture (ms)",
"GVS"= "GVS general RTs advantage (ms)",
"GVS_Rotation"= "GVS-induced illusion of rotation (%)")
Set up all possible combinations:
corr_comb= combn(length(colnames(DF.correlations)), 2)
Finally plot!
for (i in 1: ncol(corr_comb)){
p= my_corplot(colnames(DF.correlations)[corr_comb[1, i]],
colnames(DF.correlations)[corr_comb[2, i]],
corr_var[colnames(DF.correlations)[corr_comb[1, i]]][[1]],
corr_var[colnames(DF.correlations)[corr_comb[2, i]]][[1]])
print(p)
}
## [1] "Influential observations omitted: 27 Subjects included"
## [1] "Influential observations omitted: 30 Subjects included"
## [1] "Influential observations omitted: 27 Subjects included"
## [1] "Influential observations omitted: 28 Subjects included"
## [1] "Influential observations omitted: 29 Subjects included"
## [1] "Influential observations omitted: 27 Subjects included"
## [1] "Influential observations omitted: 28 Subjects included"
## [1] "Influential observations omitted: 29 Subjects included"
## [1] "Influential observations omitted: 27 Subjects included"
## [1] "Influential observations omitted: 26 Subjects included"
Thus, this is the only possible link among those many:
my_corplot("GVS_Reward", "GVS_Validity_Reward", "GVS effects on Reward (ms)", "GVS effects on Attentional Capture (ms)")
## [1] "Influential observations omitted: 29 Subjects included"
The reduction in attentional capture seems to correlate - at least visually, and to a small extent - with the reduction in sensitivity to rewards.
Note that statistics displayed on plots: 1) exclude extremely influential points; 2) are not corrected for multiple tests. This is what happens when a correction is applied (but no influent points are omitted):
corr.test(DF.correlations, adjust= "fdr")
## Call:corr.test(x = DF.correlations, adjust = "fdr")
## Correlation matrix
## SVV_effects GVS_Reward GVS_Validity_Reward GVS
## SVV_effects 1.00 -0.05 0.12 -0.04
## GVS_Reward -0.05 1.00 0.28 0.08
## GVS_Validity_Reward 0.12 0.28 1.00 0.30
## GVS -0.04 0.08 0.30 1.00
## GVS_Rotation -0.17 -0.02 0.15 -0.50
## GVS_Rotation
## SVV_effects -0.17
## GVS_Reward -0.02
## GVS_Validity_Reward 0.15
## GVS -0.50
## GVS_Rotation 1.00
## Sample Size
## [1] 30
## Probability values (Entries above the diagonal are adjusted for multiple tests.)
## SVV_effects GVS_Reward GVS_Validity_Reward GVS
## SVV_effects 0.00 0.93 0.90 0.93
## GVS_Reward 0.80 0.00 0.46 0.93
## GVS_Validity_Reward 0.54 0.14 0.00 0.46
## GVS 0.84 0.67 0.11 0.00
## GVS_Rotation 0.38 0.93 0.43 0.00
## GVS_Rotation
## SVV_effects 0.87
## GVS_Reward 0.93
## GVS_Validity_Reward 0.87
## GVS 0.05
## GVS_Rotation 0.00
##
## To see confidence intervals of the correlations, print with the short=FALSE option
There is one significant correlation:
my_corplot("GVS", "GVS_Rotation", "GVS general RTs advantage (ms)", "GVS-induced illusion of rotation (%)",
Cook_omit=F)
Perhaps not surprising: those who experienced the largest GVS illusions of body rotation were generally slower with respect to the SHAM (but note that this changes dramatically when excluding four extreme observations). Data from the self-evaluation, in addition, do not appear properly normally-distributed.
my_corplot("GVS", "GVS_Rotation", "GVS general RTs advantage (ms)", "GVS-induced illusion of rotation (%)",
Cook_omit=T)
## [1] "Influential observations omitted: 26 Subjects included"
This is the function to summarise data (summarySEwithin
):
## Summarizes data.
## Gives count, mean, standard deviation, standard error of the mean, and confidence interval (default 95%).
## data: a data frame.
## measurevar: the name of a column that contains the variable to be summariezed
## groupvars: a vector containing names of columns that contain grouping variables
## na.rm: a boolean that indicates whether to ignore NA's
## conf.interval: the percent range of the confidence interval (default is 95%)
summarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE,
conf.interval=.95, .drop=TRUE) {
library(plyr)
# New version of length which can handle NA's: if na.rm==T, don't count them
length2 <- function (x, na.rm=FALSE) {
if (na.rm) sum(!is.na(x))
else length(x)
}
# This does the summary. For each group's data frame, return a vector with
# N, mean, and sd
datac <- ddply(data, groupvars, .drop=.drop,
.fun = function(xx, col) {
c(N = length2(xx[[col]], na.rm=na.rm),
mean = mean (xx[[col]], na.rm=na.rm),
sd = sd (xx[[col]], na.rm=na.rm)
)
},
measurevar
)
# Rename the "mean" column
datac <- plyr::rename(datac, c(mean = measurevar))
datac$se <- datac$sd / sqrt(datac$N) # Calculate standard error of the mean
# Confidence interval multiplier for standard error
# Calculate t-statistic for confidence interval:
# e.g., if conf.interval is .95, use .975 (above/below), and use df=N-1
ciMult <- qt(conf.interval/2 + .5, datac$N-1)
datac$ci <- datac$se * ciMult
return(datac)
}
## Norms the data within specified groups in a data frame; it normalizes each
## subject (identified by idvar) so that they have the same mean, within each group
## specified by betweenvars.
## data: a data frame.
## idvar: the name of a column that identifies each subject (or matched subjects)
## measurevar: the name of a column that contains the variable to be summariezed
## betweenvars: a vector containing names of columns that are between-subjects variables
## na.rm: a boolean that indicates whether to ignore NA's
normDataWithin <- function(data=NULL, idvar, measurevar, betweenvars=NULL,
na.rm=FALSE, .drop=TRUE) {
library(plyr)
# Measure var on left, idvar + between vars on right of formula.
data.subjMean <- ddply(data, c(idvar, betweenvars), .drop=.drop,
.fun = function(xx, col, na.rm) {
c(subjMean = mean(xx[,col], na.rm=na.rm))
},
measurevar,
na.rm
)
# Put the subject means with original data
data <- merge(data, data.subjMean)
# Get the normalized data in a new column
measureNormedVar <- paste(measurevar, "_norm", sep="")
data[,measureNormedVar] <- data[,measurevar] - data[,"subjMean"] +
mean(data[,measurevar], na.rm=na.rm)
# Remove this subject mean column
data$subjMean <- NULL
return(data)
}
## Retrieved here: http://www.cookbook-r.com/Graphs/Plotting_means_and_error_bars_(ggplot2)/
## Summarizes data, handling within-subjects variables by removing inter-subject variability.
## It will still work if there are no within-S variables.
## Gives count, un-normed mean, normed mean (with same between-group mean),
## standard deviation, standard error of the mean, and confidence interval.
## If there are within-subject variables, calculate adjusted values using method from Morey (2008).
## data: a data frame.
## measurevar: the name of a column that contains the variable to be summariezed
## betweenvars: a vector containing names of columns that are between-subjects variables
## withinvars: a vector containing names of columns that are within-subjects variables
## idvar: the name of a column that identifies each subject (or matched subjects)
## na.rm: a boolean that indicates whether to ignore NA's
## conf.interval: the percent range of the confidence interval (default is 95%)
summarySEwithin <- function(data=NULL, measurevar, betweenvars=NULL, withinvars=NULL,
idvar=NULL, na.rm=FALSE, conf.interval=.95, .drop=TRUE) {
# Ensure that the betweenvars and withinvars are factors
factorvars <- vapply(data[, c(betweenvars, withinvars), drop=FALSE],
FUN=is.factor, FUN.VALUE=logical(1))
if (!all(factorvars)) {
nonfactorvars <- names(factorvars)[!factorvars]
message("Automatically converting the following non-factors to factors: ",
paste(nonfactorvars, collapse = ", "))
data[nonfactorvars] <- lapply(data[nonfactorvars], factor)
}
# Get the means from the un-normed data
datac <- summarySE(data, measurevar, groupvars=c(betweenvars, withinvars),
na.rm=na.rm, conf.interval=conf.interval, .drop=.drop)
# Drop all the unused columns (these will be calculated with normed data)
datac$sd <- NULL
datac$se <- NULL
datac$ci <- NULL
# Norm each subject's data
ndata <- normDataWithin(data, idvar, measurevar, betweenvars, na.rm, .drop=.drop)
# This is the name of the new column
measurevar_n <- paste(measurevar, "_norm", sep="")
# Collapse the normed data - now we can treat between and within vars the same
ndatac <- summarySE(ndata, measurevar_n, groupvars=c(betweenvars, withinvars),
na.rm=na.rm, conf.interval=conf.interval, .drop=.drop)
# Apply correction from Morey (2008) to the standard error and confidence interval
# Get the product of the number of conditions of within-S variables
nWithinGroups <- prod(vapply(ndatac[,withinvars, drop=FALSE], FUN=nlevels,
FUN.VALUE=numeric(1)))
correctionFactor <- sqrt( nWithinGroups / (nWithinGroups-1) )
# Apply the correction factor
ndatac$sd <- ndatac$sd * correctionFactor
ndatac$se <- ndatac$se * correctionFactor
ndatac$ci <- ndatac$ci * correctionFactor
# Combine the un-normed means with the normed results
merge(datac, ndatac)
}