Chapter 4 PSM Models {#PSM Mods}

Now that the propensity score matching procedure and balance has run, we can set up the final data sets, run the models, recompile across imputations, and create the tables and figures.

4.1 Part 5: Data Setup

4.1.3 Outcomes

4.1.3.1 Matched

factor_fun <- function(x){if(is.numeric(x)){diff(range(x, na.rm = T)) %in% 1:2} else{F}}

read_fun <- function(file){
  load(sprintf("%s/data/psm_matched/%s", wd, file))
  d %>% select(SID, o_value) %>% as_tibble() %>%
    mutate(o_value = as.numeric(as.character(o_value)),
           o_value = ifelse(o_value > 1, 1, o_value))
}  

merge_fun <- function(df3, Study, df){
  df %>% filter(study == Study) %>% select(-year) %>%
    full_join(df3) %>% select(-study) %>%
    filter(!is.na(o_value) & !is.na(p_value)) 
}

combine_fun <- function(df2, outcome, mod, trait, df){
  # plan(multisession)
  df_l <- df2 %>% 
    mutate(data = map(file, read_fun),
           data = pmap(list(df3 = data, Study = study, df = list(df)), merge_fun)) %>%
    select(-file, -year) %>%
    group_by(imp) %>%
    nest() %>%
    ungroup() %>%
    mutate(data = map(data, ~(.) %>% unnest(data)),
           data = map(data, clean_fun))
  df_l <- df_l$data
  save(df_l, file = sprintf("%s/data/psm_combined/%s_%s_%s.RData", wd, outcome, trait, mod))
  rm(list = c("df2", "df_l"))
  gc()
}

clean_fun <- function(df4){
  df4 <- df4 %>% 
    mutate_if(factor_fun, as.factor) %>%
    mutate(o_value = as.numeric(as.character(o_value))) %>%
    group_by(study) %>%
    mutate_at(vars(p_value, parOccPrstg, grsWages), 
            ~((. - min(., na.rm = T))/(max(., na.rm = T) - min(., na.rm = T))*10)) %>%
    mutate(age = scale(age, center = T, scale = F)) %>%
    ungroup() %>%
    mutate_if(is.numeric, ~ifelse(is.infinite(.), NA, .)) 
  
  std <- df4 %>%
        group_by(study, o_value) %>%
        tally() %>%
        full_join(crossing(study = unique(.$study), o_value = c(0,1)))
  std <- unique(std$study[std$n < 8 | is.na(std$n)])
  df4 <- df4 %>%
    filter(!study %in% std) 
}


out_data_fun <- function(trait, df){
  print(trait)
  plan(multisession(workers = 6L))
  df1 <- df %>% select(study, p_year = year) %>% distinct() %>%
    mutate(file = map(study, ~list.files(sprintf("%s/data/psm_matched", wd), pattern = .))) %>%
    select(-study) %>%
    unnest(file) %>%
    separate(file, c("study","Trait", "Outcome", "year", "imp", "Moderator"), sep = "_", remove = F) %>%
    group_by(study, Trait, Outcome, year, Moderator) %>%
    filter(n() == 5) %>%
    ungroup() %>%
    filter(!is.na(Moderator)) %>%
    filter(Outcome == "retired") %>%
    filter(p_year == year & Trait == trait) %>% select(-p_year) %>%
    mutate(Moderator = str_remove_all(Moderator, ".RData")) %>%
    group_by(Moderator, Outcome, Trait) %>%
    nest() %>%
    ungroup() %>%
    mutate(data = future_pmap(
      list(df2 = data, outcome = Outcome, mod = Moderator, trait = Trait, df = list(df))
      , combine_fun
      , .options = future_options(
        globals = c("clean_fun", "combine_fun", "factor_fun",
                    "loadRData", "merge_fun", "read_fun","wd")
        , packages = c("plyr", "tidyverse"))))
  closeAllConnections()
  return(T)
}
# plan(multisession(workers = 5L))
psm_data <- psm_pers %>%
  full_join(mod_data) %>%
  filter(!is.na(p_value) & !is.na(Trait)) %>%
  group_by(Trait) %>%
  nest() %>%
  ungroup() %>%
  filter(Trait == "LOC") %>%
  mutate(data = map2(Trait, data, out_data_fun))
closeAllConnections()

4.2 Part 6: Models

4.2.1 Matched Model Function

brms_matched_fun <- function(outcome, trait, mod){
  # load data 
  m <- if(mod == "SES") c("parEdu", "grsWages", "parOccPrstg")  else mod
  d <- if(mod %in% c("reliability", "predInt")){"none"} else mod
  load(sprintf("%s/data/psm_combined/%s_%s_%s.RData", wd, outcome, trait, d))
  
  df_l <- map(df_l, ~(.) %>% select(study, SID, p_value, o_value, one_of(d)) %>%
                filter(complete.cases(.)))
  
  # set priors
  Prior <-  c(set_prior("cauchy(0,1)", class = "sd"))
  
  # formula 
  if(mod == "none"){f <- formula(o_value ~ p_value + (p_value | study))}
  else if(mod %in% c("reliability", "predInt")){f <- formula(paste("o_value ~ p_value + ", paste("p_value*", m, collapse = " + "), " + (p_value | study)", sep = ""))}
  else {f <- formula(paste("o_value ~ p_value + ", paste("p_value*", m, collapse = " + "), "+ (", paste("p_value*", m, collapse = " + "), " | study)", sep = ""))}
  # run the model 
  Iter <- 2000; Warmup <- 1000; treedepth <- 20
  
  start.tmp <- Sys.time()
  fit2 <- brm_multiple(formula = f
             , data = df_l
             , prior = Prior
             , iter = Iter
             , warmup = Warmup
             , family = binomial(link = "logit")
             , control = list(adapt_delta = 0.99, max_treedepth = treedepth)
             , cores = 4)
  print(end.tmp <- Sys.time() - start.tmp)
  
  # extract key parameters
  # fixed effects
  fx <- fixef(fit, probs = c(0.055, 0.945)) %>% data.frame %>% 
    rownames_to_column("names") %>%
    mutate_at(vars(-names), lst(OR = inv_logit_scaled)) %>%
    as_tibble 
  # random effects
  rx <- ranef(fit, probs = c(0.055, 0.945))[[1]] %>% array_tree(3) %>% 
    tibble(names = names(.), data = .) %>% 
    mutate(data = map(data, ~(.) %>% data.frame %>% 
      rownames_to_column("study"))) %>% 
    unnest(data) %>%
    mutate_at(vars(-names, -study), lst(OR = inv_logit_scaled)) %>%
    as_tibble
  
  
  
  # samples
  fx.draws <- fit %>% tidy_draws() %>% 
    select(.chain:.draw, matches("^b_"), matches("p_value]$")) %>%
    mutate_at(vars(matches("p_value]$")), ~(.) + b_p_value) %>%
    gather(key = item, value = s_value, -(.chain:.draw))
  
  tau.draws <- fit %>% tidy_draws() %>% 
    select(.chain:.draw, matches("^sd"), matches("^cor"))
  
  if(mod != "none"){
    pred.fx <- fx_pred_fun(fit, m)
    pred.rx <- rx_pred_fun(fit, m)
    save(pred.fx, pred.rx, file = sprintf("/scratch/edbeck/psm/predicted/pred_%s_%s_%s", trait, outcome, mod))
  }
  
  save(fit, file = sprintf("/scratch/edbeck/psm/models/matched_%s_%s_%s", trait, outcome, mod))
  save(fx, rx, file = sprintf("/scratch/edbeck/psm/summary/matched_%s_%s_%s", trait, outcome, mod))
  save(fx.draws, tau.draws, file = sprintf("/scratch/edbeck/psm/draws/matched_%s_%s_%s", trait, outcome, mod))
  rm(list("fit", "fx", "rx", "fx.draws", "rx.draws", "df"))
  gc()
}

4.3 Part 7: Compile Cluster Jobs

4.3.1 Moderator Predicted Values

Below are functions to get predicted values of moderated personality-outcome associations for both fixed effects and random effects.

# fixed effect prediction function
fx_pred_fun <- function(fits, m){
  x <- lapply(m, function(x) {
  cl <- class(fits$data[, x])

  if(cl %in% c("numeric", "integer", "matrix")){
  msd <- fits$data %>% 
    select(one_of(x)) %>% 
    as_tibble() %>%
    summarize_all(lst(mean, sd))
  
  pred.fx <- crossing(
    p_value = seq(0,10,.25)
    , mod_value = with(msd, c(mean-sd, mean,mean+sd))
    ) %>%
    setNames(c("p_value", x))
  } else {
    pred.fx <- crossing(
      p_value = seq(0,10,.25)
      , mod_value = factor(levels(fits$data[, x]))
    ) %>%
    setNames(c("p_value", x))
  }
  })
  
  pred.fx <- if(length(x) > 1){x %>% reduce(full_join)} else {x[[1]]}
    
  pred.fx <- bind_cols(
    pred.fx, 
    fitted(fits
           , newdata = pred.fx
           , probs = c(0.055, 0.945)
           , re_formula = NA
           , scale = "linear") %>% data.frame
    ) %>%
    mutate_at(vars(Estimate, Q5.5, Q94.5), lst(OR = exp))
}

# short function  to get crossings of continuous personality and moderator levels
crossing_fun <- function(df, mod){
  pred.rx <- crossing(
    p_value = seq(0,10,.25),
    mod_value = with(df, c(mean-sd, mean,mean+sd))
    ) %>%
    setNames(c("p_value", mod))
  return(pred.rx)
}

# function to get predicted values across random effects (studies)
rx_pred_fun <- function(fits, m){
  studies <- unique(fits$data$study)
  x <- lapply(m, function(x) {
   cl <- class(fits$data[, x])
  
  if(cl %in% c("numeric", "integer", "matrix")){
    pred.rx <- fits$data %>% select(study, one_of(x)) %>% 
      as_tibble() %>%
      group_by(study) %>%
      summarize_all(lst(mean, sd)) %>%
      group_by(study) %>%
      nest() %>%
      ungroup() %>%
      mutate(pred = map2(data, x, crossing_fun)) %>%
      unnest(pred) %>%
      select(-data)
  } else {
    pred.rx <- crossing(
      study = studies
      , p_value = seq(0,10,.25)
      , mod_value = factor(levels(fits$data[, x]))
    ) %>%
      setNames(c("study", "p_value", x))
  }
  })
  
  pred.rx <- if(length(x) > 1){x %>% reduce(full_join)} else {x[[1]]}
  
  pred.rx <- bind_cols(
    pred.rx, 
    fitted(fits
           , newdata = pred.rx
           , probs = c(0.055, 0.945)
           , scale = "linear"
           ) %>% data.frame
    ) %>%
    mutate_at(vars(Estimate, Q5.5, Q94.5), lst(OR = exp))
  return(pred.rx)
}

4.3.2 Compilation and Summary Function

Now that we have functions to get predicted values, the function below will load in each of the 5 imputations for a given trait-outcome-moderator combination, get fixed and random effects, Level 2 variances, predicted values (moderator models only), and fixed and random posterior draws.

combine_fun <- function(trait, outcome, mod){
  # get short moderator names
  m <- if(mod == "SES") c("parEdu", "grsWages", "parOccPrstg")  else mod
  d <- if(mod %in% c("reliability", "predInt")){"none"} else mod
  # load in each of the models and change their class to both brmsfit and brmsfit_multiple
  fits <- map(1:5, function(x){
    load(sprintf("%s/results/psm/matched/models/matched_%s_%s_%s_%s.RData", wd, trait, outcome, mod, x))
    class(fit) <- c("brmsfit_multiple", class(fit))
    return(fit)})
  
  # combine models #
  # get the rhat values
  rhats <- map_df(fits, function(x)data.frame(as.list(rhat(x))))
  # combined the models
  fits <- combine_models(mlist = fits, check_data = FALSE)
  # change the data name
  fits$data.name <- "df_l"
  # add in pooled rhats
  fits$rhats <- rhats
  # change the class
  class(fits) <- c("brmsfit_multiple", class(fits))
  
  # extract key parameters
  # fixed effects
  fx <- fixef(fits, probs = c(0.055, 0.945)) %>% data.frame %>%
    rownames_to_column("names") %>%
    mutate_at(vars(-names), lst(OR = exp)) %>%
    as_tibble()
  # random effects
  rx <- coef(fits, probs = c(0.055, 0.945))[[1]] %>% array_tree(3) %>%
    tibble(names = names(.), data = .) %>%
    mutate(data = map(data, ~(.) %>% data.frame %>%
                        rownames_to_column("study"))) %>%
    unnest(data) %>%
    mutate_at(vars(-names, -study), lst(OR = exp)) %>%
    as_tibble()
  
  # variance covariance matrix
  vc <- VarCorr(fits, probs = c(0.055, 0.945))$study

  # samples
  # fixed 
  fx.draws <- fits %>% tidy_draws() %>%
    select(.chain:.draw, matches("^b_"), matches("p_value]$")) %>%
    mutate_at(vars(matches("p_value]$")), ~(.) + b_p_value) %>%
    gather(key = item, value = s_value, -(.chain:.draw))
  # random
  tau.draws <- fits %>% tidy_draws() %>%
    select(.chain:.draw, matches("^sd"), matches("^cor"))

  # if it's a moderator model, get and save predicted values
  if(mod != "none"){
    pred.fx <- fx_pred_fun(fits, m)
    pred.rx <- rx_pred_fun(fits, m)
    save(pred.fx, pred.rx, file = sprintf("%s/results/psm/matched/predicted/matched_pred_%s_%s_%s.RData", wd, trait, outcome, mod))
    rm(list = c("pred.fx", "pred.rx"))
  }
# save variance covariance matrix, fixed and random summaries, 
# and posterior draws to folders
  save(vc, file = sprintf("%s/results/psm/matched/vc/matched_%s_%s_%s.RData", wd, trait, outcome, mod))
  save(fx, rx, file = sprintf("%s/results/psm/matched/summary/matched_%s_%s_%s.RData", wd, trait, outcome, mod))
  save(fx.draws, tau.draws, file = sprintf("%s/results/psm/matched/draws/matched_%s_%s_%s.RData", wd, trait, outcome, mod))
  # remove objects from local enviornment and clean up the garbage
  rm(list = c("fits", "fx", "rx", "fx.draws", "tau.draws"))
  gc()
}

4.4 Part 8: Tables and Plots

4.4.1 Summaries

## 37.14% of the tested unmoderated, matched personality-outcome associations were significant.
## 8.21% of personality-outcome associations had reliable moderators effects.
## 15.71% of the matched personality-outcome associations were significantly moderated by age.
## 14.29% of the matched personality-outcome associations were significantly moderated by gender
## 0% of the matched personality-outcome associations were significantly moderated by race (black v white)
## 1.43% of the matched personality-outcome associations were significantly moderated by race (other v white)
## 10% of the matched personality-outcome associations were significantly moderated by parental education (SES; college v HS or lower)
## 8.57% of the matched personality-outcome associations were significantly moderated by parental education (SES; beyond college v HS or lower)
## 4.29% of the matched personality-outcome associations were significantly moderated by parental occupational prestige (SES)
## 8.57% of the matched personality-outcome associations were significantly moderated by gross wages (SES)
## 0% of the matched personality-outcome associations were significantly moderated by personality scale reliability
## 5.71% of the matched personality-outcome associations were significantly moderated by the interval between personality and outcome measures
## Conscientiousness was the most frequently moderated personality characteristic (30%), while Neuroticism was the least frequently moderated trait (15%).
## Child Birth was the most frequently moderated outcome (20%), while Higher Ed was the least frequently moderated outcome (2.5%).

4.4.1.1 Plots

4.4.1.1.1 Study-Specific Forest Plots

The function below creates a plot for each outcome-moderator combination of mega-analytic and study-specific associations across traits and saves them to a folder.

plot_fun <- function(df, outcome, mod){
  print(paste(outcome, mod))
  o <- mapvalues(outcome, outcomes$short_name, outcomes$long_name, warn_missing = F)
  m <- mapvalues(mod, moderators$mod, moderators$mod_name, warn_missing = F)
  d <- round(max(abs(1-min(df$Estimate_OR)), abs(1-max(df$Estimate_OR))), 2)
  lim <- if(mod == "none"){c(.68, 1.32)} else{c(1-d-(d/2.5), 1+d+(d/2.5))}
  brk <- if(mod == "none"){seq(.75, 1.25, .25)} else{round(c(1-d-(d/5), 1, 1+d+(d/5)),2)}
  lab <- if(mod == "none"){c(".75", "1", "1.25")} else{str_remove(round(c(1-d-(d/5), 1, 1+d+(d/5)),2), "^0")}
  shapes <- c(15, 16, 17, 18)[1:length(unique(df$names))]
  lt <- rep("solid", length(unique(df$names)))
  titl <- if(mod == "none"){o} else {sprintf("%s: Personality x %s", o, m)}
  leg <- if(length(unique(df$names)) > 1){"bottom"} else {"none"}
  p <- df %>%
    mutate(Q5.5_OR = ifelse(Q5.5_OR < lim[1], lim[1], Q5.5_OR),
           Q94.5_OR = ifelse(Q94.5_OR > lim[2], lim[2], Q94.5_OR),
           study = factor(study, levels = c("Overall", studies_long)),
           Trait = factor(Trait, levels = traits$short_name, labels = traits$long_name)) %>%
    ggplot(aes(x = study, y = Estimate_OR)) +
      scale_y_continuous(limits = lim, breaks = brk, labels = lab) +
      scale_size_manual(values = c(2.5, 1.5)) +
      scale_shape_manual(values = shapes) + 
      scale_color_manual(values = c("blue", "black")) +
      scale_linetype_manual(values = lt) +
      geom_hline(aes(yintercept = 1), linetype = "dashed") +
      geom_errorbar(aes(ymin = Q5.5_OR, ymax = Q94.5_OR, linetype = names)
                    , width = 0
                    , position = position_dodge(width = .9)) + 
      geom_point(aes(color = type, size = type, shape = names)
                    , position = position_dodge(width = .9)) +
      labs(x = NULL, y = "Odds Ratio", title = titl) +
      facet_wrap(~Trait, scales = "free_y", nrow = 3) +
      coord_flip() +
      theme_classic() +
      theme(legend.position = leg,
            plot.title = element_text(face = "bold", size = rel(1.2), hjust = .5),
            strip.background = element_rect(fill = "black"),
            strip.text = element_text(face = "bold", color = "white"),
            axis.text = element_text(color = "black"))
  ggsave(p, file = sprintf("%s/results/psm/matched/plots/study specific forest/%s/matched_%s.pdf", res_path, mod, outcome), width = 6, height = 6)
  save(p, file = sprintf("%s/results/psm/matched/plots/study specific forest/rdata/matched_%s_%s.RData", res_path, outcome, mod))
  rm(p)
  gc()
  return(T)
}

# keep only personality-outcome associations in the unmoderated
# models or the moderator association in the moderated models
cmb_res <- fx_res %>% 
  filter((Moderator == "none" & names == "p_value")|
         (Moderator != "none" & grepl("p_value:", names)) &
         (!Moderator %in% c("reliability", "predInt"))) %>%
  mutate(study = "Overall", type = "fixed") %>%
  full_join(
    rx_res %>% 
      filter((Moderator == "none" & names == "p_value")|
             (Moderator != "none" & grepl("p_value:", names)) &
         (!Moderator %in% c("reliability", "predInt"))) %>% 
      mutate(type = "random")
  ) 

# run the plot function
cmb_res %>%
  group_by(Outcome, Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(pmap(list(data, Outcome, Moderator), plot_fun))
4.4.1.1.2 Fixed-Effect Forest Plots

Now that we have plots of both mega-analytic and study-specific effects, we’ll plot just the mega-analytic effecs for each moderator for parsimony (i.e. each of the 196 personality-outcome associations or moderators of them in a single graph).

fx_plot_fun <- function(df, mod){
  m <- mapvalues(mod, moderators$short_name, moderators$long_name, warn_missing = F)
  d <- round(max(abs(1-min(df$Estimate_OR)), abs(1-max(df$Estimate_OR))), 2)
  lim <- if(mod == "none"){c(.8, 1.2)} else{c(1-d-(d/2.5), 1+d+(d/2.5))}
  brk <- if(mod == "none"){seq(.85, 1.15, .15)} else{round(c(1-d-(d/5), 1, 1+d+(d/5)),2)}
  lab <- if(mod == "none"){c(".85", "1", "1.15")} else{str_remove(round(c(1-d-(d/5), 1, 1+d+(d/5)),2), "^0")}
  shapes <- c(15, 16, 17, 18)[1:length(unique(df$names))]
  lt <- rep("solid", length(unique(df$names)))
  titl <- if(mod == "none"){NULL} else {sprintf("%s Moderation of Personality-Outcome Associations", m)}
  leg <- if(length(unique(df$names)) > 1){"bottom"} else {"none"}
  p <- df %>%
    mutate(Q5.5_OR = ifelse(Q5.5_OR < lim[1], lim[1], Q5.5_OR),
           Q94.5_OR = ifelse(Q94.5_OR > lim[2], lim[2], Q94.5_OR)) %>% 
  ggplot(aes(x = Outcome, y = Estimate_OR)) +
    scale_y_continuous(limits = lim, breaks = brk, labels = lab) + 
    scale_size_manual(values = c(1.6, 1.2)) +
    scale_shape_manual(values = shapes) +
    scale_color_manual(values = c("blue", "black")) +
    scale_linetype_manual(values = lt) +
    geom_hline(aes(yintercept = 1), size = .25, color = "gray50") +
    geom_errorbar(aes(ymin = Q5.5_OR, ymax = Q94.5_OR, linetype = names)
                  , width = 0
                  , position = position_dodge(width = .9)) + 
    geom_point(aes(color = sig, size = sig, shape = names)
                  , position = position_dodge(width = .9)) +
    labs(x = NULL, y = "Odds Ratio", title = titl) +
    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),
          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)))
ggsave(file = sprintf("%s/results/psm/matched/plots/overall forest/%s_fixed.png", res_path, mod), width = 7, height = 6)
save(p, file = sprintf("%s/results/psm/matched/plots/overall forest/rdata/%s_fixed.RData", res_path, mod))
rm(p)
gc()
return(T)
}

fx_res %>% 
    filter((Moderator == "none" & names == "p_value")|
         (Moderator != "none" & grepl("p_value:", names))) %>%
    mutate(sig = ifelse(sign(Q5.5) == sign(Q94.5), "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)),
           Outcome = forcats::fct_rev(Outcome))  %>%
  group_by(Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(map2(data, Moderator, fx_plot_fun))

4.4.1.2 Tables

4.4.1.2.1 Study-Specific

First, I’ll create tables of personality-outcome associations for each moderator (including unmoderated results) and create a table with both the mega-analytic and study-specific associations across traits and outcomes.

psm_tab_fun <- function(d, mod){
  t_cols <- rep(1, 5); names(t_cols) <- traits$long_name
  m <- mapvalues(mod, moderators$short_name, moderators$long_name, warn_missing = F)
  # restructure data for tabling
  tab <- d %>%
    # format values rounded to 2 decimal places and bolded if significant
    mutate_at(vars(Estimate_OR:Q94.5_OR), ~sprintf("%.2f", .)) %>%
    mutate(est = sprintf("%s [%s, %s]", Estimate_OR, Q5.5_OR, Q94.5_OR),
           est = ifelse(sig == "sig", sprintf("<strong>%s</strong>", est), est)) %>%
    select(Trait, Outcome, study, names, est) %>%
    # spread it wide and order Outcomes, columns, and studies
    pivot_wider(names_from = "Trait" , values_from = "est", names_sep = "_") %>%
    left_join(crossing(Outcome = outcomes$short_name, Trait = traits$short_name, study = "Overall", value  = NA_character_, names = unique(d$names)) %>% spread(Trait, value)) %>%
    mutate(Outcome = factor(Outcome, levels = outcomes$short_name, outcomes$long_name),
           study = factor(study, levels = c("Overall", studies_long))) %>%
    arrange(Outcome, study) 
  # a little bit of housekeeping so moderators play nicely together
  mod_old <- unique(d$names)
  mod_num <- length(mod_old)
  if(mod_num == 1){
    keep <- c("study", traits$short_name)
    cols <- c("Study", rep("OR [CI]", times = 5))
    t_cols <- c(" " = 1, t_cols)
    algn <- c("r", rep("c", 5))
  } else {
    keep <- c("study", "names", traits$short_name)
    cols <- c("Study", "Moderator", rep("OR [CI]", times = 5))
    mod_new <- str_replace(str_remove(mod_old, "p_value:"), mod, paste(m, " ", sep =""))
    tab <- tab %>% mutate(names = mapvalues(names, mod_old, mod_new))
    t_cols <- c(" " = 2, t_cols)
    algn <- c("r", "r", rep("c", 5))
  }
  cap <- if(mod == "none") "Mega-Analytic and Study-Specific Personality-Outcome Associations in Matched Samples" else sprintf("Mega-Analytic and Study-Specific %s Moderation of Personality-Outcome Associations in Matched Samples", m)
  
 rs <- tab %>% group_by(Outcome) %>% tally() %>% 
    mutate(end = cumsum(n), start = lag(end) + 1, start = ifelse(is.na(start), 1, start))
  
  tab %>%
    select(one_of(keep)) %>% 
    kable(.
          , "html"
          , escape = F
          , col.names = cols
          , align = algn
          , caption = cap) %>%
    kable_styling(full_width = F) %>% 
    add_header_above(t_cols) %>%
    collapse_rows(1, valign = "top") %>%
    kableExtra::group_rows("Mortality", rs$start[1], rs$end[1], label_row_css = NULL) %>%
    kableExtra::group_rows("Major Health Event", rs$start[2], rs$end[2], label_row_css = NULL) %>%
    kableExtra::group_rows("Mental Health Event", rs$start[3], rs$end[3], label_row_css = NULL) %>%
    kableExtra::group_rows("Child Birth",rs$start[4], rs$end[4], label_row_css = NULL) %>%
    kableExtra::group_rows("Move in with a partner", rs$start[5], rs$end[5], label_row_css = NULL) %>%
    kableExtra::group_rows("Marriage", rs$start[6], rs$end[6], label_row_css = NULL) %>%
    kableExtra::group_rows("Divorce", rs$start[7], rs$end[7], label_row_css = NULL) %>%
    kableExtra::group_rows("Child Moves Out", rs$start[8], rs$end[8], label_row_css = NULL) %>%
    kableExtra::group_rows("Higher Education", rs$start[9], rs$end[9], label_row_css = NULL)  %>%
    kableExtra::group_rows("First Job", rs$start[10], rs$end[10], label_row_css = NULL) %>%
    kableExtra::group_rows("Unemployment", rs$start[11], rs$end[11], label_row_css = NULL) %>%
    kableExtra::group_rows("Retirement", rs$start[12], rs$end[12], label_row_css = NULL) %>%
    kableExtra::group_rows("Volunteering", rs$start[13], rs$end[13], label_row_css = NULL) %>%
    kableExtra::group_rows("Criminality", rs$start[14], rs$end[14], label_row_css = NULL) %>%
    footnote("<em>OR</em> = Odds Ratio; <em>UI</em> = 89% Bayesian Uncertainty Interval. Bold indicates model terms whose 89% UI of log odds did not overlap with 0.", escape = F)  %>%
    save_kable(., file = sprintf("%s/results/psm/matched/tables/study specific/%s.html", res_path, mod))
}

cmb_res %>%
  mutate(sig = ifelse(sign(Q5.5) == sign(Q94.5), "sig", "ns")) %>%
  select(-(Estimate:Q94.5)) %>%
  group_by(Moderator) %>%
  nest() %>%
  ungroup() %>%
  # filter(Moderator == "none") %>%
  mutate(map2(data, Moderator, possibly(psm_tab_fun, NA_real_)))
4.4.1.2.2 Fixed Effects

Next, I’ll create more parsimonious tables for each moderator.

psm_fxd_tab_fun <- function(d, mod){
  m <- mapvalues(mod, moderators$short_name, moderators$long_name, warn_missing = F)
  t_cols <- c(2, rep(1, 5)); names(t_cols) <- c(" ", traits$long_name)
  cap <- if(mod == "none") "Mega-Analytic Personality-Outcome Associations in Matched Samples" else sprintf("Mega-Analytic %s Moderation of Personality-Outcome Associations in Matched Samples", m)
  d %>% 
    mutate_at(vars(Estimate_OR:Q94.5_OR), ~sprintf("%.2f", .)) %>%
    mutate(est = sprintf("%s<br>[%s, %s]", Estimate_OR, Q5.5_OR, Q94.5_OR),
           est = ifelse(sig == "sig", sprintf("<strong>%s</strong>", est), est)) %>%
    select(Trait, Outcome, Term = names, est) %>%
    # spread it wide and order Outcomes, columns, and studies
    pivot_wider(names_from = "Trait" , values_from = "est", names_sep = "_") %>%
    left_join(crossing(Outcome = outcomes$short_name, Trait = traits$short_name, value  = NA_character_, Term = unique(d$names)) %>% spread(Trait, value)) %>%
    select(Outcome, Term, one_of(traits$short_name)) %>%
    mutate(Term = str_to_title(str_replace(str_remove(Term, "p_value:"), mod, paste(m, " ", sep =""))),
           Outcome = factor(Outcome, levels = outcomes$short_name, labels = outcomes$long_name)) %>%
    arrange(Outcome) %>%
    kable(.
          , "html"
          , escape = F
          , col.names = c("Outcome", "Term", rep("OR [CI]", times = 5))
          , align = c("r", "r", rep("c", 5))
          , caption = cap) %>%
    kable_styling(full_width = F) %>%
    add_header_above(t_cols) %>%
    footnote("<em>OR</em> = Odds Ratio; <em>UI</em> = 89% Bayesian Uncertainty Interval. Bold indicates model terms whose 89% UI of log odds did not overlap with 0.", escape = F) %>%
    save_kable(., file = sprintf("%s/results/psm/matched/tables/fixed/%s.html", res_path, mod))
}

fx_res %>%
  filter((Moderator == "none" & names == "p_value")|
         (Moderator != "none" & grepl("p_value:", names))) %>%
  mutate(sig = ifelse(sign(Q5.5) == sign(Q94.5), "sig", "ns")) %>%
  select(-(Estimate:Q94.5)) %>%
  group_by(Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(tab = map2(data, Moderator, psm_fxd_tab_fun))

r_cols <- c(1, rep(1, 14)); names(r_cols) <- c(" ", outcomes$long_name)
rs <- tibble(start = seq(from = 1, by = 10, length.out = 14),
            end = seq(from = 10, by = 10, length.out = 14))

fx_res %>%
  filter((Moderator != "none" & grepl("p_value:", names))) %>%
  mutate(sig = ifelse(sign(Q5.5) == sign(Q94.5), "sig", "ns")) %>%
  select(-(Estimate:Q94.5)) %>%mutate_at(vars(Estimate_OR:Q94.5_OR), ~sprintf("%.2f", .)) %>%
    mutate(est = sprintf("%s<br>[%s, %s]", Estimate_OR, Q5.5_OR, Q94.5_OR),
           est = ifelse(sig == "sig", sprintf("<strong>%s</strong>", est), est)) %>%
    select(Trait, Outcome, Term = names, est) %>%
    # spread it wide and order Outcomes, columns, and studies
    pivot_wider(names_from = "Outcome" , values_from = "est", names_sep = "_") %>%
  mutate(Term = str_remove(Term, "p_value:"),
         Term = factor(Term, levels = moderators$short_name, labels = moderators$breaks),
           Trait = factor(Trait, levels = traits$short_name, labels = traits$long_name)) %>%
  filter(!is.na(Term)) %>%
    arrange(Trait, Term) %>%
    select(Term, one_of(outcomes$short_name)) %>%
    kable(.
          , "html"
          , escape = F
          , col.names = c( "Term", rep("OR [CI]", times = 14))
          , align = c(  "r", rep("c", 14))
          , caption = "Mega-Analytic Moderators of Personality-Outcome Associations in Matched Samples" ) %>%
    kable_styling(full_width = F) %>%
    kableExtra::group_rows("Extraversion", rs$start[1], rs$end[1], label_row_css = NULL) %>%
    kableExtra::group_rows("Agreeableness", rs$start[2], rs$end[2], label_row_css = NULL) %>%
    kableExtra::group_rows("Conscientiousness", rs$start[3], rs$end[3], label_row_css = NULL) %>%
    kableExtra::group_rows("Neuroticism",rs$start[4], rs$end[4], label_row_css = NULL) %>%
    kableExtra::group_rows("Openness to Experience", rs$start[5], rs$end[5], label_row_css = NULL) %>%
    # collapse_rows(1, valign = "top") %>%
    add_header_above(r_cols) %>%
    footnote("<em>OR</em> = Odds Ratio; <em>UI</em> = 89% Bayesian Uncertainty Interval. Bold indicates model terms whose 89% UI of log odds did not overlap with 0. Binary variables were dummy coded such that the label to the right of the 'v' indicates the reference group.", escape = F) %>% save_kable(., file = sprintf("%s/results/psm/matched/tables/fixed/all_moderators.html", res_path))
4.4.1.2.3 Significant Fixed Effects
## # A tibble: 26 x 12
##    Trait Outcome      Moderator names   Estimate Est.Error     Q5.5    Q94.5 Estimate_OR Est.Error_OR Q5.5_OR Q94.5_OR
##    <chr> <chr>        <chr>     <chr>      <dbl>     <dbl>    <dbl>    <dbl>       <dbl>        <dbl>   <dbl>    <dbl>
##  1 A     crim         none      p_value  -0.0467    0.0230 -0.0837  -0.0104        0.954         1.02   0.920    0.990
##  2 A     divorced     none      p_value   0.0504    0.0210  0.0189   0.0838        1.05          1.02   1.02     1.09 
##  3 A     married      none      p_value   0.0364    0.0123  0.0177   0.0557        1.04          1.01   1.02     1.06 
##  4 A     physhlthevnt none      p_value   0.0247    0.0127  0.00458  0.0440        1.03          1.01   1.00     1.05 
##  5 A     vlntred      none      p_value   0.0351    0.0139  0.0130   0.0566        1.04          1.01   1.01     1.06 
##  6 C     chldbrth     none      p_value   0.0327    0.0199  0.00345  0.0655        1.03          1.02   1.00     1.07 
##  7 C     divorced     none      p_value   0.0362    0.0161  0.0114   0.0622        1.04          1.02   1.01     1.06 
##  8 C     edu          none      p_value  -0.0297    0.0163 -0.0551  -0.00380       0.971         1.02   0.946    0.996
##  9 C     married      none      p_value   0.0347    0.0164  0.00955  0.0606        1.04          1.02   1.01     1.06 
## 10 C     mntlhlthevnt none      p_value  -0.0354    0.0168 -0.0611  -0.00819       0.965         1.02   0.941    0.992
## # … with 16 more rows
## # A tibble: 48 x 12
##    Trait Outcome      Moderator   names               Estimate Est.Error      Q5.5     Q94.5 Estimate_OR Est.Error_OR Q5.5_OR Q94.5_OR
##    <chr> <chr>        <chr>       <chr>                  <dbl>     <dbl>     <dbl>     <dbl>       <dbl>        <dbl>   <dbl>    <dbl>
##  1 A     chldbrth     age         p_value:age         -0.00226   0.00116 -0.00395  -0.000535       0.998         1.00   0.996    0.999
##  2 A     chldbrth     predInt     p_value:predInt     -0.0130    0.00421 -0.0196   -0.00626        0.987         1.00   0.981    0.994
##  3 A     married      age         p_value:age         -0.00301   0.00171 -0.00587  -0.000757       0.997         1.00   0.994    0.999
##  4 A     mntlhlthevnt parOccPrstg p_value:parOccPrstg  0.00788   0.00442  0.000943  0.0149         1.01          1.00   1.00     1.02 
##  5 A     mntlhlthevnt predInt     p_value:predInt     -0.00831   0.00521 -0.0164   -0.000179       0.992         1.01   0.984    1.00 
##  6 A     mortality    gender      p_value:gender1     -0.0470    0.0220  -0.0820   -0.0126         0.954         1.02   0.921    0.988
##  7 A     retired      parEdu      p_value:parEdu2      0.0981    0.0555   0.0127    0.188          1.10          1.06   1.01     1.21 
##  8 C     chldbrth     gender      p_value:gender1     -0.0301    0.0185  -0.0589   -0.00107        0.970         1.02   0.943    0.999
##  9 C     chldbrth     parEdu      p_value:parEdu1      0.0656    0.0254   0.0269    0.106          1.07          1.03   1.03     1.11 
## 10 C     chldbrth     predInt     p_value:predInt     -0.0133    0.00729 -0.0243   -0.00188        0.987         1.01   0.976    0.998
## # … with 38 more rows

4.4.2 Variance-Covariance Matrices

## # A tibble: 625 x 5
##    Trait Outcome   Moderator   vc               sd              
##    <chr> <chr>     <chr>       <list>           <list>          
##  1 A     chldbrth  age         <named list [3]> <tibble [4 × 5]>
##  2 A     chldbrth  gender      <named list [3]> <tibble [4 × 5]>
##  3 A     chldbrth  grsWages    <named list [3]> <tibble [4 × 5]>
##  4 A     chldbrth  none        <named list [3]> <tibble [2 × 5]>
##  5 A     chldbrth  parEdu      <named list [3]> <tibble [6 × 5]>
##  6 A     chldbrth  parOccPrstg <named list [3]> <tibble [4 × 5]>
##  7 A     chldbrth  predInt     <named list [3]> <tibble [2 × 5]>
##  8 A     chldbrth  race        <named list [3]> <tibble [6 × 5]>
##  9 A     chldbrth  reliability <named list [3]> <tibble [2 × 5]>
## 10 A     chldmvout age         <named list [3]> <tibble [4 × 5]>
## # … with 615 more rows

4.4.2.1 Tables

vc_var <- vc_res %>% 
  select(-vc) %>%
  filter(!Moderator %in% c("reliability", "predInt"))%>%
  unnest(sd) %>%
  filter((Moderator == "none" & names == "p_value") |
         (Moderator != "none" & grepl("p_value:", names))) %>% 
  filter(complete.cases(.))
  # mutate(sig = ifelse(sign(Q5.5) == sign(Q94.5), "sig", "ns")) %>%
  # mutate_at(vars(Estimate, Q5.5, Q94.5), ~(.)^2)

hcols <- rep(1,6)
names(hcols) <- c(" ", traits$short_name)
vc_var %>%
  filter(Moderator == "none") %>%
  mutate_at(vars(Estimate, Q5.5, Q94.5), 
    ~ifelse(abs(.) < .01, sprintf("%.3f", .), sprintf("%.2f", .))) %>%
  mutate_at(vars(Estimate, Q5.5, Q94.5), ~str_remove(., "^0")) %>%
  mutate(est = sprintf("%s<br>[%s,%s]", Estimate, Q5.5, Q94.5),
         Outcome = factor(Outcome, levels = outcomes$short_name, labels =  outcomes$long_name)) %>%
  select(Trait, Outcome, est) %>%
  filter(complete.cases(.)) %>%
  pivot_wider(names_from = "Trait"
              , values_from = "est") %>% 
  select(Outcome, all_of(traits$short_name)) %>%
  arrange(Outcome) %>%
  kable(.
        , "html"
        , escape = F
        , align = c("r", rep("c", 5))
        , col.names = c("Outcome", rep("b [CI]", 5))) %>%
  kable_styling(full_width = F) %>%
  add_header_above(hcols) %>%
  save_kable(., file = sprintf("%s/results/psm/matched/tables/level 2/p_value_none.html", res_path))

hcols <- rep(1,6)
names(hcols) <- c(" ", traits$short_name)
vc_var %>%
  mutate_at(vars(Estimate, Q5.5, Q94.5), ~sprintf("%.3f", .)) %>%
  mutate_at(vars(Estimate, Q5.5, Q94.5), ~str_remove(., "^0")) %>%
  mutate(est = sprintf("%s<br>[%s, %s]", Estimate, Q5.5, Q94.5),
         names = str_remove(names, "p_value:"),
         names = factor(names, c("p_value", moderators$short_name), c("None", moderators$long_name)),
         Outcome = factor(Outcome, levels = outcomes$short_name, labels =  outcomes$long_name)) %>%
  select(Trait, Outcome, Moderator = names, est) %>%
  filter(complete.cases(.)) %>%
  pivot_wider(names_from = "Trait"
              , values_from = "est") %>% 
  select(Outcome, Moderator, all_of(traits$short_name)) %>%
  arrange(Outcome, Moderator) %>%
  select(-Outcome) %>%
  kable(.
        , "html"
        , escape = F
        , align = c("r", rep("c", 5))
        , col.names = c("Moderator", rep("b [CI]", 5))) %>%
  kable_styling(full_width = F) %>%
  add_header_above(hcols) %>%
  save_kable(., file = sprintf("%s/results/psm/matched/tables/level 2/all_mods.html", res_path))

vc_tab_fun <- function(d, mod){
  hcols <- c(2, rep(1,5))
  names(hcols) <- c(" ", traits$short_name)
  d %>%
  mutate_at(vars(Estimate, Q5.5, Q94.5), ~sprintf("%.3f", .)) %>%
  mutate_at(vars(Estimate, Q5.5, Q94.5), ~str_remove(., "^0")) %>%
  mutate(est = sprintf("%s<br>[%s, %s]", Estimate, Q5.5, Q94.5),
         names = str_remove(names, "p_value:"),
         names = factor(names, c("p_value", moderators$short_name), c("None", moderators$long_name)),
         Outcome = factor(Outcome, levels = outcomes$short_name, labels =  outcomes$long_name)) %>%
  select(Trait, Outcome, Moderator = names, est) %>%
  filter(complete.cases(.)) %>%
  pivot_wider(names_from = "Trait"
              , values_from = "est") %>% 
  select(Outcome, Moderator, all_of(traits$short_name)) %>%
  arrange(Outcome, Moderator) %>%
  # select(-Outcome) %>%
  kable(.
        , "html"
        , escape = F
        , align = c("r", "r", rep("c", 5))
        , col.names = c("Outcome", "Moderator", rep("b [CI]", 5))) %>%
  kable_styling(full_width = F) %>%
  add_header_above(hcols) %>%
  save_kable(., file = sprintf("%s/results/psm/matched/tables/level 2/%s.html", res_path, mod))
}

vc_var %>% 
  filter(Moderator != "none") %>%
  group_by(Moderator) %>% 
  nest() %>%
  ungroup() %>%
  mutate(map2(data, Moderator, vc_tab_fun))

4.4.3 Simple Effects

4.4.3.1 All Fixed-Effect Simple Effects

simp_eff_fun <- function(df, outcome, mod){ 
  print(paste(outcome, mod))
  o <- mapvalues(outcome, outcomes$short_name, outcomes$long_name, warn_missing = F)
  m <- mapvalues(mod, moderators$mod, moderators$mod_name, warn_missing = F)
  d <- round(max(abs(1-min(df$Estimate_OR)), abs(1-max(df$Estimate_OR))), 2)
  mini <- if(d > 2) .05 else 1-(d+(d/5)) 
  maxi <- if(d > 2) 2.05 else 1+d+(d/5)
  lim <- c(mini, maxi)
  brk <- if(d > 2) c(0, 1, 2) else{round(c(1-d-(d/10), 1, 1+d+(d/10)),2)}
  lab <- if(d > 2){c("0", "1", "2")} else{str_remove(c(round(1-d-(d/10),2), 1, round(1+d+(d/10),2)), "^0")}
  titl <- if(mod == "none"){o} else {sprintf("%s: Personality x %s Simple Effects", o, m)}
  df <- df %>% unclass %>% data.frame
  df$mod_value <- df[,mod]
  df <- df %>% select(-all_of(mod)) %>% as_tibble
  if(class(df$mod_value) == "factor"){df <- df %>% mutate(mod_fac = factor(mod_value))} 
  else{df <- df %>%
    group_by(Trait) %>%
    mutate(mod_fac = factor(mod_value, levels = unique(mod_value), labels = c("-1 SD", "M", "+1 SD"))) %>%
    ungroup()
  }
  lt <- c("dotted", "solid", "dashed")[1:length(unique(df$mod_fac))]
  
  df %>%
    mutate(Trait = factor(Trait, levels = traits$short_name, labels = traits$long_name),
           Q5.5_OR = ifelse(Q5.5_OR < mini, mini, Q5.5_OR),
           Q94.5_OR = ifelse(Q94.5_OR > maxi, maxi, Q94.5_OR)) %>%
    ggplot(aes(x = p_value
               , y = Estimate_OR
               , group  = mod_fac))  +
      scale_y_continuous(limits = lim
                         , breaks = brk
                         , labels = lab) +
      scale_linetype_manual(values = lt) +
      geom_hline(aes(yintercept = 1), size = .25) +
      geom_ribbon(aes(ymin = Q5.5_OR
                      , ymax = Q94.5_OR
                      , fill = mod_fac)
                  , alpha = .25) +
      geom_line(aes(linetype = mod_fac)) +
      labs(x = "Personality (POMP)"
           , y = "Odds Ratio"
           , title = titl
           , linetype = m
           , fill = m) +
      facet_wrap(~Trait, nrow = 3) +
      theme_classic() +
      theme(legend.position = "bottom"
            , plot.title = element_text(face = "bold", size = rel(1.2), 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/psm/matched/plots/simple effects/%s/matched_%s.pdf", res_path, mod, outcome), width = 6, height = 6)
}

fx_pred %>%
  filter(!Moderator %in% c("SES", "reliability", "predInt")) %>%
  group_by(Outcome, Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(data = map(data, ~(.) %>% unnest(fx) %>% mutate_at(vars(Estimate, Q5.5, Q94.5), lst(OR = exp))),
         plot = pmap(list(data, Outcome, Moderator), simp_eff_fun))

4.4.3.2 Reliable Simple Effects

sig_simp_eff_fun <- function(df, mod){ 
  print(paste(mod))
  num_sig <- nrow(df %>% select(Trait, Outcome) %>% distinct())
  ncols <- if(num_sig%/%5>=2) 5 else 3
  nrows <- ceiling(num_sig / ncols)
  m <- mapvalues(mod, moderators$short_name, moderators$long_name, warn_missing = F)
  d <- round(max(abs(1-min(df$Estimate_OR)), abs(1-max(df$Estimate_OR))), 2)
  mini <- if(d > 2) .05 else 1-(d+(d/5)) 
  maxi <- if(d > 2) 2.05 else 1+d+(d/5)
  lim <- c(mini, maxi)
  brk <- if(d > 2) c(0, 1, 2) else{round(c(1-d-(d/10), 1, 1+d+(d/10)),2)}
  lab <- if(d > 2){c("0", "1", "2")} else{str_remove(c(round(1-d-(d/10),2), 1, round(1+d+(d/10),2)), "^0")}
  titl <- sprintf("Simple Effects of %s Moderating Personality-Outcome Associations", m)
  df <- df %>% unclass %>% data.frame
  df$mod_value <- df[,mod]
  df <- df %>% select(-all_of(mod)) %>% as_tibble
  if(class(df$mod_value) == "factor"){df <- df %>% mutate(mod_fac = factor(mod_value))} 
  else{df <- df %>%
    group_by(Trait, Outcome) %>%
    mutate(mod_fac = factor(mod_value, levels = unique(mod_value), labels = c("-1 SD", "M", "+1 SD"))) %>%
    ungroup()
  }
  lt <- c("dotted", "solid", "dashed")[1:length(unique(df$mod_fac))]
  shrt_levs <- paste(rep(outcomes$short_name, each = 14), rep(traits$short_name, times = 14))
  long_levs <- paste(rep(outcomes$long_name, each = 14), rep(traits$long_name, times = 14), sep = "\n")
  df %>%
    unite(levs, Outcome, Trait, sep = " ") %>%
    mutate(levs = factor(levs, shrt_levs, long_levs),
           Q5.5_OR = ifelse(Q5.5_OR < mini, mini, Q5.5_OR),
           Q94.5_OR = ifelse(Q94.5_OR > maxi, maxi, Q94.5_OR)) %>%
    ggplot(aes(x = p_value
               , y = Estimate_OR
               , group  = mod_fac))  +
      scale_y_continuous(limits = lim
                         , breaks = brk
                         , labels = lab) +
      scale_linetype_manual(values = lt) +
      geom_hline(aes(yintercept = 1), size = .25, color = "gray50") +
      geom_ribbon(aes(ymin = Q5.5_OR
                      , ymax = Q94.5_OR
                      , fill = mod_fac)
                  , alpha = .25) +
      geom_line(aes(linetype = mod_fac)) +
      labs(x = "Personality (POMP)"
           , y = "Odds Ratio"
           , title = titl
           , linetype = m
           , fill = m) +
      facet_wrap(~levs, ncol = ncols, nrow = nrows) +
      theme_classic() +
      theme(legend.position = "bottom"
            , panel.background = element_rect(color = "black", fill = "white")
            , plot.title = element_text(face = "bold", size = rel(1.2), hjust = .5)
            , strip.background = element_blank()#element_rect(fill = "black")
            , strip.text = element_text(color = "black")
            , axis.text = element_text(color = "black"))
  ggsave(file = sprintf("%s/results/psm/matched/plots/simple effects/reliable/matched_%s.pdf", res_path, mod), width = ncols*2+2, height = nrows*2)
}

sig_mod <- fx_res %>% 
  filter(Moderator != "none" & 
         grepl("p_value:", names) &
         (sign(Q5.5) == sign(Q94.5))) %>%
  select(Trait, Outcome, Moderator) %>% 
  distinct() %>%
  mutate(sig = "sig")

fx_pred %>%
  filter(!Moderator %in% c("SES")) %>%
  left_join(sig_mod) %>%
  filter(!is.na(sig)) %>%
  arrange(Moderator, Outcome) %>%
  group_by(Moderator) %>%
  nest() %>%
  ungroup() %>%
  mutate(data = map(data, ~(.) %>% unnest(fx) %>% mutate_at(vars(Estimate, Q5.5, Q94.5), lst(OR = exp))),
         plot = map2(data, Moderator, sig_simp_eff_fun))