Chapter 5 Method 3: Two-Stage Individual Participant Meta-Analysis

5.1 Analytic Plan

In the present study, we estimate associations between the Big Five personality traits and crystallized cognitive abilities using a two-stage individual participant data meta-analysis (also known as a coordinated data analysis when multiple analysts are used). The procedure is as follows:

5.1.1 1. Sample-Specific Statistical Modeling

Models were run separately for each sample, personality characteristic, outcome, covariate, and moderator combination. To do so, we wrote a series of functions in the R programming language (see online materials) to (1) set up and run the model and extract model coefficients, (2) extract simple-effects predictions for moderator models (i.e. predicted values across levels of the moderator values), and (3) extract effect size metrics for the later meta-analysis. The basic form of the model is as follows:

\(Y_{ij}=b_0+b_1\ast predictor_{ij}+\epsilon_{ij}\) \(\epsilon_{ij}\sim\mathcal{N}(0, \sigma^2)\),

where \(b_1\) represents the effect of personality predicting the outcome separately in each sample.
The modeling function used the base R lm() function to run the models and the tidy() function from the broom package to extract model coefficients and confidence intervals (CI). Inferences will be made based on the 95% confidence intervals. Effect sizes and their standard errors were extracted from the model summary(). Simple-effects predictions were calculated by providing the full range of personality levels (0-10) and average levels of the covariates from the data used to estimate the model as the “newdata” argument in the base R predict() function.

5.1.2 2. Results Pooling Using Meta-Analysis

Once all the models were run, we next combined the effects across samples for each personality characteristic (5), outcome (1), covariate (6; none, each alone, all covariates), and moderator (3) combination and estimated a meta-analysis model of the target effect. We chose to conduct a random effects meta-analysis, which assumes that all effect sizes are randomly drawn from a population of effect sizes. To do so, we constructed three helper functions to (1) set up and run the meta-analytic models, (2) extract the meta-analytic estimates, and (3) extract heterogeneity estimates. The functions and more detail on them can be found in the online materials. The meta-analytic model is as follows:

\(T_i=\mu+\zeta_i+\epsilon_i\)

\(\epsilon_i\sim \mathcal{N}\left(0,\sigma^2\right)\ \) \(\zeta_i\sim \mathcal{N}\left(0,\tau^2\right)\ \) \(Cov\left(\zeta_i,\epsilon_i\right)=0\),

where \(T_i\) is the sample-specific effect of sample \(i\), \(\mu\) is the overall meta-analytic estimate, \(\zeta_i\) is true sample variability of sample \(i\) from the overall estimate, and \(\epsilon_i\) is sampling error.

Random effects meta-analyses were estimated using the metafor package in R. Meta-analytic estimates and confidence intervals were extracted using the coef() function. Inferences were based on the 95% confidence intervals (CI). Heterogeneity estimates were directly extracted from the model. Simple effects predictions of participant-level moderators were calculated using the weighted average sample-level predictions across levels of the moderators.

5.2 Step 1: Combine Data

We again need to combine data. However, rather than combining data across studies, for the two-stage approach, we’ll be combining data within studies in order to run separate analyses for each before combining via meta-analytic tools.

loadRData <- function(fileName, type){
#loads an RData file, and returns it
    path <- sprintf("%s/data/clean/%s_cleaned.RData", local_path, fileName)
    print(path)
    load(path)
    get(ls()[grepl(type, ls())])
}

ipd3_meta_data <- tibble(
  study = studies[!studies %in% c("CNLSY", "SLS")]
  , data = map(str_to_lower(study), ~loadRData(., "combined"))
  ) %>% mutate(
    data = map(data, ~(.) %>% 
                 ungroup() %>% 
                 mutate(SID = as.character(SID)))
    , study = mapvalues(study, studies, studies_long)
  ) %>%
  unnest(data) %>%
  mutate(age = ifelse(is.na(age), p_year - yearBrth, age))
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/base-i_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/eas_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/gsoep_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/hilda_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/hrs_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/lasa_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/map_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/mars_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/octo-twin_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/ros_cleaned.RData"
## [1] "~/Documents/projects/data synthesis/crystallized/data/clean/satsa_cleaned.RData"
ipd3_meta_data
## # A tibble: 119,597 × 13
##    study Trait p_value p_year SID   Outcome      o_value o_year education gender SRhealth yearBrth   age
##    <chr> <chr>   <dbl>  <dbl> <chr> <chr>          <dbl>  <dbl>     <dbl>  <dbl>    <dbl>    <dbl> <dbl>
##  1 BASE  E        3.83   1990 10004 crystallized   2.17    2000        17      0        4     1918    72
##  2 BASE  E        3.83   1990 10010 crystallized   3.08    1995        11      0        2     1911    79
##  3 BASE  E        2.5    1990 10033 crystallized   0.909   1997         8      1        5     1910    80
##  4 BASE  E        3      1990 10034 crystallized   6.54    1995        10      0        5     1914    76
##  5 BASE  E        2.5    1990 10111 crystallized   6.54    1995        13      1        4     1901    89
##  6 BASE  E        3.17   1990 10115 crystallized  10       1995        11      0        2     1918    72
##  7 BASE  E        3      1990 10116 crystallized   3.85    1995        10      0        4     1917    73
##  8 BASE  E        3      1990 10145 crystallized   6.36    1997        10      1        5     1897    93
##  9 BASE  E        3.33   1990 10175 crystallized   2.31    1995         8      1        3     1911    79
## 10 BASE  E        3.33   1990 10188 crystallized   5.77    2004        10      1        2     1903    87
## # ℹ 119,587 more rows

5.2.1 Study-Level Moderators

save_fun <- function(d, trait, outcome){
  save(d, file = sprintf("%s/data/two_stage/meta_data/%s_%s.RData", local_path, trait, outcome))
}

url <- "https://github.com/emoriebeck/data-synthesis-tutorial/raw/main/codebooks/crystallized_tables.xlsx"
destfile <- "tables.xlsx"
curl::curl_download(url, destfile)

ipd_metaMod_data <- readxl::read_xlsx(destfile, sheet = "Table 4") %>%
  select(-Category, -Construct, -category) %>%
  pivot_longer(cols = c("BASE-I":"SATSA")
               , names_to = "study"
               , values_to = "value") %>%
  pivot_wider(names_from = "name"
              , values_from = "value") %>%
  mutate(continent = relevel(factor(continent), ref = "North America")
         , country = relevel(factor(country), ref = "United States")
         , scale = relevel(factor(scale), ref = "NEO-FFI")) %>%
  right_join(
    ipd3_meta_data %>% 
      select(-p_value, -o_value, -(education:yearBrth))
    ) %>%
  group_by(study, Trait, Outcome) %>%
  mutate(baseAge = mean(age, na.rm = T) - 60, # center at age 60
         predInt = mean(o_year - p_year) - 5, # center at 5 years
         baseYear = ifelse(study %in% c("MARS", "MAP", "ROS"), mean(p_year), mean(p_year) - 2000)) %>% # center at 2000
  ungroup() %>%
  select(Trait, Outcome, study, continent, country, scale, baseAge, baseYear, predInt) %>%
  distinct() %>%
  group_by(Trait, Outcome) %>%
  nest() %>%
  ungroup()
ipd_metaMod_data
## # A tibble: 5 × 3
##   Trait Outcome      data             
##   <chr> <chr>        <list>           
## 1 A     crystallized <tibble [6 × 7]> 
## 2 C     crystallized <tibble [7 × 7]> 
## 3 E     crystallized <tibble [9 × 7]> 
## 4 N     crystallized <tibble [11 × 7]>
## 5 O     crystallized <tibble [7 × 7]>
ipd_metaMod_data %>% mutate(pmap(list(data, Trait, Outcome), save_fun))
## # A tibble: 5 × 4
##   Trait Outcome      data              `pmap(list(data, Trait, Outcome), save_fun)`
##   <chr> <chr>        <list>            <list>                                      
## 1 A     crystallized <tibble [6 × 7]>  <NULL>                                      
## 2 C     crystallized <tibble [7 × 7]>  <NULL>                                      
## 3 E     crystallized <tibble [9 × 7]>  <NULL>                                      
## 4 N     crystallized <tibble [11 × 7]> <NULL>                                      
## 5 O     crystallized <tibble [7 × 7]>  <NULL>
readxl::read_xlsx(destfile, sheet = "Table 4") %>%
    select(-Category, -Construct, -category) %>%
    pivot_longer(cols = c("BASE-I":"SATSA")
                 , names_to = "study"
                 , values_to = "value") %>%
    pivot_wider(names_from = "name"
                , values_from = "value") %>%
    right_join(
        ipd3_meta_data %>% 
            select(-p_value, -o_value, -(education:yearBrth))
    ) %>%
    group_by(study, Trait, Outcome) %>%
    mutate(baseAge = mean(age, na.rm = T), # center at age 60
           baseAgeSD = sd(age, na.rm = T), # center at age 60
           predInt = mean(o_year - p_year), # center at 5 years
           predIntSD = sd(o_year - p_year), # center at 5 years
           baseYear = ifelse(study %in% c("MARS", "MAP", "ROS"), median(p_year), median(p_year)),
           baseYearSD = ifelse(study %in% c("MARS", "MAP", "ROS"), sd(p_year), sd(p_year)),
           cogYear = ifelse(study %in% c("MARS", "MAP", "ROS"), median(o_year), median(o_year)),
           cogYearSD = ifelse(study %in% c("MARS", "MAP", "ROS"), sd(o_year), sd(o_year))) %>% # center at 2000
    ungroup() %>%
    select(Trait, Outcome, study, continent, country, scale, baseAge, baseAgeSD, baseYear, baseYearSD, cogYear, cogYearSD, predInt, predIntSD) %>%
    distinct() %>%
  group_by(study, Outcome) %>%
  summarize_at(vars(baseAge:predIntSD), mean) %>%
  kable(., "html", digits = 2) %>%
  kable_classic(full_width = F, html_font = "Times New Roman") 
study Outcome baseAge baseAgeSD baseYear baseYearSD cogYear cogYearSD predInt predIntSD
BASE crystallized 78.23 6.66 1990 0.00 1997.00 3.51 8.56 3.51
EAS crystallized 79.47 5.36 2011 3.18 2014.00 3.09 2.15 2.41
GSOEP crystallized 49.83 15.83 2005 0.00 2012.00 0.00 7.00 0.00
HILDA crystallized 44.51 16.92 2005 0.00 2012.00 0.00 7.00 0.00
HRS crystallized 71.71 6.97 2006 0.00 2010.00 0.00 4.00 0.00
LASA crystallized 61.46 15.71 1992 1.19 1995.00 9.09 8.39 9.45
MAP crystallized 79.45 7.32 0 0.00 6.33 4.53 6.75 4.53
MARS crystallized 73.60 6.37 0 0.00 6.00 3.86 6.46 3.86
OCTO-Twin crystallized 82.99 2.66 1991 0.00 1997.00 2.82 6.21 2.82
ROS crystallized 75.87 7.38 0 0.03 8.00 6.42 9.53 6.42
SATSA crystallized 54.77 9.84 1984 0.00 1999.00 0.00 15.00 0.00

5.2.2 Harmonize Data

ipd3_meta_data <- ipd3_meta_data %>%
  group_by(study, Trait, Outcome) %>%
  mutate_at(vars(p_value, o_value, SRhealth), 
            ~((. - min(., na.rm = T))/(max(., na.rm = T) - min(., na.rm = T))*10)) %>%
  mutate(gender = factor(gender, levels = c(0,1), labels = c("Male", "Female")),
         education = education - 12,
         age = age - mean(age, na.rm = T)) %>% 
  ungroup()
ipd3_meta_data
## # A tibble: 119,597 × 13
##    study Trait p_value p_year SID   Outcome      o_value o_year education gender SRhealth yearBrth    age
##    <chr> <chr>   <dbl>  <dbl> <chr> <chr>          <dbl>  <dbl>     <dbl> <fct>     <dbl>    <dbl>  <dbl>
##  1 BASE  E         7     1990 10004 crystallized   2.17    2000         5 Male        7.5     1918 -6.23 
##  2 BASE  E         7     1990 10010 crystallized   3.08    1995        -1 Male        2.5     1911  0.766
##  3 BASE  E         3     1990 10033 crystallized   0.909   1997        -4 Female     10       1910  1.77 
##  4 BASE  E         4.5   1990 10034 crystallized   6.54    1995        -2 Male       10       1914 -2.23 
##  5 BASE  E         3     1990 10111 crystallized   6.54    1995         1 Female      7.5     1901 10.8  
##  6 BASE  E         5     1990 10115 crystallized  10       1995        -1 Male        2.5     1918 -6.23 
##  7 BASE  E         4.5   1990 10116 crystallized   3.85    1995        -2 Male        7.5     1917 -5.23 
##  8 BASE  E         4.5   1990 10145 crystallized   6.36    1997        -2 Female     10       1897 14.8  
##  9 BASE  E         5.5   1990 10175 crystallized   2.31    1995        -4 Female      5       1911  0.766
## 10 BASE  E         5.5   1990 10188 crystallized   5.77    2004        -2 Female      2.5     1903  8.77 
## # ℹ 119,587 more rows

5.2.3 Save Data Files

save_fun <- function(d, trait, outcome, study){
  save(d, file = sprintf("%s/data/two_stage/%s_%s_%s.RData", local_path, trait, outcome, study))
}

ipd3_meta_data %>%
  group_by(study, Trait, Outcome) %>%
  nest() %>%
  ungroup() %>%
  mutate(data = pmap(list(data, Trait, Outcome, study), save_fun))
## # A tibble: 40 × 4
##    study Trait Outcome      data  
##    <chr> <chr> <chr>        <list>
##  1 BASE  E     crystallized <NULL>
##  2 BASE  N     crystallized <NULL>
##  3 BASE  O     crystallized <NULL>
##  4 EAS   A     crystallized <NULL>
##  5 EAS   C     crystallized <NULL>
##  6 EAS   E     crystallized <NULL>
##  7 EAS   N     crystallized <NULL>
##  8 EAS   O     crystallized <NULL>
##  9 GSOEP A     crystallized <NULL>
## 10 GSOEP C     crystallized <NULL>
## # ℹ 30 more rows

5.3 Step 2: Run Models for Each Study

5.3.1 Functions

5.3.1.1 Model Function

ipd3_study_mod_fun <- function(trait, outcome, type, mod, study, cov){
  ## load the data
  load(sprintf("%s/data/two_stage/%s_%s_%s.RData", local_path, trait, outcome, study))
  
  ## compiled Bayesian model to speed up processing and avoid crashing
  if(type == "Bayesian") load(sprintf("%s/results/3_ipd_meta/bayes_sample_mod.RData", local_path))
  
  ## model formula 
  if (cov == "all") cv <- c("age", "gender", "education")
  if (!cov %in% c("all", "none")) cv <- cov
  rhs <- "p_value"
  rhs <- if(cov != "none") c(rhs, cv) else rhs
  if(mod != "none"){rhs <- c(rhs, paste("p_value", mod, sep = "*"))}
  rhs <- paste(rhs, collapse = " + ")
  f <- paste("o_value ~ ", rhs, collapse = "")
  
  ## run the models & save
  m <- if(type == "Frequentist"){do.call("lm", list(formula = f, data = quote(d)))} else {update(m, formula = f, newdata = d, warmup = 1000, iter = 2000, cores = 1)}
  save(m, file = sprintf("%s/results/3_ipd_meta/%s/studyModels/%s_%s_%s_%s_%s.RData"
                         , local_path, type, outcome, trait, mod, cov, study))
  
  ## extract model terms and confidence intervals & save
  rx <- tidy(m, conf.int = T) %>%
    select(term, estimate, conf.low, conf.high)
  save(rx, file = sprintf("%s/results/3_ipd_meta/%s/studySummary/%s_%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov, study))
    
  ## calculate effect sizes for random effects meta analysis
  es <- ipd3_es_fun(m, type, mod)
  save(es, file = sprintf("%s/results/3_ipd_meta/%s/studyEffects/%s_%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov, study))
  
  ## calculate simple effects  
  if(mod != "none"){
    pred.rx <- ipd3_study_simpeff_fun(m, mod, type)
    save(pred.rx, file = sprintf("%s/results/3_ipd_meta/%s/studyPredicted/%s_%s_%s_%s_%s.RData"
                                 , local_path, type, outcome, trait, mod, cov, study))
  }
  
  ## clean up the local function environment
  rm(list = c("d", "f", "m", "fx", "es", "rhs"))
  gc()
}

5.3.1.2 Effect Size Function

ipd3_es_fun <- function(m, type, mod){
  ## extract model features needed for meta-analysis
  ts <- insight::clean_parameters(m)$Cleaned_Parameter
  ts <- if(mod == "none") "p_value" else ts[grepl("p_value.", ts)]
  ts <- str_replace(ts, "[.]", ":")
  ## standardize the model 
  # ms <- standardize(m)
  ## get standardized model coefficients and standard errors
  ## for bayesian models this is the sd of the posterior estimates
  es <- if(type == "Frequentist"){ 
    summary(m)$coef[c("(Intercept)", ts), c("Estimate", "Std. Error")] %>% as.data.frame() %>% setNames(c("Estimate", "SEI"))
    } else {
      fixef(m)[c("Intercept", ts), c("Estimate", "Est.Error")]  %>% as.data.frame() %>% setNames(c("Estimate", "SEI"))
    }
  ## format to standardized format
  es <- es %>%
    rownames_to_column("term") %>%
    # t() %>% 
    # as.data.frame() %>% 
    mutate(ni = if(type == "Frequentist") {nrow(m$model)} else {nrow(m$data)})
  return(es)
}

5.3.1.3 Study-Specific Simple Effects Function

ipd3_study_simpeff_fun <- function(m, moder, type){
  d <- if(type == "Bayesian") m$data else m$model
  d <- d %>% select(-o_value, -p_value)
  cols <- colnames(d)
  md_cl <- class(d[,moder])
  if(any(sapply(d, class) == "numeric")){
    msd <- d %>%
      select_if(is.numeric) %>%
      pivot_longer(everything()
                   , names_to = "item"
                   , values_to = "value") %>%
      group_by(item) %>%
      summarize_at(vars(value), lst(mean, sd)) %>%
      ungroup()
  }
  if(any(sapply(d, class) == "factor")){
    fct_lev <- d %>% 
      select_if(is.factor) %>%
      summarize_all(~list(unique(.)))
  }
  d <- d %>% select(-one_of(moder))
  md_levs <- if(md_cl == "numeric"){
    if(moder %in% c("age", "baseAge", "baseYear")) {
      c(-10, 0, 10)
      } else if (moder %in% c("predInt", "education")) {
      c(-5, 0, 5) 
      } else {
        with(msd, c(mean[item == moder] - sd[item == moder], mean[item == moder], mean[item == moder] + sd[item == moder]))
      }
  } else { 
    unique(fct_lev[,moder][[1]])
  }
  
  mod_frame <- crossing(
    p_value = seq(0,10,.5)
    , modvalue = md_levs
    ) %>% setNames(c("p_value", moder))
  
  if(ncol(d) > 0){
    if(any(sapply(d, class) == "numeric")){
      mod_frame <- tibble(mod_frame, d %>% select_if(is.numeric) %>% summarize_all(mean))
    } 
    if(any(sapply(d, class) == "factor")){
      mod_frame <- tibble(mod_frame, d %>% select_if(is.factor) %>% summarize_all(~levels(.)[1]))
    }
  }
  
  pred.fx <- if(type == "Bayesian"){
    bind_cols(
      mod_frame, 
      fitted(m, newdata = mod_frame) %>% data.frame
    ) %>%
      select(one_of(colnames(m$data)), pred = Estimate, lower = Q2.5, upper = Q97.5)
  } else {
    bind_cols(
      mod_frame, 
      predict(m, newdata = mod_frame, interval = "confidence") %>% data.frame 
    ) %>%
      select(one_of(colnames(m$model)), pred = fit, lower = lwr, upper = upr)
  }
  
  rm(list = c("m", "mod_frame", "d", "md_levs"))
  gc()
  return(pred.fx)
}

5.3.2 Run Models

# done <- tibble(file = list.files(sprintf("%s/results/3_ipd_meta/Bayesian/studyModels", local_path))) %>%
#   separate(file, c("Outcome", "Trait", "Moderator", "Covariate", "study"), sep = "_") %>%
#   mutate(study = str_remove_all(study, ".RData"),
#          done = "done")
plan(multisession(workers = 12L))
nested_ipd_reg <- tibble(files = list.files(sprintf("%s/data/two_stage", local_path))) %>%
  separate(files, c("Trait", "Outcome", "study"), sep = "_") %>%
  filter(!is.na(study)) %>%
  mutate(study = str_remove(study, ".RData")) %>%
  left_join(
    crossing(
      study = studies
      , Trait = traits$short_name
      , Outcome = outcomes$short_name
      , type = c("Frequentist", "Bayesian")
      , Moderator = c("age", "gender", "education")
      , Covariate = c("none", "all")
    ) %>% 
      full_join(
        crossing(
          study = studies
          , Trait = traits$short_name
          , Outcome = outcomes$short_name
          , type = c("Frequentist", "Bayesian")
          , Moderator = "none"
          , Covariate = c("none", "age", "gender", "education", "all")
          )
        )
    ) %>%
  filter(!is.na(Covariate)) %>%
  # full_join(done) %>% filter(is.na(done)) %>%
  # filter(type == "Frequentist") %>%
  filter(Trait == "N" & study == "HILDA") %>%
  mutate(run = 
           # pmap(list(Trait, Outcome, type, Moderator, study, Covariate)
           future_pmap(list(Trait, Outcome, type, Moderator, study, Covariate)
                    , possibly(ipd3_study_mod_fun, NA_real_)
                    , .progress = T
             , .options = furrr_options(
                                    globals = c("ipd3_es_fun"
                                                , "ipd3_study_simpeff_fun"
                                                , "read_path"
                                                , "local_path"
                                                , "res_path"
                                                , "codebook"
                                                , "covars"
                                                , "moders"
                                                , "outcomes"
                                                , "studies"
                                                , "stdyModers"
                                                , "traits"
                                                , "data_path")
                                  , packages = c("lme4"
                                                 , "broom"
                                                 , "psych"
                                                 , "knitr"
                                                 , "broom.mixed"
                                                 , "brms"
                                                 #, "tidybayes"
                                                 #, "bootpredictlme4"
                                                 , "rstan"
                                                 , "estimatr"
                                                 #, "merTools"
                                                 , "plyr"
                                                 , "tidyverse"))
             ))
closeAllConnections()

5.4 Step 3: Meta-Analyze Results

5.4.1 Functions

5.4.1.0.1 Meta-Analysis Function
ipd3_meta_fun <- function(es, type, trait, outcome, mod, cov){
  print(paste(type, trait, outcome, mod, cov))
  ## bayesian sample models for stability and speed
  if(type == "Bayesian") {
    mr <- if(mod %in% stdyModers$short_name) "metareg" else "meta"
    load(sprintf("%s/results/3_ipd_meta/bayes_sample_%s.RData", local_path, mr))
  }
  
  ## adding meta-regression values to effect sizes
  if(mod %in% stdyModers$short_name) {
    load(sprintf("%s/data/two_stage/meta_data/%s_%s.RData",  local_path, trait, outcome))
    es <- d %>% select(study, one_of(mod)) %>% 
      setNames(c("study", "metamod")) %>% 
      full_join(es) %>%
      mutate_if(is.character, factor)
    es0 <- es %>% filter(grepl("Intercept", term)) %>% select(-term)
  }
    es <- es %>% filter(!grepl("Intercept", term)) %>% select(-term)
  
  ## base bayesian model
    # brm(formula = bf(Estimate | se(SEI) ~ 1 + (1 | study))
  #     , save_pars = "all"
  #     , sample_prior = T
  #     , prior = prior(cauchy(0,1), class = sd)
  #     , iter = 4000)
  
  ## base bayesian meta-regression model
  # update(mt
  #     , formula. = bf(~ . + metamod)
  #     , newdata = es
  #     , sample_prior = T)
  # run the meta-analytic model
  f <- if (mod %in% c("none", moders$short_name)) "Estimate ~ 1" else "Estimate ~ 1 + metamod"
  mt <- if(type == "Frequentist"){
    rma(formula(f)
        , sei = SEI
        , ni = ni
        , slab = study
        , data = es)
  } else {
    if(!mod %in% stdyModers$short_name) {
      update(m
             , newdata = es
             , iter = 2000
             , warmup = 1000
             , cores = 1)
    } else {
      update(m
             , formula. = bf(~ . + metamod)
             , newdata = es
             , iter = 2000
             , warmup = 1000
             , cores = 1)
    }
  }
  
  if(mod %in% stdyModers$short_name) {
    mt0 <- if(type == "Frequentist") {
      rma(formula(f)
        , sei = SEI
        , ni = ni
        , slab = study
        , data = es0)
    } else {
      update(m
             , formula. = bf(~ . + metamod)
             , newdata = es0
             , iter = 2000
             , warmup = 1000
             , cores = 1)
    }
  }
  
  save(mt, file = sprintf("%s/results/3_ipd_meta/%s/metaModels/%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov))
  
  # pull out and format the fixed effects (i.e. overall effects)
  fx <- ipd3_meta_fx_fun(mt, type, mod)
  save(fx, file = sprintf("%s/results/3_ipd_meta/%s/metaSummary/%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov))
  
  # meta-analysis predictions
  if(mod %in% stdyModers$short_name) {
    pred.fx0 <- ipd3_meta_simpeff_fun(mt0, mod, type) %>% setNames(c(mod, "b0_pred", "b0_lower", "b0_higher"))
    pred.fx1 <- ipd3_meta_simpeff_fun(mt, mod, type) %>% setNames(c(mod, "b1_pred", "b1_lower", "b1_higher"))
    pred.fx <- crossing(
      p_value = seq(0, 10, .25)
      , pred.fx0 %>%
        full_join(pred.fx1)
    ) %>%
      mutate(pred = b0_pred + b1_pred*p_value) %>%
      select(p_value, all_of(mod), pred)
    save(pred.fx, file = sprintf("%s/results/3_ipd_meta/%s/metaPredicted/%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov))
  }
  
  # pull out and format the cross-study heterogeneity estimates
  het <- ipd3_meta_rx_fun(mt, type)
  save(het, file = sprintf("%s/results/3_ipd_meta/%s/metaHetero/%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov))
  rm(list = c("mt", "es", "type", "trait", "outcome", "mod", "fx", "rx"))
  return(NULL)
}
5.4.1.0.2 Meta-Analysis Fixed Effect Function
ipd3_meta_fx_fun <- function(mt, type, mod){
  trgt <- if(mod %in% c("none", stdyModers$short_name)) "p_value" else paste0("p_value:", mod)
  if (type == "Frequentist"){
    coef(summary(mt)) %>%
      rownames_to_column("term") %>%
      select(term, estimate, SE = se, conf.low = ci.lb, conf.high = ci.ub) %>%
      mutate(study = "Meta-Analytic",
             term = mapvalues(term, "intrcpt", trgt))
  } else {
    fixef(mt) %>% data.frame() %>%
      rownames_to_column("term") %>%
      select(term, estimate = Estimate, SE = Est.Error, conf.low = Q2.5, conf.high = Q97.5) %>%
      mutate(study = "Meta-Analytic",
             term = mapvalues(term, "Intercept", trgt))
  }
}
5.4.1.0.3 Meta-Analysis Heterogeneity Function
ipd3_meta_rx_fun <- function(mt, type){
  if (type == "Frequentist"){
    ## for frequentist, we'll grab estimates of:
    ## - tau^2: estimated between-study heterogeneity
    ## - I^2: total hetero (tau^2) / total hetero + total var (tau^2 + sigma^2)
    ## - H^2: total var (tau^2 + sigma^2) / total sampling var (sigma^2)
    ## - QE: Chi^2 dist Cochran's Q statistic (hetero > 0)
    ## - QEp: associated p-value for QE for k df
    mt[c("tau2", "se.tau2", "I2", "H2", "QE", "QEp")] %>%
      ldply() %>%
      pivot_wider(names_from = ".id", values_from = "V1")
  } else {
    ## for Bayesian, we'll grab estimates of: 
    ## note, for these, we must estimate some directly
    ## but will use Bayes Factor to estimate probability of tau^2 > 0
    ## this should converge with other estimates but is more appropriate for Bayes
    ## - tau^2: average estimated between-study hetero across Bayes samples
    ## - I^2: total hetero (tau^2) / total hetero + total var (tau^2 + sigma^2)
    ## - H^2: total var (tau^2 + sigma^2) / total sampling var (sigma^2)
    ## - BF: posterior prob / prior prob
    tibble(tau2 = summary(mt)$random$study[,"Estimate"]^2
           , se.tau2 = summary(mt)$random$study[,"Est.Error"]^2
           , I2 = tau2 / (tau2 + var(resid(mt)[,"Estimate"]))
           , H2 = (tau2 + var(resid(mt)[,"Estimate"])) / var(resid(mt)[,"Estimate"])
           , BF = 1/hypothesis(mt, "study__Intercept^2 = 0", class = "sd")$hypothesis$Evid.Ratio
    )
  }
}
5.4.1.0.4 Meta-Analysis Simple Effects Function
## seemingly only seems to make sense for meta regressions
## the effect sizes uses would be the difference in the association 
## associated with a 1 SD change in the moderator
## or with a dummy code?\
ipd3_meta_simpeff_fun <- function(m, moder, type){
  d <- if(type == "Bayesian") m$data %>% select(metamod) else m$X %>% data.frame() %>% select(-intrcpt)
  md_cl <- class(d$metamod)
  if(any(sapply(d, class) == "numeric")){
    msd <- d %>%
      select_if(is.numeric) %>%
      pivot_longer(everything()
                   , names_to = "item"
                   , values_to = "value") %>%
      group_by(item) %>%
      summarize_at(vars(value), lst(mean, sd)) %>%
      ungroup()
  }
  if(any(sapply(d, class) == "factor")){
    fct_lev <- d %>% 
      select_if(is.factor) %>%
      summarize_all(~list(unique(.)))
  }
  # d <- d %>% select(-one_of(moder))
  
  md_levs <- if(md_cl == "numeric"){
    if(moder %in% c("age", "baseAge", "baseYear")) {
      c(-10, 0, 10)
      } else if (moder %in% c("predInt", "education")) {
      c(-5, 0, 5) 
      } else {
        with(msd, c(mean[item == moder] - sd[item == moder], mean[item == moder], mean[item == moder] + sd[item == moder]))
      }
  } else { 
    if(type == "Bayesian"){
      unique(fct_lev[,moder][[1]])
    } else {
      unique(d) %>% as.matrix()
    }
  }
  if(moder %in% c("country", "continent", "scale")) rownames(md_levs) <- 1:nrow(md_levs)
  
  probs <- tribble(
    ~wrong           , ~correct,
    "metamodNEO.FFI" , "metamodNEO-FFI",
    "metamodBFI.S"   , "metamodBFI-S",
    "metamodIPIP.NEO", "metamodIPIP NEO",
    "metamodTDA.40"  , "metamodTDA-40",
    "metamodThe.Netherlands"  , "metamodThe Netherlands"
  )
  
  mod_frame <- if(type == "Bayesian") {
    expand.grid(
      SEI = 0
      , metamod = md_levs
      , stringsAsFactors = F
      ) 
  } else {
    md_levs
  }
  
  if(moder %in% c("scale", "country")) colnames(mod_frame) <- mapvalues(colnames(mod_frame), probs$wrong, probs$correct)
  
  pred.fx <- if(type == "Bayesian"){
    bind_cols(
      mod_frame, 
      fitted(m
             , newdata = mod_frame
             , re_formula = NA
             ) %>% data.frame
    ) %>%
      select(metamod, pred = Estimate, lower = Q2.5, upper = Q97.5)
  } else {
    bind_cols(
      tibble(modvalue = mod_frame), 
      predict(m, mod_frame) %>% data.frame()
    ) %>%
      select(modvalue, pred, lower = ci.lb, upper = ci.ub)
  }
  
  if(moder %in% c("country", "scale", "continent")){
    mssng <- if(moder == "country") "United States" else if(moder == "continent") "North America" else "NEO-FFI"
    nms <- str_remove(colnames(mod_frame), "metamod")
    trck <- apply(mod_frame, 1, function(x){
      if(sum(x) > 0) which(x == 1) else ncol(mod_frame) + 1
    }); trck <- c(nms, mssng)[trck]
    rownames(mod_frame) <- trck
    pred.fx <- pred.fx %>% mutate(modvalue = trck)
  }
  
  rm(list = c("m", "mod_frame", "d", "md_levs"))
  gc()
  return(pred.fx)
}

5.4.2 Run Meta-Analysis and Meta-Regression Models

loadRData <- function(fileName, type, obj, folder){
#loads an RData file, and returns it
    path <- sprintf("%s/results/3_ipd_meta/%s/%s/%s", local_path, type, folder, fileName)
    load(path)
    get(ls()[grepl(obj, ls())])
}

## load in effect size data 
## first get file names
nested_ipd3_meta <- tibble(type = c("Frequentist", "Bayesian")) %>%
  mutate(file = map(type, ~list.files(sprintf("%s/results/3_ipd_meta/%s/studyEffects", local_path, .)))) %>%
  unnest(file) %>%
  separate(file, c("Outcome", "Trait", "Moderator", "Covariate", "study"), sep = "_", remove = F) %>% 
  filter(!is.na(study)) %>%
  # filter(Trait == "N") %>%
  ## read in the files
  mutate(study = str_remove(study, ".RData"),
         data = map2(file, type, ~loadRData(.x, .y, "es", "studyEffects"))) %>%
  select(-file) %>%
  ## unnest effect sizes
  unnest(data)
nested_ipd3_meta
## # A tibble: 1,712 × 10
##    type        Outcome      Trait Moderator Covariate study term         Estimate      SEI    ni
##    <chr>       <chr>        <chr> <chr>     <chr>     <chr> <chr>           <dbl>    <dbl> <int>
##  1 Frequentist crystallized A     age       all       EAS   (Intercept)  5.67     0.344      788
##  2 Frequentist crystallized A     age       all       EAS   p_value:age -0.0103   0.00881    788
##  3 Frequentist crystallized A     age       all       GSOEP (Intercept)  8.08     0.206      647
##  4 Frequentist crystallized A     age       all       GSOEP p_value:age -0.00168  0.00195    647
##  5 Frequentist crystallized A     age       all       HILDA (Intercept)  5.13     0.110     7755
##  6 Frequentist crystallized A     age       all       HILDA p_value:age  0.000451 0.000817  7755
##  7 Frequentist crystallized A     age       all       HRS   (Intercept)  5.14     0.112     8432
##  8 Frequentist crystallized A     age       all       HRS   p_value:age -0.00142  0.00185   8432
##  9 Frequentist crystallized A     age       all       ROS   (Intercept)  7.02     0.284     1372
## 10 Frequentist crystallized A     age       all       ROS   p_value:age  0.0136   0.00651   1372
## # ℹ 1,702 more rows
## group and nest
nested_ipd3_meta <- nested_ipd3_meta %>%
  group_by(type, Trait, Outcome, Moderator, Covariate) %>%
  nest() %>%
  ungroup()

## add in meta-moderators, which requires taking original models and 
## modifying the Moderator column
nested_ipd3_meta <- nested_ipd3_meta %>%
  filter(Moderator == "none") %>%
  select(-Moderator) %>%
  full_join(crossing(
    Trait = traits$short_name
    , Moderator = stdyModers$short_name)) %>%
  full_join(nested_ipd3_meta)
mod <- "age"
mr <- if(mod %in% stdyModers$short_name) "metareg" else "meta"
## adding meta-regression values to effect sizes
es <- (nested_ipd3_meta %>% filter(Trait == "N" & Covariate == "all" & Moderator == "age" & type == "Bayesian"))$data[[1]]

# base bayesian model
m <- brm(formula = bf(Estimate | se(SEI) ~ 1 + (1 | study))
    , data = es
    , save_pars = save_pars(all = T)
    , sample_prior = T
    , prior = prior(cauchy(0,1), class = sd)
    , iter = 30
    , warmup = 20)
save(m, file = sprintf("%s/results/3_ipd_meta/bayes_sample_meta.RData", local_path))

mod <- "scale"
load(sprintf("%s/data/two_stage/meta_data/N_episodicMem.RData",  local_path))
es <- d %>% select(study, one_of(mod)) %>% 
  setNames(c("study", "metamod")) %>% 
  full_join(es) 
m <- brm(formula = bf(Estimate | se(SEI) ~ 1 + metamod + (1 | study))
    , data = es
    , save_pars = save_pars(all = T)
    , sample_prior = T
    , prior = prior(cauchy(0,1), class = sd)
    , iter = 30
    , warmup = 20)
save(m, file = sprintf("%s/results/3_ipd_meta/bayes_sample_metareg.RData", local_path))
rm(list = c("d", "es", "mod", "m", "mr"))
plan(multisession(workers = 12L))
nested_ipd3_meta <- nested_ipd3_meta %>%
  # full_join(done) %>% filter(is.na(done)) %>%
  filter(type == "Bayesian") %>%
  # filter(Moderator %in% c("scale", "country", "continent")) %>%
  mutate(metamod = 
           # pmap(list(data, type, Trait, Outcome, Moderator, Covariate)
           future_pmap(list(data, type, Trait, Outcome, Moderator, Covariate)
                        , possibly(ipd3_meta_fun, NA_real_)
                        , .progress = T
             , .options = furrr_options(
                                    globals = c("ipd3_meta_fx_fun"
                                                , "ipd3_meta_rx_fun"
                                                , "ipd3_meta_simpeff_fun"
                                                , "read_path"
                                                , "local_path"
                                                , "res_path"
                                                , "codebook"
                                                , "covars"
                                                , "moders"
                                                , "outcomes"
                                                , "studies"
                                                , "stdyModers"
                                                , "traits"
                                                , "data_path")
                                  , packages = c("lme4"
                                                 , "broom"
                                                 , "psych"
                                                 , "knitr"
                                                 , "broom.mixed"
                                                 , "brms"
                                                 #, "tidybayes"
                                                 #, "bootpredictlme4"
                                                 , "rstan"
                                                 , "estimatr"
                                                 , "metafor"
                                                 , "plyr"
                                                 , "tidyverse"))
                        ))
closeAllConnections()
nested_ipd3_meta
contr_fun <- function(m, std){
  cntrm <- c("p_value = 0", "p_value + p_value:genderFemale = 0")
  
  (multcomp::glht(m, cntrm) %>% # multcomp hypothesis function
        confint(., calpha = multcomp::univariate_calpha()))$confint %>%
        data.frame() %>% 
        mutate(term = c("male", "female")) %>%
        rename(estimate = Estimate, conf.low = lwr, conf.high = upr)
}

res <- nested_ipd3_meta %>% 
  filter(type == "Frequentist" & Covariate == "all") %>%
  mutate(res = map2(model, study, contr_fun))

res %>% 
  select(-model) %>%
  unnest(res) %>% 
  mutate(sig = ifelse(sign(conf.low) == sign(conf.high), "sig", "ns")) %>%
  filter(sig == "sig")

5.4.3 Compile Results

loadRData <- function(fileName, type, obj, folder){
#loads an RData file, and returns it
    path <- sprintf("%s/results/3_ipd_meta/%s/%s/%s", local_path, type, folder, fileName)
    # print(path)
    load(path)
    get(ls()[grepl(obj, ls())])
}

## load in effect size data 
## first get file names
nested_ipd3_meta <- tibble(type = c("Frequentist", "Bayesian")) %>%
  mutate(file = map(type, ~list.files(sprintf("%s/results/3_ipd_meta/%s/studyEffects", local_path, .)))) %>%
  unnest(file) %>%
  separate(file, c("Outcome", "Trait", "Moderator", "Covariate", "study"), sep = "_", remove = F) %>% 
  filter(!is.na(study)) %>%
  ## read in the files
  mutate(study = str_remove(study, ".RData"),
         studyEff = map2(file, type, ~loadRData(.x, .y, "x", "studySummary")),
         n = map2(file, type, ~loadRData(.x, .y, "es", "studyEffects")),
         n = map_dbl(n, ~(.)$ni[1])) %>%
  select(-file) %>%
  unnest(studyEff) %>%
  group_by(type, Trait, Outcome, Moderator, Covariate) %>%
  nest(studyEff = study:n) %>%
  ungroup()

## now we add in the study-level moderators (i.e. meta-regression)
  
nested_ipd3_meta <- nested_ipd3_meta %>%
  filter(Moderator == "none") %>%
  select(-Moderator) %>%
  full_join(crossing(
    Trait = traits$short_name
    , Moderator = stdyModers$short_name)) %>%
  full_join(nested_ipd3_meta) %>%
  mutate(file = sprintf("%s_%s_%s_%s.RData", Outcome, Trait, Moderator, Covariate),
         metaEff = map2(file, type, ~loadRData(.x, .y, "fx", "metaSummary")),
         metaHet = map2(file, type, possibly(~loadRData(.x, .y, "het", "metaHetero"), NA_real_))) %>%
  select(-file)

This results in a nested data frame, with columns:

  • studyEff = standardized study-specific effects from stage 1 regressions
  • metaEff = standardized meta-analytic effect from stage 2 meta-analysis
  • metaHet = Measures of cross-study heterogeneity
nested_ipd3_meta
## # A tibble: 410 × 8
##    type        Outcome      Trait Covariate studyEff          Moderator metaEff      metaHet         
##    <chr>       <chr>        <chr> <chr>     <list>            <chr>     <list>       <list>          
##  1 Frequentist crystallized A     age       <tibble [18 × 6]> baseAge   <df [2 × 6]> <tibble [1 × 6]>
##  2 Frequentist crystallized A     age       <tibble [18 × 6]> baseYear  <df [2 × 6]> <tibble [1 × 6]>
##  3 Frequentist crystallized A     age       <tibble [18 × 6]> continent <df [3 × 6]> <tibble [1 × 6]>
##  4 Frequentist crystallized A     age       <tibble [18 × 6]> country   <df [4 × 6]> <tibble [1 × 6]>
##  5 Frequentist crystallized A     age       <tibble [18 × 6]> predInt   <df [2 × 6]> <tibble [1 × 6]>
##  6 Frequentist crystallized A     age       <tibble [18 × 6]> scale     <df [2 × 6]> <dbl [1]>       
##  7 Frequentist crystallized A     all       <tibble [30 × 6]> baseAge   <df [2 × 6]> <tibble [1 × 6]>
##  8 Frequentist crystallized A     all       <tibble [30 × 6]> baseYear  <df [2 × 6]> <tibble [1 × 6]>
##  9 Frequentist crystallized A     all       <tibble [30 × 6]> continent <df [3 × 6]> <tibble [1 × 6]>
## 10 Frequentist crystallized A     all       <tibble [30 × 6]> country   <df [4 × 6]> <tibble [1 × 6]>
## # ℹ 400 more rows
5.4.3.0.1 Tables

First, we’ll combine study and meta-analytic results.

ipd3_meta_res <- nested_ipd3_meta %>%
  mutate(comEff = map2(studyEff, metaEff, ~(.y) %>% full_join(.x))) %>%
  select(type, Outcome, Trait, Moderator, Covariate, comEff) %>%
  unnest(comEff) %>%
  filter((Moderator == "none" & term == "p_value") | 
         (!Moderator %in% stdyModers$short_name & (grepl("p_value:", term))) |
         (Moderator %in% stdyModers$short_name & grepl("metamod", term))) %>%
  mutate(term = str_replace_all(term, "metamod", paste0("p_value:", Moderator)),
         study = mapvalues(study, c("BASE-I", "OCTO-TWIN"), c("BASE", "OCTO-Twin"))) %>%
  select(-SE)
ipd3_meta_res
## # A tibble: 1,635 × 11
##    type        Outcome      Trait Moderator Covariate term                       estimate  conf.low conf.high study         n
##    <chr>       <chr>        <chr> <chr>     <chr>     <chr>                         <dbl>     <dbl>     <dbl> <chr>     <dbl>
##  1 Frequentist crystallized A     baseAge   age       p_value:baseAge             0.00400 -0.00174    0.00973 Meta-Ana…    NA
##  2 Frequentist crystallized A     baseYear  age       p_value:baseYear            0.0102   0.00399    0.0164  Meta-Ana…    NA
##  3 Frequentist crystallized A     continent age       p_value:continentAustralia -0.0489  -0.211      0.113   Meta-Ana…    NA
##  4 Frequentist crystallized A     continent age       p_value:continentEurope    -0.177   -0.314     -0.0391  Meta-Ana…    NA
##  5 Frequentist crystallized A     country   age       p_value:countryAustralia   -0.0309  -0.0775     0.0158  Meta-Ana…    NA
##  6 Frequentist crystallized A     country   age       p_value:countryGermany     -0.0873  -0.133     -0.0418  Meta-Ana…    NA
##  7 Frequentist crystallized A     country   age       p_value:countrySweden      -0.268   -0.377     -0.158   Meta-Ana…    NA
##  8 Frequentist crystallized A     predInt   age       p_value:predInt            -0.0198  -0.0331    -0.00647 Meta-Ana…    NA
##  9 Frequentist crystallized A     baseAge   all       p_value:baseAge             0.00164 -0.00104    0.00431 Meta-Ana…    NA
## 10 Frequentist crystallized A     baseYear  all       p_value:baseYear            0.00507  0.000720   0.00943 Meta-Ana…    NA
## # ℹ 1,625 more rows
5.4.3.0.1.1 Study-Specific

Next, we’ll make a table of the results, separately for each moderator. To do this efficiently, we’ll make a function that creates those tables across all combinations. Before calling that function, we’ll do some reformatting and reshaping to get the data ready.

ipd3_res_tab <- ipd3_meta_res %>%
  mutate(sig = ifelse(sign(conf.low) == sign(conf.high), "sig", "ns")) %>% # significance marker
  mutate_at(vars(estimate:conf.high), 
            ~ifelse(abs(.) < .01, sprintf("%.3f", .), sprintf("%.2f",.))) %>%
  mutate(est = sprintf("%s<br>[%s, %s]", estimate, conf.low, conf.high),
         est = ifelse(sig == "sig", sprintf("<strong>%s</strong>", est), est),
         study = factor(study, c(studies_long, "Meta-Analytic")),
         Outcome = factor(Outcome, outcomes$short_name, outcomes$long_name)) %>%
  select(type:Covariate, term, study, est) %>%
  pivot_wider(names_from = "Trait", values_from = "est") %>%
  select(type:study, E, A, C, N, O) %>%
  arrange(type, Outcome, Moderator, Covariate, study)
ipd3_res_tab
## # A tibble: 414 × 11
##    type     Outcome              Moderator Covariate term        study     E                          A     C     N     O    
##    <chr>    <fct>                <chr>     <chr>     <chr>       <fct>     <chr>                      <chr> <chr> <chr> <chr>
##  1 Bayesian Crystallized Ability age       all       p_value:age BASE      0.002<br>[-0.04, 0.04]     <NA>  <NA>  0.01… -0.0…
##  2 Bayesian Crystallized Ability age       all       p_value:age EAS       -0.005<br>[-0.02, 0.01]    -0.0… <str… <str… -0.0…
##  3 Bayesian Crystallized Ability age       all       p_value:age GSOEP     -0.000<br>[-0.004, 0.003]  -0.0… 0.00… 0.00… -0.0…
##  4 Bayesian Crystallized Ability age       all       p_value:age HILDA     <strong>0.001<br>[0.000, … 0.00… <str… <str… <str…
##  5 Bayesian Crystallized Ability age       all       p_value:age HRS       -0.002<br>[-0.005, 0.001]  -0.0… <str… 0.00… <str…
##  6 Bayesian Crystallized Ability age       all       p_value:age LASA      <NA>                       <NA>  <NA>  -0.0… <NA> 
##  7 Bayesian Crystallized Ability age       all       p_value:age MAP       -0.002<br>[-0.009, 0.005]  <NA>  0.00… -0.0… <NA> 
##  8 Bayesian Crystallized Ability age       all       p_value:age MARS      <NA>                       <NA>  <NA>  -0.0… <NA> 
##  9 Bayesian Crystallized Ability age       all       p_value:age OCTO-Twin -0.01<br>[-0.06, 0.03]     <NA>  <NA>  0.03… <NA> 
## 10 Bayesian Crystallized Ability age       all       p_value:age ROS       0.004<br>[-0.007, 0.01]    <str… 0.00… <str… <str…
## # ℹ 404 more rows
ipd3_stdmeta_table_fun <- function(d, type, moder, cov){
  cv <- mapvalues(cov, covars$short_name, covars$long_name, warn_missing = F)
  if(!grepl("djust", cv)) cv <- paste(cv, "Adjusted")
  md <- mapvalues(moder, c(moders$short_name, stdyModers$short_name),
                  c(moders$long_name, stdyModers$long_name), warn_missing = F)
  rs <- d %>% group_by(Outcome) %>% tally() %>% 
    mutate(end = cumsum(n), start = lag(end) + 1, start = ifelse(is.na(start), 1, start))
  cs <- rep(1,6); names(cs) <- c(" ", traits$short_name)
  cap <- if(moder == "none") {
      sprintf("<strong>Table X.</strong><br><em>Method 3 Pooled Regression Using Random Effects: Study and Meta-Analytic Estimates of %s Personality-Cognitive Domain Relationships</em>", cv) 
    } else {
      sprintf("<strong>Table X.</strong><br><em>Method 3 Pooled Regression Using Random Effects: Study and Meta-Analytic %s Moderation of %s Personality-Cognitive Domain Relationships</em>", md, cv)
    }
  tab <- d %>%
    select(-Outcome) %>%
    kable(., "html"
          , escape = F
          , booktabs = T
          , col.names = c("Study", rep("$\\beta$ [CI]", 5))
          , align = c("r", rep("c", 5))
          , caption = cap) %>%
    kable_classic(full_width = F, html_font = "Times New Roman") %>%
    add_header_above(cs) 
  for (i in 1:nrow(rs)){
    tab <- tab %>% 
      kableExtra::group_rows(rs$Outcome[i], rs$start[i], rs$end[i])
  }
  save_kable(tab, file = sprintf("%s/results/3_ipd_meta/%s/tables/study specific/%s_%s.html",
                                 local_path, type, moder, cov))
  return(tab)
}

ipd3_std_tab <- ipd3_res_tab %>%
  filter(!Moderator %in% stdyModers$short_name) %>%
  select(-term) %>%
  group_by(type, Moderator, Covariate) %>%
  nest() %>%
  ungroup() %>%
  mutate(tab = pmap(list(data, type, Moderator, Covariate), ipd3_stdmeta_table_fun))
5.4.3.0.1.2 Meta-Analytic
ipd3_tab_fun <- function(d, type, moder){
  md <- mapvalues(moder, c(moders$long_name, stdyModers$long_name), c(moders$short_name, stdyModers$short_name), warn_missing = F)
  rs <- d %>% group_by(Outcome) %>% tally() %>% 
    mutate(end = cumsum(n), start = lag(end) + 1, start = ifelse(is.na(start), 1, start))
  cs <- if(length(unique(d$term)) == 1)  rep(1,6) else c(2, rep(1,5)) 
  names(cs) <- c(" ", traits$long_name)
  cln <- if(length(unique(d$term)) == 1) c("CovariaCtes", rep("<em>b</em> [CI]", 5)) else c("Covariates", "Term", rep("<em>b</em> [CI]", 5))
  # cln <- if(length(unique(d$term)) == 1) c("Covariates", rep("\\textit{b} [CI]", 5)) else c("Covariates", "Term", rep("\\textit{b} [CI]", 5))
  al <- if(length(unique(d$term)) == 1) c("r", rep("c", 5)) else c("r", "r", rep("c", 5))
  if(length(unique(d$term)) == 1) d <- d %>% select(-term)
  cap <- if(md == "none") "<strong>Table X.</strong><br><em>Method 3 Pooled Regression Using Random Effects: Meta-Analytic Effects of Personality-Crystallized Domain Associations</em>" else sprintf("<strong>Table X.</strong><br><em>Method 3 Pooled Regression Using Random Effects: Meta-Analytic %s Moderation of Personality-Crystallized Domain Associations</em>", md)
  tab <- d %>%
    arrange(Outcome) %>%
    select(-Outcome) %>%
    kable(., "html"
    # kable(., "latex"
          , booktabs = T
          , escape = F
          , col.names = cln
          , align = al
          , caption = cap
    ) %>% 
    kable_classic(full_width = F, html_font = "Times New Roman") %>%
    add_header_above(cs) 
  for (i in 1:nrow(rs)) {
    tab <- tab %>% kableExtra::group_rows(rs$Outcome[i], rs$start[i], rs$end[i])
  }
  save_kable(tab, file = sprintf("%s/results/3_ipd_meta/%s/tables/overall/%s.html"
                                 , local_path, type, md))
  return(tab)
}

ipd3_meta_res_tab <- ipd3_res_tab %>%
  filter(study == "Meta-Analytic") %>%
  select(-study) %>%
  mutate(term = str_remove_all(term, "p_value:"),
         term = mapvalues(term, str_remove_all(stdyModers$short_term, " "), stdyModers$long_term),
         term = mapvalues(term, str_replace_all(stdyModers$short_term, "-", "M"), stdyModers$long_term),
         term = mapvalues(term, moders$short_term, moders$long_term),
         term = mapvalues(term, moders$short_name, moders$long_term),
         Covariate = mapvalues(Covariate, covars$short_name, covars$long_name)) %>%
  arrange(Outcome, term, Covariate)
ipd3_meta_res_tab %>%
  group_by(type, Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(tab = pmap(list(data, type, Moderator), ipd3_tab_fun))
## # A tibble: 20 × 4
##    type        Moderator data              tab           
##    <chr>       <chr>     <list>            <list>        
##  1 Bayesian    age       <tibble [2 × 8]>  <kablExtr [1]>
##  2 Frequentist age       <tibble [2 × 8]>  <kablExtr [1]>
##  3 Bayesian    continent <tibble [10 × 8]> <kablExtr [1]>
##  4 Frequentist continent <tibble [10 × 8]> <kablExtr [1]>
##  5 Bayesian    country   <tibble [20 × 8]> <kablExtr [1]>
##  6 Frequentist country   <tibble [20 × 8]> <kablExtr [1]>
##  7 Bayesian    education <tibble [2 × 8]>  <kablExtr [1]>
##  8 Frequentist education <tibble [2 × 8]>  <kablExtr [1]>
##  9 Bayesian    gender    <tibble [2 × 8]>  <kablExtr [1]>
## 10 Frequentist gender    <tibble [2 × 8]>  <kablExtr [1]>
## 11 Bayesian    none      <tibble [5 × 8]>  <kablExtr [1]>
## 12 Frequentist none      <tibble [5 × 8]>  <kablExtr [1]>
## 13 Bayesian    predInt   <tibble [5 × 8]>  <kablExtr [1]>
## 14 Frequentist predInt   <tibble [5 × 8]>  <kablExtr [1]>
## 15 Bayesian    scale     <tibble [30 × 8]> <kablExtr [1]>
## 16 Frequentist scale     <tibble [30 × 8]> <kablExtr [1]>
## 17 Bayesian    baseAge   <tibble [5 × 8]>  <kablExtr [1]>
## 18 Frequentist baseAge   <tibble [5 × 8]>  <kablExtr [1]>
## 19 Bayesian    baseYear  <tibble [5 × 8]>  <kablExtr [1]>
## 20 Frequentist baseYear  <tibble [5 × 8]>  <kablExtr [1]>
ipd3_tab_fun <- function(d, type, cov){
  # long outcome name
  covar <- mapvalues(cov, covars$long_name, covars$short_name, warn_missing = F)
  # getting row numbers for later grouping
  rs <- d %>% group_by(Moderator) %>% tally() %>% 
    mutate(end = cumsum(n), start = lag(end) + 1, start = ifelse(is.na(start), 1, start))
  # number and name of columns for span columns 
  cs <- rep(1,6)
  names(cs) <- c(" ", traits$long_name)
  # cln <- if(length(unique(d$term2)) == 1) c("Covariate", rep("\\textit{b} [CI]", 5)) else c(" ", "Term", rep("\\textit{b} [CI]", 5))
  cln <- c("Term", rep("<em>b</em> [CI]", 5))
  al <- c("r", rep("c", 5))
  # caption 
  cap <- sprintf("Method 3 Pooled Regression Using Random Effects: Meta-Analytic Estimates of %s Personality-Crystallized Domain Associations", cov)
  # kable the table
  tab <- d %>%
    select(-Moderator) %>%
    kable(., "html"
    # kable(., "latex"
          , booktabs = T
          , escape = F
          , col.names = cln
          , align = al
          , caption = cap
    ) %>% 
    kable_classic(full_width = F, html_font = "Times New Roman") %>%
    # kable_styling(full_width = F, font_size = 7) %>%
    add_header_above(cs)
  # for loop to add grouped sections 
  for (i in 1:nrow(rs)){
    tab <- tab %>% 
      kableExtra::group_rows(rs$Moderator[i], rs$start[i], rs$end[i]) 
  }
  # save the resulting html table
  save_kable(tab, file = sprintf("%s/results/3_ipd_meta/%s/tables/key terms/%s.html"
                                 , local_path, type, covar))
  return(tab) # return the html table
}

ipd3_fx_tab2 <- ipd3_res_tab %>%
  filter(study == "Meta-Analytic") %>%
  select(-study) %>%
  mutate(term = str_remove_all(term, "p_value:"),
         term = mapvalues(term, str_remove_all(stdyModers$short_term, " "), stdyModers$long_term),
         term = mapvalues(term, str_replace_all(stdyModers$short_term, "-", "M"), stdyModers$long_term),
         term = mapvalues(term, moders$short_term, moders$long_term),
         term = mapvalues(term, moders$short_name, moders$long_term),
         Moderator = factor(Moderator, levels = c(moders$short_name, stdyModers$short_name), labels = c(moders$long_name, stdyModers$long_name)),
         Covariate = mapvalues(Covariate, covars$short_name, covars$long_name)) %>%
  arrange(Outcome, Moderator, term, Covariate) %>%
  filter(Covariate %in% c("Unadjusted", "Fully Adjusted")) %>%
  group_by(Outcome, type, Covariate) %>% 
  nest() %>%
  ungroup() %>%
  mutate(tab = pmap(list(data, type, Covariate), ipd3_tab_fun))

# save(ipd1a_fx_tab, ipd1a_fx_tab2, ipd1a_res, file = sprintf("%s/manuscript/results/ipd1b_fx_tab.RData", res_path))
5.4.3.0.1.3 All Model Terms
ipd3_mod_tab <- nested_ipd3_meta %>%
  select(-metaEff, -metaHet) %>%
  unnest(studyEff) %>%
  # keep key terms 
  # mark significance and prettify trait, outcome, and covariate names
  mutate(sig = ifelse(sign(conf.low) == sign(conf.high), "sig", "ns"),
         Trait = factor(Trait, traits$short_name),
         Outcome = factor(Outcome, outcomes$short_name, outcomes$long_name),
         Moderator = factor(Moderator, c(moders$short_name, stdyModers$short_name), c(moders$long_name, stdyModers$long_name)),
         Covariate = factor(Covariate, covars$short_name, str_wrap(covars$long_name, 15)),
         term = str_replace_all(term, "metamod", paste0("p_value:", Moderator))) %>%
  # format values as text, combine estimates and CI's, bold significance
  mutate_at(vars(estimate, conf.low, conf.high), 
            ~ifelse(abs(.) < .01, sprintf("%.3f", .), sprintf("%.2f", .))) %>%
  mutate(est = sprintf("%s<br>[%s, %s]", estimate, conf.low, conf.high),
         est = ifelse(sig == "sig", sprintf("<strong>%s</strong>", est), est),
         study = mapvalues(study, c("BASE-I", "OCTO-TWIN"), c("BASE", "OCTO-Twin"))) %>%
  # mutate(est = sprintf("%s [%s, %s]", estimate, conf.low, conf.high),
  # est = ifelse(sig == "sig", sprintf("\\textbf{%s}", est), est)) %>%
  # final reshaping, remove extra columns, arrange values, and change to wide format
  select(-estimate, -conf.low, -conf.high, -sig, -n) %>%
  arrange(type, Outcome, Trait, Moderator, Covariate) %>%
  pivot_wider(names_from = "Trait", values_from = "est") 

ipd3_mod_tab_fun <- function(d, type, out, moder, cov){
  md <- mapvalues(moder, c(moders$long_name, stdyModers$long_name), c(moders$short_name, stdyModers$short_name), warn_missing = F)
  o <- mapvalues(out, outcomes$long_name, outcomes$short_name, warn_missing = F)
  cv <- mapvalues(cov, covars$long_name, covars$short_name, warn_missing = F)
  cs <- rep(1,6)
  names(cs) <- c(" ", traits$long_name)
  # cln <- if(length(unique(d$term2)) == 1) c("Covariate", rep("\\textit{b} [CI]", 5)) else c(" ", "Term", rep("\\textit{b} [CI]", 5))
  cln <- c("Term", rep("<em>b</em> [CI]", 5))
  al <- c("r", rep("c", 5))
  # caption 
  cap <- if(md == "none") "3 Separate Models Followed Random Effects Meta-Analysis: All Model Estimates of Fixed Effect Personality-Crystallized Domain Associations" else sprintf("3 Separate Models Followed Random Effects Meta-Analysis: All Model Estimates of Fixed Effect %s Moderation of Personality-Crystallized Domain Associations", md)
  
  d <- d %>% arrange(study, term)
  
  rs <- d %>% group_by(study) %>% tally() %>% 
    mutate(end = cumsum(n), start = lag(end) + 1, start = ifelse(is.na(start), 1, start))
  
  # kable the table
  tab <- d %>%
    select(-study) %>%
    kable(., "html"
    # kable(., "latex"
          , booktabs = T
          , escape = F
          , col.names = cln
          , align = al
          , caption = cap
    ) %>% 
    kable_classic(full_width = F, html_font = "Times New Roman") %>%
    # kable_styling(full_width = F, font_size = 7) %>%
    add_header_above(cs)
  
  for(i in 1:nrow(rs)){
    tab <- tab %>% kableExtra::group_rows(rs$study[i], rs$start[i], rs$end[i])
  }
  # save the resulting html table
  save_kable(tab, file = sprintf("%s/results/3_ipd_meta/%s/tables/all terms/%s-%s-%s.html"
                                 , local_path, type, o, md, cv))
  return(tab) # return the html table
}

ipd3_mod_tab2 <- ipd3_mod_tab %>%
  group_by(type, Outcome, Moderator, Covariate) %>%
  nest() %>%
  ungroup() %>%
  mutate(tab = pmap(list(data, type, Outcome, Moderator, Covariate), ipd3_mod_tab_fun))
ipd3_meta_all <- nested_ipd3_meta %>% 
  select(-studyEff, -metaHet) %>%
  unnest(metaEff) %>% 
  mutate(sig = ifelse(sign(conf.low) == sign(conf.high), "sig", "ns"),
         Trait = factor(Trait, traits$short_name),
         Outcome = factor(Outcome, outcomes$short_name, outcomes$long_name),
         Moderator = factor(Moderator, c(moders$short_name, stdyModers$short_name), c(moders$long_name, stdyModers$long_name)),
         Covariate = ifelse(Moderator != "None" & Covariate == "None", Moderator, Covariate),
         Covariate = factor(Covariate, moders$short_name, str_wrap(moders$long_name, 15)),
         term = str_replace_all(term, "metamod", paste0("p_value:", Moderator))) %>%
  # format values as text, combine estimates and CI's, bold significance
  mutate_at(vars(estimate, conf.low, conf.high), 
            ~ifelse(abs(.) < .01, sprintf("%.3f", .), sprintf("%.2f", .))) %>%
  mutate(est = sprintf("%s<br>[%s, %s]", estimate, conf.low, conf.high),
         est = ifelse(sig == "sig", sprintf("<strong>%s</strong>", est), est)) %>%
  # mutate(est = sprintf("%s [%s, %s]", estimate, conf.low, conf.high),
  # est = ifelse(sig == "sig", sprintf("\\textbf{%s}", est), est)) %>%
  # final reshaping, remove extra columns, arrange values, and change to wide format
  select(-estimate, -conf.low, -conf.high, -sig, -SE) %>%
  arrange(type, Outcome, Trait, Moderator, Covariate) %>%
  pivot_wider(names_from = "Trait", values_from = "est") %>%
  group_by(type, Outcome, Covariate) %>%
  nest() %>%
  ungroup() 
5.4.3.0.1.4 Heterogeneity
loadRData <- function(fileName, type, obj, folder){
#loads an RData file, and returns it
    path <- sprintf("%s/results/3_ipd_meta/%s/%s/%s", local_path, type, folder, fileName)
    load(path)
    get(ls()[grepl(obj, ls())])
}

## load in "fixed" effects
## first get file names
nested_ipd3_het <- tibble(type = c("Frequentist", "Bayesian")) %>%
  mutate(file = map(type, ~list.files(sprintf("%s/results/3_ipd_meta/%s/metaHetero", local_path, .)))) %>%
  unnest(file) %>%
  separate(file, c("Outcome", "Trait", "Moderator", "Covariate"), sep = "_", remove = F) %>% 
  ## read in the files
  mutate(Covariate = str_remove(Covariate, ".RData"),
         het = map2(file, type, possibly(~loadRData(.x, .y, "het", "metaHetero"), NA_real_))) %>%
  filter(!is.na(het)) %>%
  select(-file) 
round_fun <- function(x){
  ifelse(x < .001 & x > 0, "&lt; .001"
   , ifelse(x > -.001 & x < 0, "&gt; -.001" 
   , ifelse(abs(x) < .01, sprintf("%.3f", x) 
   , sprintf("%.2f", x))))
}

ip3_hetero_tab_fun <- function(d, type, out, mod, cov){
  moder <- mapvalues(mod, moders$short_name, moders$long_name, warn_missing = F)
  d2 <- d %>%
    mutate(Trait = factor(Trait, traits$short_name, traits$long_name)) %>%
    mutate_at(vars(-Trait), round_fun) %>%
    arrange(Trait)
  
  cap <- if(mod == "none") "Heterogeneity Estimates of Personality-Crystallized Domain Associations" else sprintf("Heterogeneity Estimates for Overall %s Moderation of Personality-Crystallized Domain Associations", moder)
  cap <- sprintf("<strong>Table SX</strong><br><em>%s</em>", cap)
  
  tab <- d2 %>%
    kable(.
          , "html"
          , align = c("r", rep("c", ncol(d2)-1))
          , caption = cap
          , escape = F
    ) %>%
    kable_classic(full_width = F, html_font = "Times New Roman") 
  
  save_kable(tab, file = sprintf("%s/results/3_ipd_meta/%s/tables/heterogeneity/%s-%s-%s.html", local_path, type, out, mod, cov))
  return(tab)
}

nested_ipd3_het_tab <- nested_ipd3_het %>%
  filter(!Moderator %in% stdyModers$short_name) %>%
  group_by(type) %>%
  nest() %>%
  ungroup() %>%
  mutate(data = map(data, ~(.) %>% 
                      unnest(het) %>%
                      group_by(Outcome, Moderator, Covariate) %>% 
                      nest() %>%
                      ungroup())) %>%
  unnest(data) %>%
  mutate(tab = pmap(list(data, type, Outcome, Moderator, Covariate), ip3_hetero_tab_fun))

5.4.4 Figures

5.4.4.0.0.1 Overall Forest
ipd3_fx_plot_fun <- function(df, mod, type, cov){
  m <- mapvalues(mod, c(moders$long_name, stdyModers$long_name), c(moders$short_name, stdyModers$short_name), warn_missing = F)
  cv <- mapvalues(cov, covars$short_name, covars$long_name, warn_missing = F)
  d <- round(max(abs(min(df$estimate)), abs(max(df$estimate))), 3)
  lim <- c(0-d-(d/2.5), 0+d+(d/2.5))
  brk <- if(d > .01) round(c(0-d-(d/5), 0, 0+d+(d/5)),2) else round(c(0-d-(d/5), 0, 0+d+(d/5)),3) 
  # lim_high <- lim[2]*4
  lab <- str_replace(brk, "^0.", ".")
  shapes <- c(15, 16, 17, 18)[1:length(unique(df$term))]
  lt <- rep("solid", length(unique(df$term)))
  titl <- if(mod == "none"){NULL} else {sprintf("%s Moderation of Personality-Outcome Associations", mod)}
  titl <- if(!cov %in% c("none", "all")) paste(cv, "Adjusted", titl, collapse = " ") else paste(cv, titl, collapse = " ")
  leg <- if(length(unique(df$term)) > 1){"bottom"} else {"none"}
  p <- df %>%
    mutate(conf.low = ifelse(conf.low < lim[1], lim[1], conf.low),
           conf.high = ifelse(conf.high > lim[2], lim[2], conf.high)) %>% 
  ggplot(aes(x = Outcome, y = estimate)) +
    scale_y_continuous(limits = lim, breaks = brk, labels = lab) + 
    scale_size_manual(values = c(1.2, .85)) +
    scale_shape_manual(values = shapes) +
    scale_color_manual(values = c("blue", "black")) +
    scale_linetype_manual(values = lt) +
    geom_hline(aes(yintercept = 0), size = .25, color = "gray50") +
    geom_errorbar(aes(ymin = conf.low, ymax = conf.high, linetype = term)
                  , width = 0
                  , position = position_dodge(width = .9)) + 
    geom_point(aes(color = sig, size = sig, shape = term)
                  , position = position_dodge(width = .9)) +
    labs(x = NULL
         , y = "Estimate (POMP)"
         , title = titl
         , subtitle = "Method 3: Pooled Regression Using Random Effects"
         ) +
    guides(size = "none", color = "none") +
    facet_grid(~Trait, scales = "free_y", space = "free") +
    coord_flip() +
    theme_classic() +
    theme(legend.position = leg,
          plot.title = element_text(face = "bold", size = rel(1.2), hjust = .5),
          plot.subtitle = element_text(size = rel(1.1), hjust = .5),
          panel.background = element_rect(color = "black", fill = "white"),
          strip.background = element_blank(),
          strip.text = element_text(face = "bold", color = "black", size = rel(1.4)),
          axis.text = element_text(color = "black"),
          axis.text.y = element_text(size = rel(1)))
  ht <- length(unique(df$Outcome)); ht2 <- length(unique(df$term))
  local_path <- length(unique(df$Trait))
ggsave(file = sprintf("%s/results/3_ipd_meta/%s/figures/overall forest/%s_%s_fixed.png", local_path, type, m, cov), width = local_path*2, height = 1.25*ht + .75*ht2)
rm(p)
gc()
return(T)
}

nested_ipd3_meta %>%
  select(-studyEff, -metaHet) %>%
  unnest(metaEff) %>%
    filter((Moderator == "none" & term == "p_value")|
          (Moderator != "none" & grepl("^p_value:", term))) %>%
    mutate(sig = ifelse(sign(conf.low) == sign(conf.high), "sig", "ns"),
           sig = factor(sig, levels = c("sig","ns")),
           Trait = factor(Trait, levels = traits$short_name),
           Outcome = factor(Outcome, levels = outcomes$short_name, labels = str_wrap(outcomes$long_name, 15)),
           Moderator = factor(Moderator, levels = c(moders$short_name, stdyModers$short_name), labels = c(moders$long_name, stdyModers$long_name)),
           Outcome = forcats::fct_rev(Outcome))  %>%
  filter(type == "Frequentist" & Moderator == "None") %>%
  group_by(type, Moderator, Covariate) %>%
  nest() %>%
  ungroup() %>%
  mutate(pmap(list(data, Moderator, type, Covariate), ipd3_fx_plot_fun))
5.4.4.0.0.2 Study-Specific Forest
ipd3_rx_plot_fun <- function(df, outcome, mod, type, cov, trait){
  print(paste(outcome, mod))
  trt <- mapvalues(trait, traits$short_name, traits$long_name)
  m <- mapvalues(mod, moders$short_name, moders$long_name, warn_missing = F)
  d <- round(max(abs(min(df$estimate)), abs(max(df$estimate))), 3)
  # stds <- unique(df$study)
  lim <- c(0-d-(d/2.5), 0+d+(d/2.5))
  brk <- round(c(0-d-(d/5), 0, 0+d+(d/5)),2)
  lab <- str_remove(round(c(0-d-(d/5), 0, 0+d+(d/5)),2), "^0")
  shapes <- c(15, 16, 17, 18)[1:length(unique(df$term))]
  lt <- rep("solid", length(unique(df$term)))
  titl <- if(mod == "none"){trt} else {sprintf("%s x %s", trt, m)}
  leg <- if(length(unique(df$term)) > 1){"bottom"} else {"none"}
  df <- df %>% mutate(study = mapvalues(study, c("BASE-I", "OCTO-TWIN"), c("BASE", "OCTO-Twin")))
  df <- df %>% full_join(tibble(study = " ", estimate = NA, n = NA))
  df <- df %>% arrange(estimate)
  stds <- df$study[!df$study %in% c("Meta-Analytic", " ")]
  df <- df %>%
    mutate(study = factor(study, rev(c(" ", stds, "Meta-Analytic")))
           # , conf.low = ifelse(conf.low < lim[1], lim[1], conf.low)
           # , conf.high = ifelse(conf.high > lim[2], lim[2], conf.high)
           , lb = ifelse(conf.low < lim[1], "lower"
                         , ifelse(conf.high > lim[2], "upper", "neither"))
           , conf.low2 = ifelse(conf.low < lim[1], lim[1], conf.low)
           , conf.high2 = ifelse(conf.high > lim[2], lim[2], conf.high)
           # , study = factor(study, levels = str_remove_all(c("Overall", studies_long), "-"), labels = c("Overall", studies_long))
           # Trait = factor(Trait, levels = traits$short_name, labels = traits$long_name),
           , type = ifelse(study == "Meta-Analytic", "fixed", "random"))
  p1 <- df %>%
    ggplot(aes(x = study, y = estimate)) + 
    geom_errorbar(aes(ymin = conf.low, ymax = conf.high)
                  , position = "dodge"
                  , width = .2) + 
    geom_point(aes(shape = term, size = term)) + 
    geom_segment(data = df %>% filter(lb == "lower")
                 , aes(y = conf.high2, yend = conf.low2, xend = study)
                 , arrow = arrow(type = "closed", length = unit(0.1, "cm"))) +
    geom_segment(data = df %>% filter(lb == "upper")
                 , aes(y = conf.low2, yend = conf.high2, xend = study)
                 , arrow = arrow(type = "closed", length = unit(0.1, "cm"))) +
    geom_hline(aes(yintercept = 0), linetype = "dashed", size = .5) +
    geom_vline(aes(xintercept = 1.5)) +
    geom_vline(aes(xintercept = length(stds) + 1.5)) +
    annotate("rect", xmin = length(stds) + 1.6, xmax = Inf, ymin = -Inf, ymax = Inf, fill = "white") +
    scale_y_continuous(limits = lim, breaks = brk, labels = lab) + 
    scale_size_manual(values = c(3,2)) + 
    scale_shape_manual(values = c(15, 16)) +
    labs(x = NULL
         , y = "Estimate"
         # , title = "  "
    ) +
    coord_flip() + 
    theme_classic() + 
    theme(legend.position = "none"
          , axis.text = element_text(face = "bold")
          , axis.title = element_text(face = "bold")
          , plot.title = element_text(face = "bold", hjust = .5)
          , axis.ticks.y = element_blank()
          , axis.line.y = element_blank()
          , axis.line.x.top = element_line(size = 1))
  
  d2 <- df %>%
    mutate_at(vars(estimate, conf.low, conf.high)
              , ~ifelse(abs(.) < .01, sprintf("%.3f", .), sprintf("%.2f", .))) %>%
    mutate_at(vars(estimate, conf.low, conf.high), ~str_replace_all(., "^0.", ".")) %>%
    mutate_at(vars(estimate, conf.low, conf.high), ~str_replace_all(., "^-0.", "-.")) %>%
    mutate(est = ifelse(study != " ", sprintf("%s [%s, %s]      ", estimate, conf.low, conf.high), "")
           , n = as.character(n)
           ) %>%
    select(study, n, est) %>%
    pivot_longer(cols = c(n, est), names_to = "est", values_to = "value")
  p2 <- d2 %>%
    ggplot(aes(x = study, y = est)) +
      geom_text(aes(label = value), hjust = .5, size = 3.5) + 
      annotate("text", label = "b [CI]", x = length(stds) + 1.75, y = "est", hjust = .5, vjust = 0) +
      annotate("text", label = "N", x = length(stds) + 1.75, y = "n", hjust = .5, vjust = 0) +
      geom_vline(aes(xintercept = 1.5)) +
      geom_vline(aes(xintercept = length(stds) + 1.5)) +
      coord_flip() +
      theme_void() +
      theme(plot.title = element_text(face = "bold", hjust = 0)
            , axis.text = element_blank()
            , axis.ticks = element_blank()
            , axis.title = element_blank())
  
  my_theme <- function(...) {
    theme_classic() + 
      theme(plot.title = element_text(face = "italic"))
  }
  title_theme <- calc_element("plot.title", my_theme())
  ttl <- ggdraw() + 
      draw_label(
          titl,
          fontfamily = title_theme$family,
          fontface = title_theme$face,
          size = title_theme$size-2
      )

  p3 <- cowplot::plot_grid(p1, p2
                     , rel_widths = c(.5, .5)#c(.4, .6)
                     , align = "h"
                     )
  # p <- cowplot::plot_grid(ttl, subttl, p3, rel_heights = c(.05, .05, .9), nrow = 3)
  p <- cowplot::plot_grid(ttl, p3, rel_heights = c(.05, .95), nrow = 2)
  save(p
       , file = sprintf("%s/results/3_ipd_meta/%s/figures/study specific forest/rdata/%s_%s_%s_%s.RData", local_path, type, outcome, trait, mod, cov))
  gc()
  return(p)
}

## fixed effects
nested_ipd3_reg_fp <- ipd3_meta_res %>%
  filter(Moderator %in% moders$short_name) %>%
  ## filter key terms
  filter((Moderator == "none" & term == "p_value")|
         (Moderator != "none" & grepl("^p_value:", term) & !grepl("study", term))) %>%
  ## significance
  mutate(sig = ifelse(sign(conf.low) == sign(conf.high), "sig", "ns")
         , study = mapvalues(study, studies_long, studies_sp, warn_missing = F)) %>%
  ## grouping for plotting
  group_by(Outcome, Moderator, type, Covariate, Trait) %>%
  nest() %>%
  ungroup() %>%
  mutate(p = pmap(list(data, Outcome, Moderator, type, Covariate, Trait), ipd3_rx_plot_fun))

ipd3_rx_plot_comb_fun <- function(outcome, cov, mod, type, d){
  o <- mapvalues(outcome, outcomes$short_name, outcomes$long_name, warn_missing = F)
  cv <- mapvalues(cov, covars$short_name, covars$long_name, warn_missing = F)
  titl <- paste0(o, ",")
  titl <- if(!cov %in% c("none", "all")) paste(titl, cv, "Adjusted", collapse = ", ") else paste(titl, cv, collapse = ", ")
  p1 <- plot_grid(
    d$p[[1]]
    , d$p[[2]]
    , d$p[[3]]
    , d$p[[4]]
    , d$p[[5]]
    , nrow = 3
    , ncol = 2
    , axis = "tblr"
    , align = "hv"
    )
  my_theme <- function(...) {
    theme_classic() + 
      theme(plot.title = element_text(face = "bold"))
  }
  title_theme <- calc_element("plot.title", my_theme())
  ttl <- ggdraw() + 
      draw_label(
          titl,
          fontfamily = title_theme$family,
          fontface = title_theme$face,
          size = title_theme$size
      )
  my_theme <- function(...) {
    theme_classic() +
      theme(plot.subtitle = element_text(hjust = 0))
  }
  subtitle_theme <- calc_element("subplot.title", my_theme())
  subttl <- ggdraw() +
      draw_label(
          "Method 3: Two-Stage Individual Participant Meta-Analysis",
          fontfamily = subtitle_theme$family,
          fontface = subtitle_theme$face,
          size = subtitle_theme$size
      )
  p <- cowplot::plot_grid(ttl, subttl, p1, rel_heights = c(.03, .03, .94), nrow = 3)
  ggsave(p 
         , file = sprintf("%s/results/3_ipd_meta/%s/figures/study specific forest/%s_%s_%s.png", local_path, type, outcome, mod, cov)
         , width = 10, height = 10)
  ggsave(p 
         , file = sprintf("%s/results/3_ipd_meta/%s/figures/study specific forest/%s_%s_%s.pdf", local_path, type, outcome, mod, cov)
         , width = 10, height = 10)
  return(T)
}

nested_ipd3_reg_fp %>%
  mutate(Trait = factor(Trait, traits$short_name)) %>%
  arrange(Trait) %>%
  select(-data) %>%
  group_by(type, Outcome, Moderator, Covariate) %>%
  nest() %>% 
  ungroup() %>%
  mutate(p = pmap(list(Outcome, Covariate, Moderator, type, data), ipd3_rx_plot_comb_fun))

5.4.4.1 Overall Simple Effects Plots

loadRData <- function(fileName, type, obj, folder){
#loads an RData file, and returns it
    path <- sprintf("%s/results/3_ipd_meta/%s/%s/%s", local_path, type, folder, fileName)
    # print(path)
    load(path)
    get(ls()[grepl(obj, ls())])
}

## load in effect size data 
## first get file names
nested_ipd3_meta <- tibble(type = c("Frequentist", "Bayesian")) %>%
  mutate(file = map(type, ~list.files(sprintf("%s/results/3_ipd_meta/%s/studyPredicted", local_path, .)))) %>%
  unnest(file) %>%
  separate(file, c("Outcome", "Trait", "Moderator", "Covariate", "study"), sep = "_", remove = F) %>% 
  filter(!is.na(study)) %>%
  ## read in the files
  mutate(study = str_remove(study, ".RData"),
         pred.rx = map2(file, type, ~loadRData(.x, .y, "pred.rx", "studyPredicted"))
         , n = map2(file, type, ~loadRData(.x, .y, "es", "studyEffects"))
         , n = map_dbl(n, ~(.)$ni[1])) %>%
  select(-file) %>%
  unnest(pred.rx) %>%
  group_by(type, Trait, Outcome, Moderator, Covariate) %>%
  nest(studyPred = study:n) %>%
  ungroup()
ipd3_pred_fx_fun <- function(d, mod, type, outcome, trait, cov){
  d <- d %>% unclass %>% data.frame
  d$mod_value <- d[,mod]
  d <- d %>% select(-all_of(mod)) %>% as_tibble
  if(class(d$mod_value) %in% c("factor", "character")){
    d <- d %>% mutate(mod_fac = factor(mod_value))
    } else{
      d2 <- d %>% 
          select(study, mod_value) %>% 
          distinct() %>% 
          arrange(study, mod_value)
      if(mod == "age") d2 <- d2 %>% mutate(mod_fac = factor(mod_value, levels = c(-10, 0, 10), labels = c("-10 yrs", "M", "+10 yrs")))
      else if(mod == "baseYear") d2 <- d2 %>% mutate(mod_fac = factor(mod_value, levels = c(-10, 0, 10), labels = c("1990", "200)0", "2010")))
      else if(mod == "baseAge") d2 <- d2 %>% mutate(mod_fac = factor(mod_value, levels = c(-10, 0, 10), labels = c("50", "60", "70")))
      else if(mod == "predInt") d2 <- d2 %>% mutate(mod_fac = factor(mod_value, levels = c(-5, 0, 5), labels = c("-5 yrs", "5 yrs", "+5 yrs")))
      else if(mod == "education") d2 <- d2 %>% mutate(mod_fac = factor(mod_value, levels = c(-5, 0, 5), labels = c("-5 yrs", "12 years", "+5 yrs")))
      else d2 <- d2 %>% mutate(mod_fac = factor(mod_value, levels = unique(mod_value), labels = c("-1 SD", "M", "+1 SD")))
      d <- d %>% full_join(d2) %>% ungroup()
  }
  pred.fx <- d %>%
    group_by(p_value, mod_fac) %>%
    summarize_at(vars(pred, lower, upper), ~weighted.mean(., n)) %>%
    ungroup()
  save(pred.fx, file = sprintf("%s/results/3_ipd_meta/%s/metaPredicted/%s_%s_%s_%s.RData"
                          , local_path, type, outcome, trait, mod, cov))
}

nested_ipd3_meta %>%
  # filter(Moderator == "baseAge") %>%
  mutate(pred = pmap(list(studyPred, Moderator, type, Outcome, Trait, Covariate), ipd3_pred_fx_fun))
ipd3_se_plot_fun <- function(d, outcome, mod, cov, type){
  # print(paste(int, mod, cov, random, imp))
  o <- mapvalues(outcome, outcomes$short_name, outcomes$long_name, warn_missing = F)
  cv <- mapvalues(cov, covars$short_name, covars$long_name, warn_missing = F)
  m <- mapvalues(mod, moders$short_name, moders$long_name, warn_missing = F)
  titl <- if(mod == "none"){sprintf("%s", o)} else {sprintf("%s: Personality x %s Simple Effects", o, m)}
  
  d <- d %>% 
    mutate(study = mapvalues(study, c("BASE-I", "OCTO-TWIN"), c("BASE", "OCTO-Twin")),
           study = factor(study, levels = stdcolors$studies))
  std <- unique(d$study)
  # cols <- (stdcolors %>% filter(studies %in% std))$colors
  d <- d %>% unclass %>% data.frame
  d$mod_value <- d[,mod]
  d <- d %>% select(-all_of(mod)) %>% as_tibble
  d <- if(class(d$mod_value) %in% c("factor", "character")){
    d %>% mutate(mod_fac = factor(mod_value))
    } else{
      d %>%
      full_join(
        d %>% select(Trait,study, mod_value) %>% distinct() %>% arrange(study, mod_value) %>%
          group_by(Trait, study) %>% mutate(mod_fac = factor(c("-1 SD", "M", "+1 SD"), levels = c("-1 SD", "M", "+1 SD"))) %>% ungroup()
        )
  }
  lt <- c("dotted", "solid", "dashed")[1:length(unique(d$mod_fac))]
  ht <- length(unique(d$mod_fac))
  p <- d %>% 
    mutate(Trait = factor(Trait, levels = traits$short_name, labels = traits$long_name)) %>%
    ggplot(aes(x = p_value, y = pred, group = interaction(Trait, mod_fac), linetype = mod_fac)) + 
      # geom_line(aes(color = study
      #               , group = interaction(study, mod_fac)
      #               , linetype = study)
      #           , size = 1) + 
      # geom_ribbon(aes(fill = study
      #                 , group = interaction(study, mod_fac)
      #                 , ymin = lower
      #                 , ymax = upper)
      #             , alpha = .25) + 
      stat_smooth(aes(weight = n, linetype = mod_fac, fill = mod_fac)
                  , method = "lm"
                  , formula = y~x
                  , size = 1
                  , color = "black"
                  ) +
      facet_wrap(~Trait, ncol = 2) + 
      scale_y_continuous(limits = c(4,10)
                         , breaks = c(4, 6, 8, 10)
                         , labels = c(4, 6, 8, 10)) +
      # scale_color_manual(values = cols) +
      scale_linetype_manual(values = lt) +
      labs(x = "Personality Score (POMP)"
           , y = "Cognition Score (POMP)"
           # , color = "Study"
           , fill = m
           , linetype = m
           , title = titl
           , subtitle = "Method 3: Two-Stage Individual Participant Meta-Analysis"
           )  +
      theme_classic() + 
      theme(legend.position = "bottom"
            , plot.title = element_text(face = "bold", size = rel(1.2), hjust = .5)
            , plot.subtitle = element_text(size = rel(1.1), hjust = .5)
            , strip.background = element_rect(fill = "black")
            , strip.text = element_text(face = "bold", color = "white")
            , axis.text = element_text(color = "black"))
  ggsave(file = sprintf("%s/results/3_ipd_meta/%s/figures/overall simple effects/%s_%s_%s.png", local_path, type, outcome, mod, cov), width = 6, height = 6)
}

nested_ipd3_meta %>%
  group_by(type, Outcome, Moderator, Covariate) %>%
  nest() %>%
  ungroup() %>%
  mutate(data = map(data, ~(.) %>% unnest(studyPred))
         , p = pmap(list(data, Outcome, Moderator, Covariate, type), ipd3_se_plot_fun))

5.4.4.2 Study-Specific Simple Effects Plots

ipd3_std_se_plot_fun <- function(d, outcome, trait, mod, cov, type){
  # print(paste(int, mod, cov, random, imp))
  o <- mapvalues(outcome, outcomes$short_name, outcomes$long_name, warn_missing = F)
  trt <- mapvalues(trait, traits$short_name, traits$long_name, warn_missing = F)
  cv <- mapvalues(cov, covars$short_name, covars$long_name, warn_missing = F)
  m <- mapvalues(mod, moders$short_name, moders$long_name, warn_missing = F)
  titl <- if(mod == "none"){sprintf("%s: %s", o, trt)} else {sprintf("%s: %s x %s Simple Effects", o, trt, m)}
  d <- d %>% mutate(study = mapvalues(study, c("BASE-I", "OCTO-TWIN"), c("BASE", "OCTO-Twin")),
           study = factor(study, levels = stdcolors$studies))
  std <- unique(d$study)
  cols <- (stdcolors %>% filter(studies %in% std))$colors
  lt <- (stdcolors %>% filter(studies %in% std))$lt
  d <- d %>% unclass %>% data.frame
  d$mod_value <- d[,mod]
  d <- d %>% select(-all_of(mod)) %>% as_tibble
  d <- if(class(d$mod_value) %in% c("factor", "character")){
    d %>% mutate(mod_fac = factor(mod_value))
    } else{
      d %>%
      full_join(
        d %>% select(study, mod_value) %>% distinct() %>% arrange(study, mod_value) %>%
          group_by(study) %>% mutate(mod_fac = factor(c("-1 SD", "M", "+1 SD"), levels = c("-1 SD", "M", "+1 SD"))) %>% ungroup()
        )
  }
  ht <- length(unique(d$mod_fac))
  p <- d %>% 
    ggplot(aes(x = p_value, y = pred)) + 
      geom_line(aes(color = study
                    , group = interaction(study, mod_fac)
                    , linetype = study)
                , size = 1) + 
      # geom_ribbon(aes(fill = study
      #                 , group = interaction(study, mod_fac)
      #                 , ymin = lower
      #                 , ymax = upper)
      #             , alpha = .25) + 
      stat_smooth(aes(weight = n)
                  , method = "lm"
                  , formula = y~x
                  , size = 1.2
                  , color = "darkslateblue"
                  ) +
      scale_y_continuous(limits = c(4,10)
                         , breaks = c(4, 6, 8, 10)
                         , labels = c(4, 6, 8, 10)) +
      scale_color_manual(values = cols) +
      scale_linetype_manual(values = lt) +
      labs(x = "Personality Score (POMP)"
           , y = "Cognition Score (POMP)"
           , color = "Study"
           , fill = "Study"
           , linetype = "Study"
           , title = titl
           , subtitle = "Method 3: Two-Stage Individual Participant Meta-Analysis"
           )  +
      facet_wrap(~mod_fac) + 
      theme_classic() + 
      theme(legend.position = "bottom"
            , plot.title = element_text(face = "bold", hjust = .5, size = rel(.95))
            , plot.subtitle = element_text(size = rel(1.1), hjust = .5)
            , strip.background = element_rect(fill = "black")
            , strip.text = element_text(face = "bold", color = "white")
            , axis.text = element_text(color = "black"))
  ggsave(file = sprintf("%s/results/3_ipd_meta/%s/figures/study specific simple effects/%s_%s_%s_%s.png", local_path, type, outcome, trt, mod, cov), width = 3*ht, height = 5)
}

nested_ipd3_meta %>%
  mutate(p = pmap(list(studyPred, Outcome, Trait, Moderator, Covariate, type), ipd3_std_se_plot_fun))
load("~/Documents/projects/data synthesis/crystallized/results/3_ipd_meta/Frequentist/metaModels/crystallized_C_country_all.RData")
coef(mt)
##          intrcpt metamodAustralia   metamodGermany    metamodSweden 
##       0.06433784      -0.06106685      -0.06322996      -0.18061653
cntrm <- rbind(
  c(1,0,0,0)
  , c(1,1,0,0)
  , c(1,0,1,0)
  , c(1,0,0,1)
); rownames(cntrm) <- c("United States", "Australia", "Germany", "Sweden")

(multcomp::glht(mt, cntrm) %>% # multcomp hypothesis function
      confint(., calpha = multcomp::univariate_calpha()))$confint %>%
      data.frame() %>% 
  data.frame() %>%
  rownames_to_column("cntr") %>%
  mutate(term = rownames(cntrm)) %>% 
  select(-cntr) %>%
  mutate(est = ifelse(abs(Estimate) < .01, sprintf("b = %.3f, 95%% CI [%.3f, %.3f]", Estimate, lwr, upr), sprintf("b = %.2f, 95%% CI [%.2f, %.2f]", Estimate, lwr, upr)))
##       Estimate         lwr         upr          term                               est
## 1  0.064337841  0.04167694  0.08699874 United States     b = 0.06, 95% CI [0.04, 0.09]
## 2  0.003270996 -0.02297616  0.02951815     Australia b = 0.003, 95% CI [-0.023, 0.030]
## 3  0.001107879 -0.05502738  0.05724314       Germany b = 0.001, 95% CI [-0.055, 0.057]
## 4 -0.116278686 -0.20058736 -0.03197002        Sweden  b = -0.12, 95% CI [-0.20, -0.03]
load("~/Documents/projects/data synthesis/crystallized/results/3_ipd_meta/Frequentist/metaModels/crystallized_C_scale_all.RData")
coef(mt)
##         intrcpt    metamodBFI-S  metamodEysenck metamodIPIP NEO     metamodMIDI   metamodTDA-40 
##      0.08889284     -0.08778496     -0.20517153     -0.12764963     -0.02316003     -0.08562185
cntrm <- rbind(
  c(1,0,0,0,0,0) # NEO-FFI
  , c(1,1,0,0,0,0) # BFI-S
  , c(1,0,1,0,0,0) # Eysenck
  , c(1,0,0,1,0,0) # IPIP NEO
  , c(1,0,0,0,1,0) # MIDI
  , c(1,0,0,0,0,1) # TDA-40
); rownames(cntrm) <- c("NEO-FFI", "BFI-S", "Eysenck", "IPIP NEO", "MIDI", "TDA-40")

(multcomp::glht(mt, cntrm) %>% # multcomp hypothesis function
      confint(., calpha = multcomp::univariate_calpha()))$confint %>%
      data.frame() %>% 
  data.frame() %>%
  rownames_to_column("cntr") %>%
  mutate(term = rownames(cntrm)) %>% 
  select(-cntr) %>%
  mutate(est = ifelse(abs(Estimate) < .01, sprintf("b = %.3f, 95%% CI [%.3f, %.3f]", Estimate, lwr, upr), sprintf("b = %.2f, 95%% CI [%.2f, %.2f]", Estimate, lwr, upr)))
##       Estimate         lwr         upr     term                               est
## 1  0.088892841  0.03486531  0.14292037  NEO-FFI     b = 0.09, 95% CI [0.03, 0.14]
## 2  0.001107879 -0.05488928  0.05710503    BFI-S b = 0.001, 95% CI [-0.055, 0.057]
## 3 -0.116278686 -0.20049547 -0.03206191  Eysenck  b = -0.12, 95% CI [-0.20, -0.03]
## 4 -0.038756793 -0.13781345  0.06029986 IPIP NEO   b = -0.04, 95% CI [-0.14, 0.06]
## 5  0.065732812  0.04024951  0.09121611     MIDI     b = 0.07, 95% CI [0.04, 0.09]
## 6  0.003270996 -0.02267949  0.02922148   TDA-40 b = 0.003, 95% CI [-0.023, 0.029]
load("~/Documents/projects/data synthesis/crystallized/results/3_ipd_meta/Frequentist/metaModels/crystallized_N_scale_all.RData")
coef(mt)
##         intrcpt    metamodBFI-S      metamodDPQ  metamodEysenck metamodIPIP NEO     metamodMIDI   metamodTDA-40 
##     -0.11303919      0.07722236      0.06822839      0.07197000      0.06484409      0.09060320     -0.00330891
cntrm <- rbind(
  c(1,0,0,0,0,0,0) # NEO-FFI
  , c(1,1,0,0,0,0,0) # BFI-S
  , c(1,0,1,0,0,0,0) # DPQ
  , c(1,0,0,1,0,0,0) # Eysenck
  , c(1,0,0,0,1,0,0) # IPIP NEO
  , c(1,0,0,0,0,1,0) # MIDI
  , c(1,0,0,0,0,0,1) # TDA-40
); rownames(cntrm) <- c("NEO-FFI", "BFI-S", "DPQ", "Eysenck", "IPIP NEO", "MIDI", "TDA-40")

(multcomp::glht(mt, cntrm) %>% # multcomp hypothesis function
      confint(., calpha = multcomp::univariate_calpha()))$confint %>%
      data.frame() %>% 
  data.frame() %>%
  rownames_to_column("cntr") %>%
  mutate(term = rownames(cntrm),
         sig = ifelse(sign(lwr) == sign(upr), "sig", "ns")) %>% 
  select(-cntr) %>%
  mutate(est = ifelse(abs(Estimate) < .01, sprintf("b = %.3f, 95%% CI [%.3f, %.3f]", Estimate, lwr, upr), sprintf("b = %.2f, 95%% CI [%.2f, %.2f]", Estimate, lwr, upr))) %>%
  arrange(sig, Estimate)
##      Estimate         lwr          upr     term sig                              est
## 1 -0.04819510 -0.14247152  0.046081326 IPIP NEO  ns  b = -0.05, 95% CI [-0.14, 0.05]
## 2 -0.04106919 -0.10615703  0.024018652  Eysenck  ns  b = -0.04, 95% CI [-0.11, 0.02]
## 3 -0.03581683 -0.08170762  0.010073957    BFI-S  ns  b = -0.04, 95% CI [-0.08, 0.01]
## 4 -0.11634810 -0.14179851 -0.090897687   TDA-40 sig b = -0.12, 95% CI [-0.14, -0.09]
## 5 -0.11303919 -0.15448612 -0.071592256  NEO-FFI sig b = -0.11, 95% CI [-0.15, -0.07]
## 6 -0.04481079 -0.08127859 -0.008343002      DPQ sig b = -0.04, 95% CI [-0.08, -0.01]
## 7 -0.02243598 -0.04344011 -0.001431855     MIDI sig b = -0.02, 95% CI [-0.04, -0.00]

5.4.5 Table Meta-Analytic Heterogeneity

Relative to previous tables, this is slightly complicated because Frequentist and Bayesian meta-analysis don’t use the same methods for estimating heterogeneity.

ipd3_het_tab <- nested_ipd3_meta %>% 
  select(-studyEff, -metaEff) %>%
  mutate(Trait = factor(Trait, traits$short_name, traits$long_name),
         Outcome = factor(Outcome, outcomes$short_name, outcomes$long_name)) %>%
  arrange(type, Outcome, Trait, Moderator) %>%
  group_by(type, Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(data = map(data, ~(.) %>% unnest(metaHet)))
ipd3_het_tab

## frequentist
ipd3_het_tab$data[[nrow(ipd3_het_tab)]]
## bayesian
ipd3_het_tab$data[[1]]
ipd3_het_tab_fun <- function(d, type, moder){
  rs <- d %>% group_by(Outcome) %>% tally() %>% 
    mutate(end = cumsum(n), start = lag(end) + 1, start = ifelse(is.na(start), 1, start))
  if(type == "Frequentist"){
    d %>%
      mutate_at(vars(tau2, QEp), ~ifelse(. < .001, "< 0.001", sprintf("%.3f", .))) %>%
      mutate_at(vars(I2:QE), ~sprintf("%.2f", .)) %>%
      select(Trait, tau2, I2, H2, QE, QEp) %>%
      kable(., "html"
            , escape = F
            , digits = 2
            , col.names = c("Trait", "$\\tau^2$", "$I^2$", "$H^2$", "<em>Q</em>", "<em>p</em>")
            , align = c("r", rep("c",5))
            , caption = sprintf("Table X. Heterogeneity estimates for %s Models with %s Moderator", type, moder)) %>%
      kable_classic(full_width = F, html_font = "Times New Roman")
  } else{
    d %>%
      mutate_at(vars(tau2, BF), ~ifelse(. < .001, "< 0.001", sprintf("%.3f", .))) %>%
      mutate_at(vars(I2, H2), ~sprintf("%.2f", .)) %>%
      select(Trait, tau2, I2, H2, BF) %>%
      kable(., "html"
            , escape = F
            , digits = 2
            , col.names = c("Trait", "$\\tau^2$", "$I^2$", "$H^2$", "BF")
            , align = c("r", rep("c",4))
            , caption = sprintf("Table X. Heterogeneity estimates for %s Models with %s Moderator", type, moder)) %>%
      kable_classic(full_width = F, html_font = "Times New Roman")
  }
}

5.5 Sample Results Section

We examined estimates of overall prospective associations between Big Five personality characteristics and crystallized abilities as well as participant and sample-level moderators of those associations using a two-stage individual participant meta-analysis of 11 studies. This allowed us to estimate both sample-specific and overall prospective associations between personality characteristics and cognitive ability as well as heterogeneity in those estimates. Fully adjusted Big Five personality trait-crystallized ability associations as well as participant- and sample-level moderators of these associations can be found in Table S5. Of the 20 key participant-level personality-crystallized ability associations and moderators of those associations, two (10%) were significant. Specifically, Openness was positively associated with later crystallized abilities, and gender moderated this association.

Table 5.1: Method 3 Pooled Regression Using Random Effects: Meta-Analytic Estimates of Fully Adjusted Personality-Crystallized Domain Associations
Extraversion
Agreeableness
Conscientiousness
Neuroticism
Openness to Experience
Term b [CI] b [CI] b [CI] b [CI] b [CI]
None
Personality 0.01
[-0.02, 0.05]
0.02
[-0.001, 0.03]
0.02
[-0.03, 0.07]
-0.07
[-0.10, -0.04]
0.17
[0.10, 0.25]
Age
Age -0.000
[-0.003, 0.002]
-0.001
[-0.003, 0.002]
-0.000
[-0.005, 0.004]
0.000
[-0.004, 0.005]
0.001
[-0.006, 0.007]
Gender
Gender (Male v Female) 0.04
[-0.004, 0.09]
0.02
[-0.02, 0.05]
0.005
[-0.03, 0.04]
-0.000
[-0.03, 0.03]
-0.04
[-0.07, -0.01]
Education
Education (Years) -0.004
[-0.02, 0.008]
-0.008
[-0.02, 0.008]
0.003
[-0.01, 0.02]
-0.001
[-0.01, 0.008]
-0.007
[-0.02, 0.005]
Continent
Continent (North America v Australia) 0.03
[-0.06, 0.12]
-0.03
[-0.15, 0.08]
-0.06
[-0.17, 0.06]
-0.05
[-0.13, 0.04]
0.06
[-0.21, 0.33]
Continent (North America v Europe) 0.06
[-0.02, 0.14]
-0.08
[-0.19, 0.03]
-0.11
[-0.21, -0.006]
0.02
[-0.04, 0.08]
0.08
[-0.13, 0.28]
Country
Country (United States v Australia) 0.03
[-0.05, 0.12]
-0.03
[-0.11, 0.06]
-0.06
[-0.10, -0.03]
-0.04
[-0.14, 0.06]
0.06
[-0.22, 0.34]
Country (United States v Germany) 0.09
[0.002, 0.18]
-0.03
[-0.12, 0.07]
-0.06
[-0.12, -0.003]
0.02
[-0.08, 0.12]
0.04
[-0.20, 0.28]
Country (United States v Sweden) 0.03
[-0.06, 0.13]
-0.15
[-0.28, -0.02]
-0.18
[-0.27, -0.09]
0.03
[-0.07, 0.13]
0.15
[-0.16, 0.46]
Country (United States v The Netherlands) 0.03
[-0.08, 0.14]
Personality Scale
Scale (NEO-FFI v Eysenck) 0.02
[-0.07, 0.12]
-0.21
[-0.31, -0.11]
0.07
[-0.005, 0.15]
0.07
[-0.37, 0.51]
Scale (NEO-FFI v BFI-S) 0.10
[0.01, 0.18]
-0.09
[-0.17, -0.010]
0.08
[0.02, 0.14]
-0.14
[-0.57, 0.28]
Scale (NEO-FFI v DPQ) 0.07
[0.01, 0.12]
Scale (NEO-FFI v IPIP NEO) 0.08
[-0.04, 0.20]
-0.13
[-0.24, -0.01]
0.06
[-0.04, 0.17]
0.04
[-0.39, 0.48]
Scale (NEO-FFI v MIDI) -0.04
[-0.11, 0.03]
-0.02
[-0.08, 0.04]
0.09
[0.04, 0.14]
-0.13
[-0.55, 0.29]
Scale (NEO-FFI v TDA-40) 0.03
[-0.05, 0.10]
-0.09
[-0.15, -0.03]
-0.003
[-0.05, 0.05]
-0.01
[-0.43, 0.41]
Baseline Age
Study Baseline Age -0.001
[-0.003, 0.002]
0.002
[-0.001, 0.004]
0.002
[-0.001, 0.006]
-0.000
[-0.003, 0.002]
0.001
[-0.005, 0.007]
Baseline Year
Study Baseline Year 0.001
[-0.004, 0.006]
0.005
[0.001, 0.009]
0.004
[-0.003, 0.01]
-0.000
[-0.005, 0.004]
-0.006
[-0.01, 0.003]
Prediction Interval
Prediction Interval -0.003
[-0.01, 0.009]
-0.007
[-0.02, 0.001]
-0.008
[-0.02, 0.006]
0.001
[-0.010, 0.01]
0.007
[-0.01, 0.03]

To better understand the moderation, we examined fully adjusted simple effects plots for tests of moderators of personality-cognitive ability associations in Figure S7. Although the figure demonstrates the simple effects for all of the Big Five, gender only significantly moderated openness to experience (b = -0.04, 95% CI [-0.07, -0.01]). In addition, the overall association between openness-crystallized ability association was positive for males and females. However, the interaction indicates that females had a significantly lower association than males.

Figure S7. Prospective sample-specific and overall associations between extraversion (in POMP units, 0-10) and crystallized abilities (in POMP units, 0-10) across genders (male, female). Different colors and line types indicate different samples. Thicker, black lines indicate the average association across samples, while thinner lines indicate sample-specific associations.

Figure 5.1: Figure S7. Prospective sample-specific and overall associations between extraversion (in POMP units, 0-10) and crystallized abilities (in POMP units, 0-10) across genders (male, female). Different colors and line types indicate different samples. Thicker, black lines indicate the average association across samples, while thinner lines indicate sample-specific associations.

Finally, we examined the consistency of the prospective associations across samples for both personality-cognitive ability associations and the simple effects across samples for each moderator. Figure S8 presents the forest plots of the fully adjusted sample-specific and meta-analytic estimates of Big Five personality characteristic-crystallized ability associations across samples. These associations were very consistent for openness, with all samples except for ROS demonstrating a positive association between openness and crystallized abilities. Other estimates were less consistent, with three samples (HRS, MAP, and ROS) showing a positive association between conscientiousness and crystallized abilities, one sample (SATSA) showing a negative association, and two samples (HILDA, GSOEP) showing a null association.

Figure S8. Forest plot of fully adjusted prospective associations between Big Five personality characteristics and crystallized abilities across samples for using one-stage pooled integrative data analysis via effects coded contrasts. Overall point estimates (squares) represent the grand-mean estimates of the association across samples, while sample point estimates represent regression terms or linear combinations of regression terms. Error bars capture the 95% CI around the point estimate. Arrows indicate the confidence band was truncated to better visualize the estimates.

Figure 5.2: Figure S8. Forest plot of fully adjusted prospective associations between Big Five personality characteristics and crystallized abilities across samples for using one-stage pooled integrative data analysis via effects coded contrasts. Overall point estimates (squares) represent the grand-mean estimates of the association across samples, while sample point estimates represent regression terms or linear combinations of regression terms. Error bars capture the 95% CI around the point estimate. Arrows indicate the confidence band was truncated to better visualize the estimates.

(Although gender did not moderate the association between extraversion and crystallized abilities in Method 3, for consistency with other methods, we include interpretation here.) Next, we examined the consistency of moderator associations across samples. Figure S7 displays the overall fully adjusted predicted crystallized abilities levels at different levels of extraversion across samples for those males and females for comparison to other methods. As is clear in the Figure, the overall trend suggests a null association for women and slight negative association for men, such that men who were higher in extraversion tended to score slightly worse on crystallized domain tasks. HRS (b = 0.04, 95% CI [-0.07, -0.02]) showed a negative association between extraversion and crystallized abilities for women, HILDA (b = 0.05, 95% CI [0.02, 0.08]) and GSOEP (b = 0.10, 95% CI [0.03, 0.17]) showed positive associations, and all other samples showed null associations. For men, HRS (b = -0.05, 95% CI [-0.08, -0.02]) and SATSA (b = -0.17, 95% CI [-0.30, -0.03]) demonstrated a negative association, only GSOEP demonstrated a positive association (b = 0.08, 95% CI [0.01, 0.14]), and all other samples demonstrated a null effect.

Among sample-level moderators, 15 of the 62 (24.19%) tested sample-level moderators were significant (fully adjusted). The most notable of these were country-level differences in the association between Conscientiousness and crystallized / knowledge domain cognitive ability and differences in the associations between Neuroticism and Conscientiousness’s associations with cognitive ability across different personality scales. Specifically, although there was a positive association between Conscientiousness and crystallized / knowledge domain cognitive ability in the United States (b = 0.06, 95% CI [0.04, 0.09]), that association was null (Germany, b = 0.001, 95% CI [-0.055, 0.057]; Australia, b = 0.003, 95% CI [-0.023, 0.030]) or negative (Sweden, b = -0.12, 95% CI [-0.20, -0.03]). Similarly, the association for Conscientiousness was positive when using the NEO-FFI (b = 0.09, 95% CI [0.03, 0.14]) and MIDI (b = 0.07, 95% CI [0.04, 0.09, negative when using the measures in SATSA and OCTO-TWIN (b = -0.12, 95% CI [-0.20, -0.03]). and null when using IPIP NEO (b = -0.04, 95% CI [-0.14, 0.06]), BFI-S (b = 0.001, 95% CI [-0.06, 0.06]), and TDA-40 (b = 0.003, 95% CI [-0.023, 0.029]). For Neuroticism, the association was negative for the NEO-FFI (b = -0.11, 95% CI [-0.15, -0.07]), MIDI (b = -0.02, 95% CI [-0.04, -0.00]), DPQ (b = -0.04, 95% CI [-0.08, -0.01]), and TDA-40 (b = -0.12, 95% CI [-0.14, -0.09]), and null for the IPIP NEO (b = -0.05, 95% CI [-0.14, 0.05]), BFI-S (b = -0.04, 95% CI [-0.08, 0.01]), and the measure used in SATSA and OCTO-TWIN (b = -0.04, 95% CI [-0.11, 0.02]). Simple effects plots for all sample-level moderators can be found in the online materials and web app.

rm(list = ls()[grepl("ipd3", ls())])