library(tidyverse)
library(interactions)
library(emmeans)
library(effectsize)
library(metafor)
library(DescTools)
library(kableExtra)
options(es.use_symbols = TRUE) 
dat_1 = read.delim("MME12.txt")
getwd()
## [1] "D:/drive_gmail/Recherche/Articles BASTIEN/Morning_morality/DataExperimentalStudy"
dat_2 = read.delim("data_study2_MME.txt")

S1. Study 1: meta-analysis

Functions performing data analysis and facilitating data reporting

Function performing correlations (depending on the study)

CorS1<-function(x){
     cor(dplyr::select(x,
             Moral_DIL,
             Heure_TOT))}

CorS2S3<-function(x){
     cor(dplyr::select(x,
             Moral_SCA,
             Heure_TOT))}

CorS4<-function(x){
     cor(dplyr::select(x,
             Moral_DIL,
             Moral_SCA,
             Moral_CAR,
             Heure_TOT))}

CorS5S6<-function(x){
     cor(dplyr::select(x,
           Moral_DIL,
           Moral_SCA,
           Heure_TOT))}

CorFUNCTION <- function(x) {
  ifelse(unique(x$Study)==1,
    COR <- CorS1(x),
    ifelse(unique(x$Study) %in% c(2,3),
       COR<- CorS2S3(x),
        ifelse(unique(x$Study)==4,
          COR<- CorS4(x),
            ifelse(unique(x$Study) %in% c(5,6),
              COR<- CorS5S6(x),
      NA))))
   COR}

Function computing variance/covariance matrix based on the equation approach (Steiger, 1980). All the credit for this function should be given to Pr Wolfgang Viechtbauer.

rmat <- function(x, n, upper=TRUE, simplify=TRUE, rtoz=FALSE, data) {

   if (inherits(x, "formula")) {

      options(na.action = "na.pass")

      if (missing(data))
         stop("Must specify 'data' argument when 'x' is a formula.")

      if (!is.data.frame(data))
         data <- data.frame(data)

      dat <- get_all_vars(x, data=data)

      if (ncol(dat) != 4)
         stop("Incorrect number of variables specified in formula.")

      id <- dat[,4]
      dat <- split(dat, id)

      res <- list()

      for (i in 1:length(dat)) {

         ri <- dat[[i]][[1]]
         var1 <- as.character(dat[[i]][[2]])
         var2 <- as.character(dat[[i]][[3]])

         vars <- sort(unique(c(var1, var2)))

         R <- matrix(NA, nrow=length(vars), ncol=length(vars))
         diag(R) <- 1
         rownames(R) <- colnames(R) <- vars

         for (j in 1:length(var1)) {
            R[var1[j],var2[j]] <- R[var2[j],var1[j]] <- ri[j]
         }

         res[[i]] <- R

      }

      return(rmat(res, n=n, simplify=TRUE, rtoz=rtoz))

   }

   if (is.list(x)) {

      k <- length(x)

      if (length(x) != length(n))
         stop("Argument 'n' must be of the same length as there are elements in 'x'.")

      res <- list()

      for (i in 1:k) {
         res[[i]] <- rmat(x[[i]], n[i], upper=upper, rtoz=rtoz)
      }

      if (simplify) {
         ki <- sapply(res, function(x) ifelse(is.null(x$dat), 0, nrow(x$dat)))
         dat <- cbind(id=rep(1:k, times=ki), do.call(rbind, lapply(res, "[[", "dat")))
         V <- bldiag(lapply(res[ki > 0], "[[", "V"))
         rownames(V) <- colnames(V) <- unlist(lapply(res, function(x) rownames(x$V)))
         return(list(dat=dat, V=V))
      } else {
         return(res)
      }

   }

   if (!is.matrix(x))
      stop("Argument 'x' must be a matrix (or list thereof).")

   if (dim(x)[1] != dim(x)[2])
      stop("Argument 'x' must be a square matrix (or list thereof).")

   dimsx <- nrow(x)
   dnames <- paste0("x", 1:dimsx)

   ### in case x has dimension names, use those

   if (!is.null(rownames(x)))
      dnames <- rownames(x)
   if (!is.null(colnames(x)))
      dnames <- colnames(x)

   ### in case x is a 1x1 (or 0x0) matrix, return nothing

   if (dimsx <= 1L)
      return(list(dat=NULL, V=NULL))

   ### make x symmetric, depending on whether we use upper or lower part

   if (upper) {
      x[lower.tri(x)] <- t(x)[lower.tri(x)]
   } else {
      x[upper.tri(x)] <- t(x)[upper.tri(x)]
   }

   ### check if x is symmetric (can be skipped since x must now be symmetric)

   #if (!isSymmetric(x))
   #   stop("x must be a symmetric matrix.")

   ### stack upper/lower triangular part of x into a column vector (this is always done column-wise!)

   if (upper) {
      ri <- cbind(x[upper.tri(x)])
   } else {
      ri <- cbind(x[lower.tri(x)])
   }

   ### apply r-to-z transformation if requested

   if (rtoz)
      ri <- 1/2 * log((1 + ri)/(1 - ri))

   ### I and J are matrices with 1:dimsx for rows and columns, respectively

   I <- matrix(1:dimsx, nrow=dimsx, ncol=dimsx)
   J <- matrix(1:dimsx, nrow=dimsx, ncol=dimsx, byrow=TRUE)

   ### get upper/lower triangular elements of I and J

   if (upper) {
      I <- I[upper.tri(I)]
      J <- J[upper.tri(J)]
   } else {
      I <- I[lower.tri(I)]
      J <- J[lower.tri(J)]
   }

   ### dimensions in V (must be dimsx*(dimsx-1)/2)

   dimsV <- length(ri)

   ### set up V matrix

   V <- matrix(NA, nrow=dimsV, ncol=dimsV)

   for (ro in 1:dimsV) {
      for (co in 1:dimsV) {

         i <- I[ro]
         j <- J[ro]
         k <- I[co]
         l <- J[co]

         ### Olkin & Finn (1995), equation 5, page 157

         V[ro,co] <- 1/2 * x[i,j]*x[k,l] * (x[i,k]^2 + x[i,l]^2 + x[j,k]^2 + x[j,l]^2) +
                     x[i,k]*x[j,l] + x[i,l]*x[j,k] -
                     (x[i,j]*x[i,k]*x[i,l] + x[j,i]*x[j,k]*x[j,l] + x[k,i]*x[k,j]*x[k,l] + x[l,i]*x[l,j]*x[l,k])

         ### Steiger (1980), equation 2, page 245 (provides the same result - checked)

         #V[ro,co] <- 1/2 * ((x[i,k] - x[i,j]*x[j,k]) * (x[j,l] - x[j,k]*x[k,l]) +
         #                   (x[i,l] - x[i,k]*x[k,l]) * (x[j,k] - x[j,i]*x[i,k]) +
         #                   (x[i,k] - x[i,l]*x[l,k]) * (x[j,l] - x[j,i]*x[i,l]) +
         #                   (x[i,l] - x[i,j]*x[j,l]) * (x[j,k] - x[j,l]*x[l,k]))

         ### Steiger (1980), equation 11, page 247 for r-to-z transformed values

         if (rtoz)
            V[ro,co] <- V[ro,co] / ((1 - x[i,j]^2) * (1 - x[k,l]^2))

      }
   }

   ### divide V by (n-1) for raw correlations and by (n-3) for r-to-z transformed correlations

   if (rtoz) {
      V <- V/(n-3)
   } else {
      V <- V/(n-1)
   }

   ### create matrix with var1 and var2 names and sort rowwise

   dmat <- cbind(dnames[I], dnames[J])
   dmat <- t(apply(dmat, 1, sort))

   ### set row/column names for V

   var1var2 <- paste0(dmat[,1], ".", dmat[,2])
   rownames(V) <- colnames(V) <- var1var2

   return(list(dat=data.frame(yi=ri, var1=dmat[,1], var2=dmat[,2], var1var2=var1var2, stringsAsFactors=FALSE), V=V))

}

Function computing the correlation and variance/covariance matrix while converting Pearson’s r to Fisher’s Z

 RmatFUNCTION<-function(x,y){ 
   rmat(x, n=y, rtoz=TRUE)}

Function extracting adjusted confidence intervals for correlations

CorCIFUNCTION<-function(x,y){
  CorCI(x,y, conf.level = (1-(0.05)))
}

Function extracting critical information from meta analyses (performed using metafor)

ExtractMetaFUNCTION<-function(x){
  cbind(
     Raw.r = FisherZInv(as.numeric(as.character(x$b))),
     p.val = as.numeric(as.character(x$pval)),
     Adj.p.val=as.numeric(as.character(x$pval)),
     Adj.CIlow=FisherZInv(x$ci.lb),
     Adj.CIup=FisherZInv(x$ci.ub),
     TOST1=abs(FisherZInv(as.numeric(as.character(x$b))-as.numeric(as.character(x$se*qnorm(1-2*(0.05/(2))))))),
     TOST2=abs(FisherZInv(as.numeric(as.character(x$b))+as.numeric(as.character(x$se*qnorm(1-2*(0.05/(2))))))),
     Cochran.Q=x$QE,
     Cochran.Q.p.val=x$QEp)
}

Function extracting critical information about heterogeneity

# x is the rma object and y the variance covariance matrix
ExtractHeterogeneityFUNCTION<-function(x, y){
     W<-solve(y)
     X<-model.matrix(x)
     P <- W - W %*% X %*% solve(t(X) %*% W %*% X) %*% t(X) %*% W
     cbind(
       stud.out=as.character(data.frame(x$g.levels.k)[,1]),
       N=data.frame(x$g.levels.k)[,2],
       Tau.sq=x$tau2,
       Tau=sqrt(x$tau2),
       Cochran.Q=x$QE,
       Cochran.Q.p.val=x$QEp,
       I.sq=100 *x$tau2 / (x$tau2 + (x$k-x$p)/sum(diag(P))))
}

Functions selecting appropriate data in the correlation or variance/covariance matrix (i.e., information regarding only the relationship between sleep and moral outcomes and not those regarding - for example - the relationship between 2 sleep indicators)

PredictFUNCTION<-function(x){
x[str_detect(row.names(x), "Heure") & str_detect(row.names(x), "Moral"),
  str_detect(colnames(x), "Heure") & str_detect(colnames(x), "Moral")]}

Data preparation

Multivariate random effects meta analysis (var-covar matrix estimated using equation based approach)

dat_1.split <- split(dat_1, dat_1$Study)

Obtaining sample size for each dataset

N.prim<-sapply(dat_1.split, nrow)

Obtention of Correlation matrix and Variance-Covariance matrix for each study

Cor.matrix.prim<-lapply(dat_1.split, CorFUNCTION)

List.Cor.Varcovar.prim<-mapply(RmatFUNCTION, Cor.matrix.prim, N.prim, SIMPLIFY = FALSE)

S1a. Associations between predictors

datS4 = dat_1.split[[4]]
datS5 = dat_1.split[[5]]
datS6 = dat_1.split[[6]]
cor(datS4[, c("Moral_DIL", "Moral_SCA", "Moral_CAR")])
##             Moral_DIL Moral_SCA   Moral_CAR
## Moral_DIL  1.00000000 0.5130030 -0.01507911
## Moral_SCA  0.51300302 1.0000000  0.21691107
## Moral_CAR -0.01507911 0.2169111  1.00000000
cor(datS5[, c("Moral_SCA", "Moral_DIL")])
##           Moral_SCA Moral_DIL
## Moral_SCA 1.0000000 0.4049381
## Moral_DIL 0.4049381 1.0000000
cor(datS6[, c("Moral_SCA", "Moral_DIL")])
##           Moral_SCA Moral_DIL
## Moral_SCA 1.0000000 0.3128948
## Moral_DIL 0.3128948 1.0000000

S1b. Meta-analysis

# Correlation (Fisher's z) between all sleep indicators and all outcomes for each study

ES.prim_transit<-do.call(rbind, lapply(List.Cor.Varcovar.prim, function(x) x$dat))

ES.prim_transit1<-data.frame(cbind(
  stud.out=row.names(ES.prim_transit), ES.prim_transit))

ES.prim_transit2<-separate(ES.prim_transit1, stud.out,
                          into=c("Study"))
## Warning: Expected 1 pieces. Additional pieces discarded in 12 rows [4, 5, 6, 7,
## 8, 9, 10, 11, 12, 13, 14, 15].
# Here, we delete all correlations not regarding sleep-moral outcomes relationship (k=40)

ES.Prim<-ES.prim_transit2 %>%
  rename("Outcome"=var2,
         "Predictor"=var1,
         "Out.Pred"=var1var2) %>% 
  mutate(
    Asso.of.int=case_when(
      str_detect(Outcome, "Heure") & str_detect(Predictor, "Moral") ~ 1,
      str_detect(Outcome, "Moral") & str_detect(Predictor, "Heure") ~ 1)) %>%
  filter(Asso.of.int==1)

ES.Prim$N<-rep(N.prim, c(1,1,1,3,2,2))

ES.Prim$ES.ID<-1:nrow(ES.Prim)

#Variance-covariance matrix for each study (N=6)
List.Varcovar.prim_transit1<-lapply(List.Cor.Varcovar.prim, function(x) x$V)

# we apply the fourpredictfunction to keep only associations of interest
List.Varcovar.prim<-lapply(List.Varcovar.prim_transit1, PredictFUNCTION)

V.Prim = bldiag(list(
  List.Varcovar.prim[[1]], 
  List.Varcovar.prim[[2]],
  List.Varcovar.prim[[3]],
  List.Varcovar.prim[[4]], 
  List.Varcovar.prim[[5]],
  List.Varcovar.prim[[6]]))

# Primary analysis 
Meta.Prim<-rma.mv(yi,V.Prim, 
                  random = ~ Out.Pred | Study, 
                  struct="UN", 
                  data=ES.Prim)

# moderation analysis
Meta.Deux<-rma.mv(yi,V.Prim, 
                  mods=~Outcome-1,
                  random = ~ Out.Pred | Study, 
                  struct="UN", 
                  data=ES.Prim)
Meta.Prim
## 
## Multivariate Meta-Analysis Model (k = 10; method: REML)
## 
## Variance Components:
## 
## outer factor: Study    (nlvls = 6)
## inner factor: Out.Pred (nlvls = 3)
## 
##             estim    sqrt  k.lvl  fixed                level 
## tau^2.1    0.0318  0.1785      1     no  Heure_TOT.Moral_CAR 
## tau^2.2    0.0072  0.0849      4     no  Heure_TOT.Moral_DIL 
## tau^2.3    0.0411  0.2028      5     no  Heure_TOT.Moral_SCA 
## 
##                      rho.H_TOT.M_C  rho.H_TOT.M_D  rho.H_TOT.M_S    H_TOT.M_C 
## Heure_TOT.Moral_CAR              1                                          - 
## Heure_TOT.Moral_DIL         0.2994              1                          no 
## Heure_TOT.Moral_SCA         0.9790         0.4876              1           no 
##                      H_TOT.M_D  H_TOT.M_S 
## Heure_TOT.Moral_CAR          1          1 
## Heure_TOT.Moral_DIL          -          3 
## Heure_TOT.Moral_SCA         no          - 
## 
## Test for Heterogeneity:
## Q(df = 9) = 23.5454, p-val = 0.0051
## 
## Model Results:
## 
## estimate      se     zval    pval    ci.lb    ci.ub    
##  -0.1359  0.0605  -2.2468  0.0247  -0.2544  -0.0173  * 
## 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Meta.Deux
## 
## Multivariate Meta-Analysis Model (k = 10; method: REML)
## 
## Variance Components:
## 
## outer factor: Study    (nlvls = 6)
## inner factor: Out.Pred (nlvls = 3)
## 
##             estim    sqrt  k.lvl  fixed                level 
## tau^2.1    0.0022  0.0468      1     no  Heure_TOT.Moral_CAR 
## tau^2.2    0.0087  0.0932      4     no  Heure_TOT.Moral_DIL 
## tau^2.3    0.0119  0.1092      5     no  Heure_TOT.Moral_SCA 
## 
##                      rho.H_TOT.M_C  rho.H_TOT.M_D  rho.H_TOT.M_S    H_TOT.M_C 
## Heure_TOT.Moral_CAR              1                                          - 
## Heure_TOT.Moral_DIL         0.0000              1                          no 
## Heure_TOT.Moral_SCA         0.0000         1.0000              1           no 
##                      H_TOT.M_D  H_TOT.M_S 
## Heure_TOT.Moral_CAR          1          1 
## Heure_TOT.Moral_DIL          -          3 
## Heure_TOT.Moral_SCA         no          - 
## 
## Test for Residual Heterogeneity:
## QE(df = 7) = 10.1715, p-val = 0.1791
## 
## Test of Moderators (coefficients 1:3):
## QM(df = 3) = 13.4814, p-val = 0.0037
## 
## Model Results:
## 
##                   estimate      se     zval    pval    ci.lb    ci.ub     
## OutcomeMoral_CAR    0.0483  0.1103   0.4375  0.6617  -0.1679   0.2645     
## OutcomeMoral_DIL   -0.1866  0.0671  -2.7828  0.0054  -0.3180  -0.0552  ** 
## OutcomeMoral_SCA    0.0330  0.0712   0.4631  0.6433  -0.1066   0.1725     
## 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Summary of the results

#individual studies
ES.Prim$Raw.r<-FisherZInv(ES.Prim$yi)

df.ES.Prim<-data.frame(cbind(
  ES.Prim,
  t(apply(ES.Prim[,c('Raw.r','N')], 1, 
          function(x) CorCIFUNCTION(x[1], x[2])))[,c('lwr.ci','upr.ci')]))

colnames(df.ES.Prim)[c(10:11)] <- c("CIlow", "CIup")

#Meta-analyses
df.Meta.Res1.Prim<-as.data.frame(ExtractMetaFUNCTION(Meta.Prim))

df.Meta.Prim.z<- df.Meta.Res1.Prim %>% 
  dplyr::rename(
    "CIlow" = "Adj.CIlow",
    "CIup" = "Adj.CIup") %>%
  dplyr::mutate(
    Study="Pooled Effect Size",
    Outcome="All outcomes",
    N=sum(N.prim)) %>%
  rowwise() %>%
  dplyr::mutate(TOST=max(TOST1, TOST2)) %>%
  dplyr::select(-c(TOST1,TOST2))

df.Meta.Prim<-df.Meta.Prim.z[,c(8:10,1:2, 4:7, 11)]


df.Results.Prim<-data.frame(bind_rows(df.Meta.Prim, df.ES.Prim[,-c(3,5,6)]))

df.Results.Prim$Outcome<-dplyr::recode(df.Results.Prim$Outcome, 
                                  Moral_DIL="Dilemmas", 
                                  Moral_SCA="Scale", 
                                  Moral_CAR="Autonomous.Cars")

#Summary of the meta analysis
kable(df.Results.Prim[1,])
Study Outcome N Raw.r p.val CIlow CIup Cochran.Q Cochran.Q.p.val TOST yi ES.ID
Pooled Effect Size All outcomes 488 -0.1350288 0.0246526 -0.2490246 -0.0173427 23.54538 0.005081 0.2310692 NA NA
#Summary of the primary studies
kable(df.Results.Prim[2:nrow(df.Results.Prim),c(
  'Study', 'Outcome', 'N', 'Raw.r', 'CIlow', 'CIup')])
Study Outcome N Raw.r CIlow CIup
2 1 Dilemmas 121 -0.1839007 -0.3508794 -0.0055876
3 2 Scale 101 0.0412951 -0.1553983 0.2348390
4 3 Scale 18 -0.1891369 -0.6027801 0.3046330
5 4 Dilemmas 100 -0.2538084 -0.4288473 -0.0604014
6 4 Scale 100 0.0187643 -0.1783110 0.2143922
7 4 Autonomous.Cars 100 0.0592862 -0.1387476 0.2527609
8 5 Dilemmas 48 -0.2929617 -0.5327500 -0.0096284
9 5 Scale 48 -0.2088947 -0.4654056 0.0799876
10 6 Dilemmas 100 0.0145262 -0.1824124 0.2103441
11 6 Scale 100 0.2394571 0.0451631 0.4162953

Heterogeneity

Heterogeneity.Prim<-as.data.frame(ExtractHeterogeneityFUNCTION(Meta.Prim, V.Prim))
Heterogeneity.Prim2<-data.frame(apply(Heterogeneity.Prim[,3:7], 2, function(x) as.numeric(as.character(x))))

kable(cbind(Heterogeneity.Prim[,1:2], Heterogeneity.Prim2)) 
stud.out N Tau.sq Tau Cochran.Q Cochran.Q.p.val I.sq
Heure_TOT.Moral_CAR 1 0.0318445 0.1784503 23.54538 0.005081 75.04747
Heure_TOT.Moral_DIL 4 0.0072100 0.0849119 23.54538 0.005081 40.51032
Heure_TOT.Moral_SCA 5 0.0411452 0.2028428 23.54538 0.005081 79.53349
#Overall heterogeneity is low. The highest heterogeneity is found for the association between moral dilemmas and chronic sleep quality. Despite this, effect sizes remained systematically low.

kable(df.Results.Prim[df.Results.Prim$Outcome=="Dilemmas",1:7])
Study Outcome N Raw.r p.val CIlow CIup
2 1 Dilemmas 121 -0.1839007 NA -0.3508794 -0.0055876
5 4 Dilemmas 100 -0.2538084 NA -0.4288473 -0.0604014
8 5 Dilemmas 48 -0.2929617 NA -0.5327500 -0.0096284
10 6 Dilemmas 100 0.0145262 NA -0.1824124 0.2103441

Plot of the primary meta-analysis

df.graph.prim<-df.Results.Prim
df.graph.prim$pooled <- factor(ifelse(df.graph.prim$Outcome == "All outcomes", 1, 0))
df.graph.prim$Study<- factor(df.graph.prim$Study)
df.graph.prim$Study <- factor(df.graph.prim$Study,
  levels=rev(levels(df.graph.prim$Study)))

df.graph.prim2 <- df.graph.prim[order(df.graph.prim$Outcome),]

p<-ggplot(data=df.graph.prim2) +
  geom_hline(yintercept =0, linetype=2) +
  geom_pointrange(position=position_dodge2(width=8), 
                  aes(x = Study, y = Raw.r, 
                       ymin = CIlow, ymax = CIup), size = 0)+
  geom_point(aes(x = Study, y = Raw.r, color=pooled, size=pooled, fill = pooled, shape = pooled),  
              position=position_dodge2(width=8))+
  xlab('')+ ylab("Correlation coefficient\n (95% Confidence Interval)")+
  scale_color_manual(values=c("#999999", "red")) +
  scale_fill_manual(values=c("#999999", "red")) +
  scale_size_manual(values=c(2, 5)) +
  scale_shape_manual(values=c(21, 22)) +
  coord_flip()+
  theme_bw() +
  theme(legend.position="right",
          axis.text.y=element_blank(),
          axis.ticks.y=element_blank(),
          axis.text.x=element_text(size=10, face="bold"),
          axis.title.y=element_text(size=12,face="bold"),
          axis.title.x=element_text(size=12,face="bold"),
          strip.text.x = element_text(hjust=0.5,vjust =0.5,angle=0,
                                      size=11),
          legend.background = element_rect(size=0.5, linetype="solid", 
                                  colour ="black"),
        legend.box = "vertical") 
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## i Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
p+guides(fill = FALSE, color= FALSE, shape = FALSE, size=FALSE,linetype=FALSE)# shape=FALSE,
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
## of ggplot2 3.3.4.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

S2. Study 2: experimental study

Data creation

Scoring variables

dat_2$chronotype_dim = as.numeric(as.character(
  apply(dat_2 %>% select(ends_with("_scored")), 1, sum)))

dat_2$chronotype = ifelse(dat_2$chronotype_dim < 12, "Evening-type", 
                        ifelse(dat_2$chronotype_dim <= 17, "Neither", "Morning-type"))
dat_2$chronotype <- factor(dat_2$chronotype, levels = c("Morning-type", "Neither", "Evening-type"))

dat_2$group <- factor(dat_2$group, levels = c("8:30am-9:30am", "6:30pm-7:30pm"))

dat_2$utilitarianism = as.numeric(as.character(
  apply(dat_2 %>% select(ends_with("_1")), 1, sum)))

dat_2$inside_hours = with(dat_2,
  ifelse(group == "8:30am-9:30am" & hour_completion > 8.5 & hour_completion < 9.5, 1,
         ifelse(group == "6:30pm-7:30pm" & hour_completion > 18.5 & hour_completion < 19.5, 1, 0)))

Filtering participants

row_hour_pb = with(dat_2, which(inside_hours == 0 & AttentionCheck == 8))  
row_att_pb = with(dat_2, which(inside_hours == 1 & AttentionCheck != 8))  
row_pb = with(dat_2, which(inside_hours == 0 & AttentionCheck != 8))  

paste0("there are ", length(row_hour_pb), " participants that completed the study outside hour range")
## [1] "there are 22 participants that completed the study outside hour range"
paste0("there are ", length(row_att_pb), " participants that failed attentional check")
## [1] "there are 2 participants that failed attentional check"
paste0("there are ", length(row_pb), " participants that completed the study outside hour range & failed attentional check")
## [1] "there are 2 participants that completed the study outside hour range & failed attentional check"
dat_agg = dat_2 %>% filter(inside_hours == 1 & AttentionCheck == 8)

paste0("there are ", nrow(dat_agg), " participants that are included in the final analyses (", round(nrow(dat_agg)/nrow(dat_2)*100, 2), "% from the total recruited)")
## [1] "there are 175 participants that are included in the final analyses (87.06% from the total recruited)"
dat_lmm = dat_agg %>%
  pivot_longer(cols = ends_with("_1"),
               names_to = "dilemmas",
               values_to = "utilitarianism_long")
if (length(unique(dat_agg$ID)) != length(unique(dat_lmm$ID))) stop("issue detected")
table1::table1(~ Sex + Profession | group,
               data=dat_agg)
8:30am-9:30am
(N=86)
6:30pm-7:30pm
(N=89)
Overall
(N=175)
Sex
Men 43 (50.0%) 48 (53.9%) 91 (52.0%)
Other 1 (1.2%) 0 (0%) 1 (0.6%)
Women 42 (48.8%) 41 (46.1%) 83 (47.4%)
Profession
Employed 48 (55.8%) 51 (57.3%) 99 (56.6%)
Not working 3 (3.5%) 5 (5.6%) 8 (4.6%)
Student 31 (36.0%) 29 (32.6%) 60 (34.3%)
Unemployed 4 (4.7%) 3 (3.4%) 7 (4.0%)
Retired 0 (0%) 1 (1.1%) 1 (0.6%)

S2a. Distribution of the variables

Chronotype categorical

ggplot(data=dat_agg, aes(x = chronotype, fill = chronotype)) + 
  geom_bar(alpha=0.5) + 
  facet_wrap(~group) + 
  theme_bw()

Chronotype dimensional

ggplot(data=dat_agg, aes(x = chronotype_dim)) + 
  geom_histogram(aes(y=..density.., fill=group), colour="black", alpha=0.2)+
  geom_density(alpha= 0.6, aes(fill=group)) +
  geom_vline(xintercept = c(10.85, 17.08), color="red",
             linetype="dashed") +
  theme_bw()
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## i Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

effsize::cohen.d(dat_agg$chronotype_dim ~ dat_agg$group)
## 
## Cohen's d
## 
## d estimate: 0.1032842 (negligible)
## 95 percent confidence interval:
##      lower      upper 
## -0.1953647  0.4019331
summary(lm(dat_agg$chronotype_dim ~ 0 + dat_agg$group))
## 
## Call:
## lm(formula = dat_agg$chronotype_dim ~ 0 + dat_agg$group)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.5814 -2.5814 -0.2022  2.4186  9.7978 
## 
## Coefficients:
##                            Estimate Std. Error t value Pr(>|t|)    
## dat_agg$group8:30am-9:30am  13.5814     0.3958   34.31   <2e-16 ***
## dat_agg$group6:30pm-7:30pm  13.2022     0.3891   33.93   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.671 on 173 degrees of freedom
## Multiple R-squared:  0.9308, Adjusted R-squared:   0.93 
## F-statistic:  1164 on 2 and 173 DF,  p-value: < 2.2e-16

Utilitarianism

ggplot(data=dat_agg, aes(x = utilitarianism)) + 
  geom_histogram(aes(y=..density..), colour="black", fill="red", alpha=0.2)+
  geom_density(alpha= 0.6, fill="lightblue") +
  theme_bw()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

S2b. Main analysis

Main model

lmm_model = lmerTest::lmer(utilitarianism_long ~ group + 
                             (1|ID) + (1|dilemmas),
                           data = dat_lmm)
summary(lmm_model)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: utilitarianism_long ~ group + (1 | ID) + (1 | dilemmas)
##    Data: dat_lmm
## 
## REML criterion at convergence: 9370.3
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.95442 -0.68618 -0.06129  0.67261  2.82489 
## 
## Random effects:
##  Groups   Name        Variance Std.Dev.
##  ID       (Intercept) 2.969    1.723   
##  dilemmas (Intercept) 3.471    1.863   
##  Residual             6.296    2.509   
## Number of obs: 1925, groups:  ID, 175; dilemmas, 11
## 
## Fixed effects:
##                     Estimate Std. Error        df t value Pr(>|t|)    
## (Intercept)          4.06053    0.59729  12.50744   6.798 1.54e-05 ***
## group6:30pm-7:30pm  -0.06662    0.28457 172.99981  -0.234    0.815    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##             (Intr)
## gr6:30-7:30 -0.242
cohens_d(utilitarianism ~ group, data = dat_agg)
## Cohen's d |        95% CI
## -------------------------
## 0.04      | [-0.26, 0.33]
## 
## - Estimated using pooled SD.
g1 = subset(dat_agg, group == unique(dat_agg$group)[1])$utilitarianism
g2 = subset(dat_agg, group == unique(dat_agg$group)[2])$utilitarianism

TOSTER::tsum_TOST(m1 = mean(g1), sd1 = sd(g1), n1 = length(g1), 
                  m2 = mean(g2), sd2 = sd(g2), n2 = length(g2), 
                  eqb = 0.30, eqbound_type = "SMD")
## Warning: setting bound type to SMD produces biased results!
## 
## Welch Modified Two-Sample t-Test
## 
## The equivalence test was significant, t(171.74) = -1.751, p = 4.08e-02
## The null hypothesis test was non-significant, t(171.74) = 0.235, p = 8.15e-01
## NHST: don't reject null significance hypothesis that the effect is equal to zero 
## TOST: reject null equivalence hypothesis
## 
## TOST Results 
##                  t    df p.value
## t-test      0.2346 171.7   0.815
## TOST Lower  2.2206 171.7   0.014
## TOST Upper -1.7515 171.7   0.041
## 
## Effect Sizes 
##                Estimate    SE              C.I. Conf. Level
## Raw             0.73278 3.124 [-4.4332, 5.8988]         0.9
## Hedges's g(av)  0.03528 0.152 [-0.2122, 0.2826]         0.9
## Note: SMD confidence intervals are an approximation. See vignette("SMD_calcs").

Plot

ggplot(dat_agg, aes(x = group, y = utilitarianism)) + 
  geom_jitter(binaxis='y',
            stackdir = 'center', size = 1.5, alpha = 0.3,
            position = position_jitter(width = 0.05, height = 0.1)) +
  geom_boxplot(width = 0.5, alpha = 0) +
  theme_bw() +
  labs(y = "Degree of utilitarianism",
       x = "") +
  theme(text = element_text(size = 14),
        legend.position = "none",
        axis.title.y = element_text(size=14)) 

Interaction

Model

lmm_mod = lmerTest::lmer(utilitarianism_long ~ group * chronotype + 
                             (1|ID) + (1|dilemmas),
                         contrasts=list(group="contr.sum", chronotype="contr.sum"),
                           data = dat_lmm)
car::Anova(lmm_mod, type = "III")
## Analysis of Deviance Table (Type III Wald chisquare tests)
## 
## Response: utilitarianism_long
##                    Chisq Df Pr(>Chisq)    
## (Intercept)      44.2632  1  2.871e-11 ***
## group             3.3213  1   0.068388 .  
## chronotype        2.5893  2   0.273990    
## group:chronotype  9.7196  2   0.007752 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
marg_means = emmeans::emmeans(lmm_mod, ~ group|chronotype)
cont = emmeans::contrast(marg_means, interaction="consec")
cont
## chronotype = Morning-type:
##  group_consec                      estimate    SE  df t.ratio p.value
##  (6:30pm-7:30pm) - (8:30am-9:30am)   -1.694 0.852 169  -1.987  0.0485
## 
## chronotype = Neither:
##  group_consec                      estimate    SE  df t.ratio p.value
##  (6:30pm-7:30pm) - (8:30am-9:30am)    0.624 0.357 169   1.749  0.0821
## 
## chronotype = Evening-type:
##  group_consec                      estimate    SE  df t.ratio p.value
##  (6:30pm-7:30pm) - (8:30am-9:30am)   -0.865 0.523 169  -1.655  0.0998
## 
## Degrees-of-freedom method: kenward-roger
paste0("Bastien les p-values des post hoc ne sont pas ajustées par Bonf! (cf protocole) à x3 MAIS le 95%CI des SMD sont bien ajustées")
## [1] "Bastien les p-values des post hoc ne sont pas ajustées par Bonf! (cf protocole) à x3 MAIS le 95%CI des SMD sont bien ajustées"
cohens_d(utilitarianism ~ group, data = subset(dat_agg, chronotype == "Morning-type"), ci = 1 - (5)/100)
## Cohen's d |       95% CI
## ------------------------
## 0.99      | [0.01, 1.95]
## 
## - Estimated using pooled SD.
cohens_d(utilitarianism ~ group, data = subset(dat_agg, chronotype == "Neither"), ci = 1 - (5)/100)
## Cohen's d |        95% CI
## -------------------------
## -0.32     | [-0.71, 0.06]
## 
## - Estimated using pooled SD.
cohens_d(utilitarianism ~ group, data = subset(dat_agg, chronotype == "Evening-type"), ci = 1 - (5)/100)
## Cohen's d |        95% CI
## -------------------------
## 0.51      | [-0.06, 1.08]
## 
## - Estimated using pooled SD.

Plot

ggplot(dat_agg, aes(x = group, y = utilitarianism)) + 
  geom_jitter(binaxis='y',
            stackdir = 'center', size = 1.5, alpha = 0.3,
            position = position_jitter(width = 0.05, height = 0.1)) +
  geom_boxplot(width = 0.5, alpha = 0) +
  facet_wrap(~chronotype) +
  theme_bw() +
  labs(y = "Degree of utilitarianism",
       x = "") +
  theme(text = element_text(size = 14),
        legend.position = "none",
        axis.title.y = element_text(size=14)) 

S2c. Non-linear interaction

lmm_nn_linear = lmerTest::lmer(utilitarianism_long ~ group * poly(chronotype_dim, 2) + 
                             (1|ID) + (1|dilemmas),
                           data = dat_lmm)
summary(lmm_nn_linear)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: utilitarianism_long ~ group * poly(chronotype_dim, 2) + (1 |  
##     ID) + (1 | dilemmas)
##    Data: dat_lmm
## 
## REML criterion at convergence: 9336.7
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.96346 -0.68694 -0.05326  0.66802  2.82731 
## 
## Random effects:
##  Groups   Name        Variance Std.Dev.
##  ID       (Intercept) 2.872    1.695   
##  dilemmas (Intercept) 3.471    1.863   
##  Residual             6.296    2.509   
## Number of obs: 1925, groups:  ID, 175; dilemmas, 11
## 
## Fixed effects:
##                                              Estimate Std. Error        df
## (Intercept)                                   4.10776    0.59736  12.51237
## group6:30pm-7:30pm                           -0.06516    0.28388 169.00010
## poly(chronotype_dim, 2)1                    -10.84442    9.67299 169.00010
## poly(chronotype_dim, 2)2                     10.98659   10.43660 169.00010
## group6:30pm-7:30pm:poly(chronotype_dim, 2)1   4.27408   12.55189 169.00010
## group6:30pm-7:30pm:poly(chronotype_dim, 2)2 -29.71435   12.98805 169.00010
##                                             t value Pr(>|t|)    
## (Intercept)                                   6.877 1.37e-05 ***
## group6:30pm-7:30pm                           -0.230   0.8187    
## poly(chronotype_dim, 2)1                     -1.121   0.2638    
## poly(chronotype_dim, 2)2                      1.053   0.2940    
## group6:30pm-7:30pm:poly(chronotype_dim, 2)1   0.341   0.7339    
## group6:30pm-7:30pm:poly(chronotype_dim, 2)2  -2.288   0.0234 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##                   (Intr) g6:30- p(_,2)1 p(_,2)2 g6:30-7:30:(_,2)1
## gr6:30-7:30       -0.243                                         
## ply(ch_,2)1       -0.020  0.043                                  
## ply(ch_,2)2        0.055 -0.115 -0.017                           
## g6:30-7:30:(_,2)1  0.016 -0.012 -0.771   0.013                   
## g6:30-7:30:(_,2)2 -0.044  0.044  0.014  -0.804  -0.011
summary(lmerTest::lmer(
    utilitarianism_long ~ group + chronotype_dim + I(chronotype_dim^2) + group:chronotype_dim + group:I(chronotype_dim^2) +
                                   (1|ID) + (1|dilemmas),
                               data = dat_lmm))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: utilitarianism_long ~ group + chronotype_dim + I(chronotype_dim^2) +  
##     group:chronotype_dim + group:I(chronotype_dim^2) + (1 | ID) +  
##     (1 | dilemmas)
##    Data: dat_lmm
## 
## REML criterion at convergence: 9383.6
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.96346 -0.68694 -0.05326  0.66802  2.82731 
## 
## Random effects:
##  Groups   Name        Variance Std.Dev.
##  ID       (Intercept) 2.872    1.695   
##  dilemmas (Intercept) 3.471    1.863   
##  Residual             6.296    2.509   
## Number of obs: 1925, groups:  ID, 175; dilemmas, 11
## 
## Fixed effects:
##                                         Estimate Std. Error        df t value
## (Intercept)                              7.46623    2.58363 178.60144   2.890
## group6:30pm-7:30pm                      -7.05648    3.11990 169.00011  -2.262
## chronotype_dim                          -0.45570    0.37456 169.00010  -1.217
## I(chronotype_dim^2)                      0.01424    0.01353 169.00010   1.053
## group6:30pm-7:30pm:chronotype_dim        1.07623    0.46624 169.00011   2.308
## group6:30pm-7:30pm:I(chronotype_dim^2)  -0.03851    0.01683 169.00011  -2.288
##                                        Pr(>|t|)   
## (Intercept)                             0.00433 **
## group6:30pm-7:30pm                      0.02499 * 
## chronotype_dim                          0.22544   
## I(chronotype_dim^2)                     0.29398   
## group6:30pm-7:30pm:chronotype_dim       0.02219 * 
## group6:30pm-7:30pm:I(chronotype_dim^2)  0.02339 * 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##              (Intr) gr6:30-7:30 chrnt_ I(_^2) g6:30-7:30:_
## gr6:30-7:30  -0.789                                       
## chrontyp_dm  -0.959  0.794                                
## I(chrnt_^2)   0.920 -0.762      -0.987                    
## g6:30-7:30:_  0.770 -0.980      -0.803  0.793             
## g6:30-7:30:I -0.739  0.937       0.793 -0.804 -0.986

Plot

nn_linear = lm(utilitarianism ~ group * poly(chronotype_dim, 2), 
               data = dat_agg)
interact_plot(nn_linear,
              pred = chronotype_dim, 
              modx = group, data = dat_agg)

ggplot(dat=dat_agg, aes(y = utilitarianism, x = chronotype_dim,
                        color = group)) + 
  geom_point(alpha=0.4, shape=21, 
             aes(fill=group), colour="black", size=3) +
  geom_smooth(se = TRUE, method = lm,
              formula = y ~ poly(x, 2), fullrange=TRUE,
              fill="lightgrey") +
  theme_bw() + 
  scale_y_continuous(limits=c(10,70))

Main model

summary(lm(utilitarianism ~ group, data = dat_agg))
## 
## Call:
## lm(formula = utilitarianism ~ group, data = dat_agg)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -44.256 -13.864  -1.526  13.677  66.067 
## 
## Coefficients:
##                    Estimate Std. Error t value Pr(>|t|)    
## (Intercept)         44.6658     2.2323  20.009   <2e-16 ***
## group6:30pm-7:30pm  -0.7328     3.1302  -0.234    0.815    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 20.7 on 173 degrees of freedom
## Multiple R-squared:  0.0003167,  Adjusted R-squared:  -0.005462 
## F-statistic: 0.0548 on 1 and 173 DF,  p-value: 0.8152

Interaction cat

lm_mod = lm(utilitarianism ~ group * chronotype, 
           data = dat_agg)
marg_means = emmeans::emmeans(lm_mod, ~ group|chronotype)
car::Anova(lm_mod)
## Anova Table (Type II tests)
## 
## Response: utilitarianism
##                  Sum Sq  Df F value   Pr(>F)   
## group                11   1  0.0278 0.867803   
## chronotype         1374   2  1.6868 0.188205   
## group:chronotype   3957   2  4.8598 0.008869 **
## Residuals         68808 169                    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans::contrast(marg_means, interaction="consec")
## chronotype = Morning-type:
##  group_consec                      estimate   SE  df t.ratio p.value
##  (6:30pm-7:30pm) - (8:30am-9:30am)   -18.63 9.38 169  -1.987  0.0485
## 
## chronotype = Neither:
##  group_consec                      estimate   SE  df t.ratio p.value
##  (6:30pm-7:30pm) - (8:30am-9:30am)     6.87 3.93 169   1.749  0.0821
## 
## chronotype = Evening-type:
##  group_consec                      estimate   SE  df t.ratio p.value
##  (6:30pm-7:30pm) - (8:30am-9:30am)    -9.51 5.75 169  -1.655  0.0998

Non-linear dimensional interaction

lm_non_mod = lm(utilitarianism ~ group * poly(chronotype_dim, 2), 
           data = dat_agg)
summary(lm_non_mod)
## 
## Call:
## lm(formula = utilitarianism ~ group * poly(chronotype_dim, 2), 
##     data = dat_agg)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -44.12 -13.11  -2.08  13.70  66.62 
## 
## Coefficients:
##                                             Estimate Std. Error t value
## (Intercept)                                  45.1854     2.2343  20.223
## group6:30pm-7:30pm                           -0.7168     3.1227  -0.230
## poly(chronotype_dim, 2)1                    -35.9669    32.0817  -1.121
## poly(chronotype_dim, 2)2                     36.4384    34.6143   1.053
## group6:30pm-7:30pm:poly(chronotype_dim, 2)1  14.1755    41.6299   0.341
## group6:30pm-7:30pm:poly(chronotype_dim, 2)2 -98.5514    43.0765  -2.288
##                                             Pr(>|t|)    
## (Intercept)                                   <2e-16 ***
## group6:30pm-7:30pm                            0.8187    
## poly(chronotype_dim, 2)1                      0.2638    
## poly(chronotype_dim, 2)2                      0.2940    
## group6:30pm-7:30pm:poly(chronotype_dim, 2)1   0.7339    
## group6:30pm-7:30pm:poly(chronotype_dim, 2)2   0.0234 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 20.42 on 169 degrees of freedom
## Multiple R-squared:  0.05018,    Adjusted R-squared:  0.02208 
## F-statistic: 1.786 on 5 and 169 DF,  p-value: 0.1183
lapply(list(lm_mod, lm_non_mod), AIC)
## [[1]]
## [1] 1556.13
## 
## [[2]]
## [1] 1560.235
lapply(list(lm_mod, lm_non_mod), BIC)
## [[1]]
## [1] 1578.284
## 
## [[2]]
## [1] 1582.388