Week 7: Piecing Plots Together

Emorie D Beck

Piecing Plots Together

Packages

# | code-line-numbers: "11-13"
library(RColorBrewer)
library(knitr)
library(kableExtra)
library(plyr)
library(broom)
library(modelr)
library(lme4)
library(broom.mixed)
library(tidyverse)
library(ggdist)
library(patchwork)
library(cowplot)
library(ggExtra)
library(distributional)
library(gganimate)

Custom Theme:

my_theme <- function(){
  theme_bw() + 
  theme(
    legend.position = "bottom"
    , legend.title = element_text(face = "bold", size = rel(1))
    , legend.text = element_text(face = "italic", size = rel(1))
    , axis.text = element_text(face = "bold", size = rel(1.1), color = "black")
    , axis.title = element_text(face = "bold", size = rel(1.2))
    , plot.title = element_text(face = "bold", size = rel(1.2), hjust = .5)
    , plot.subtitle = element_text(face = "italic", size = rel(1.2), hjust = .5)
    , strip.text = element_text(face = "bold", size = rel(1.1), color = "white")
    , strip.background = element_rect(fill = "black")
    )
}

Review

  • Over the last several weeks, we have talked about:
    • tidying data
    • ggplot2 logic
    • visualizing proportions
    • visualizing differences
    • visualizing time series
    • visualizing uncertainty
  • For the rest of the course, we will pivot to taking everything we’ve learning and piecing it all together
    • Today: Piecing visualizations together
    • Next week: Polishing visualizations **
    • 03/05: Interactive Visualizations (shiny)

Today

  • There are lots of packages for piecing visualizations together

  • Over the years, I’ve tried:

    • ggExtra
    • cowplot
    • patchwork
  • Although patchwork wins by a landslide (imo), each has helpful unique features, so I’ll show you elements of each

Today

ggExtra

  • We’ll start with ggExtra because it will help us create plots with distributions in the margins.
  • After, we’ll move to patchwork, where there will be lots of little odds and ends to step through
  • Remember these data?
load(url("https://github.com/emoriebeck/psc290-data-viz-2022/blob/main/04-week4-associations/04-data/week4-data.RData?raw=true"))
pred_data
# A tibble: 5,021 × 25
   study  o_value p_year SID     p_value     age gender grsWages parEdu race 
   <chr>  <fct>    <dbl> <chr>     <dbl>   <dbl> <fct>     <dbl> <fct>  <fct>
 1 Study1 0         2005 61215      6.67 -29.9   1         1.02  2      0    
 2 Study1 0         2005 184965     0    -22.9   0         1.14  2      0    
 3 Study1 0         2005 488251    10     -3.92  1         0.717 1      0    
 4 Study1 0         2005 650779     7.22 -25.9   1         0.644 3      0    
 5 Study1 0         2005 969691     7.22  -0.925 1         0.812 2      0    
 6 Study1 0         2005 986687     6.11  14.1   0         1.76  2      0    
 7 Study1 0         2005 1054011    5.56   8.08  0         1.34  1      0    
 8 Study1 0         2005 1372251    7.78   5.08  1         0.842 1      0    
 9 Study1 0         2005 1496703    6.11 -23.9   0         1.42  2      0    
10 Study1 0         2005 1897887    2.78  38.1   1         0.725 2      0    
# ℹ 5,011 more rows
# ℹ 15 more variables: physhlthevnt <fct>, SRhealth <dbl>, smokes <fct>,
#   alcohol <fct>, exercise <dbl>, BMI <dbl>, parDivorce <fct>, PhysFunc <fct>,
#   religion <fct>, education <fct>, married <fct>, numKids <dbl>,
#   parOccPrstg <dbl>, reliability <dbl>, predInt <dbl>

ggExtra

Let’s plot the association between conscientiousness and self-rated health across genders in Study 1:

p <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = p_value, y = SRhealth, color = gender)) + 
    geom_point(
      , size = 2
      , alpha = .5
      ) + 
    scale_color_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    labs(
      x = "Conscientiousness (POMP, 0-10)"
      , y = "Self-Rated Health (POMP, 0-10)"
      , color = "Gender"
    ) + 
    my_theme()
p

ggExtra

p <- p + 
  geom_smooth(
    , method = "lm"
    )
p

ggExtra

To get marginal distributions, we can just use ggExtra::ggMarginal()

ggMarginal(p)

This is fine, but we can do better!

ggExtra

ggMarginal(p, color = "purple4")

Let’s try color

ggExtra

ggMarginal(
  p
  , color = "purple4"
  , fill = "purple4"
  , alpha = .5
  )

Let’s try fill

ggExtra

ggMarginal(
  p
  , color = "purple4"
  , fill = "purple4"
  , alpha = .5
  , type = "histogram"
  )

Let’s try a histogram

ggExtra

ggMarginal(
  p
  , groupColour = T
  , groupFill = T
  )

cowplot + pathwork

  • Why cowplot or patchwork?
    • figure alignment
    • easier to choose relative values and layouts
    • can mix base R plots and ggplot2 plots
    • allows you to annotate plots (including stacking, as opposed to layering)
    • shared legends!
    • includes the themes from his book

Patchwork: Piecing the Plots Together

px <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = p_value, fill = gender, color = gender)) + 
    geom_density(alpha = .5) + 
    scale_color_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    scale_fill_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    labs(fill = "Gender", color = "Gender") + 
    theme_void()
px

py <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = SRhealth, fill = gender, color = gender)) + 
    geom_density(alpha = .5) + 
    scale_color_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    scale_fill_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    labs(fill = "Gender", color = "Gender") + 
    coord_flip() + 
    theme_void()
py

Patchwork: Piecing the Plots Together

We can use the + and / operators to arrange them:

px / (p + py)

That arrangement isn’t quite right

Patchwork: Piecing the Plots Together

layout <- "
AAAAAA##
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
"

px + p + py +
  plot_layout(design = layout)

Those legends are messing us up!

Patchwork: Piecing the Plots Together

layout <- "
AAAAAA##
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
"

px + p + py +
  plot_layout(
    design = layout
    , guides = "collect"
    ) & 
  theme(legend.position = "bottom")

Honestly, we don’t need the marginal legend

Patchwork: Piecing the Plots Together

layout <- "
AAAAAA##
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
BBBBBBCC
"

(px + theme(legend.position = "none")) + 
  p + 
  (py + theme(legend.position = "none")) +
  plot_layout(design = layout) 

Patchwork: Piecing the Plots Together

px <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = p_value, y = gender, fill = gender, color = gender)) + 
    geom_boxplot(alpha = .5) + 
    geom_jitter(aes(y = gender), alpha = .5) + 
    scale_color_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    scale_fill_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    labs(fill = "Gender", color = "Gender") + 
    theme_void() + 
    theme(legend.position = "none")
px

py <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = SRhealth, y = gender, fill = gender, color = gender)) + 
    geom_boxplot(alpha = .5) + 
    geom_jitter(aes(y = gender), alpha = .5) + 
    scale_color_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    scale_fill_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    labs(fill = "Gender", color = "Gender") + 
    coord_flip() + 
    theme_void() + 
    theme(legend.position = "none")
py

Patchwork: Piecing the Plots Together

layout <- "
AAAAAA##
BBBBBBCC
BBBBBBCC
BBBBBBCC
"

(px + theme(legend.position = "none")) + 
  p + 
  (py + theme(legend.position = "none")) +
  plot_layout(design = layout) 

Advanced Piecing Plots Together

Advanced Piecing Plots Together

  • Marginal plots are great for lots of reasons
  • But when it comes to piecing plots together, we are often interested for bringing together different kinds of figures together because you can’t bring them together with facets or other ways

Advanced Piecing Plots Together

Let me show you a couple of examples from my work that has used cowplot or patchwork

Beck & Jackson, 2022, JPSP

Beck et al., 2024, Alzheimer’s & Dementia

Beck & Jackson, 2023, Psych Science

Beck et al., under review

Example: Forest Plots

  • Let’s use forest plots as an example. Why use forest plots:
    • Meta-analyses are common, and within-paper meta-analyses in multi-study papers are becoming more common
    • Not only will this let us practice piecing plots together, this is a particularly advanced case that will let us learn about new elements that we can creating (e.g., via grobs)
  • Let’s build up our use cases incrementally!
  • But first, we need some data to plot!

Example Setup: Forest Plots

And remember these models?

tidy_ci <- function(m) tidy(m, conf.int = T)

nested_m <- pred_data %>%
  group_by(study) %>%
  nest() %>%
  ungroup() %>%
  mutate(
    m = map(data
            , ~glm(
              o_value ~ p_value
              , data = .
              , family = binomial(link = "logit")
              )
            )
    , tidy = map(m, tidy_ci)
  )
nested_m
# A tibble: 6 × 4
  study  data                  m      tidy            
  <chr>  <list>                <list> <list>          
1 Study1 <tibble [831 × 24]>   <glm>  <tibble [2 × 7]>
2 Study2 <tibble [1,000 × 24]> <glm>  <tibble [2 × 7]>
3 Study3 <tibble [1,000 × 24]> <glm>  <tibble [2 × 7]>
4 Study4 <tibble [574 × 24]>   <glm>  <tibble [2 × 7]>
5 Study5 <tibble [616 × 24]>   <glm>  <tibble [2 × 7]>
6 Study6 <tibble [1,000 × 24]> <glm>  <tibble [2 × 7]>

Example Setup: Forest Plots

  • And remember these models?
  • Let’s make two small changes:
    • Add the number of observations
    • Add the residual degrees of freedom
  • Why? We usually include these in a plot as it’s relevant information
m_fun <- function(d) {
  glm(o_value ~ p_value + married + married:p_value
      , data = d
      , family = binomial(link = "logit"))
}
tidy_ci <- function(m) tidy(m, conf.int = T) %>% mutate(df.resid = m$df.residual, n = nrow(m$data))

nested_m <- pred_data %>%
  group_by(study) %>%
  nest() %>%
  ungroup() %>%
  mutate(
    m = map(data, m_fun)
    , tidy = map(m, tidy_ci)
  )
nested_m
# A tibble: 6 × 4
  study  data                  m      tidy            
  <chr>  <list>                <list> <list>          
1 Study1 <tibble [831 × 24]>   <glm>  <tibble [4 × 9]>
2 Study2 <tibble [1,000 × 24]> <glm>  <tibble [4 × 9]>
3 Study3 <tibble [1,000 × 24]> <glm>  <tibble [4 × 9]>
4 Study4 <tibble [574 × 24]>   <glm>  <tibble [4 × 9]>
5 Study5 <tibble [616 × 24]>   <glm>  <tibble [4 × 9]>
6 Study6 <tibble [1,000 × 24]> <glm>  <tibble [4 × 9]>

Example Setup: Forest Plots

Here’s our unnested model terms

nested_m %>% 
  select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp)
# A tibble: 24 × 10
   study  term  estimate std.error statistic p.value conf.low conf.high df.resid
   <chr>  <chr>    <dbl>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl>    <int>
 1 Study1 (Int…    1.33     0.391      0.727  0.467    0.618      2.87       827
 2 Study1 p_va…    0.900    0.0572    -1.84   0.0655   0.804      1.01       827
 3 Study1 marr…    0.926    0.524     -0.147  0.883    0.330      2.59       827
 4 Study1 p_va…    1.02     0.0745     0.238  0.812    0.880      1.18       827
 5 Study2 (Int…    0.705    1.59      -0.220  0.826    0.0250    18.1        992
 6 Study2 p_va…    1.09     0.218      0.376  0.707    0.702      1.71       992
 7 Study2 marr…    6.40     1.62       1.14   0.253    0.237    190.         992
 8 Study2 p_va…    0.758    0.221     -1.25   0.211    0.478      1.18       992
 9 Study3 (Int…    6.03     1.20       1.49   0.135    0.581     68.2        996
10 Study3 p_va…    0.706    0.156     -2.23   0.0256   0.514      0.952      996
# ℹ 14 more rows
# ℹ 1 more variable: n <int>

Example Setup: Forest Plots

But maybe we are particularly interested in the interaction between marital status and personality in predicting mortality, which we want to plot as a forest plot

nested_m %>% 
  select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1")
# A tibble: 6 × 10
  study  term   estimate std.error statistic p.value conf.low conf.high df.resid
  <chr>  <chr>     <dbl>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl>    <int>
1 Study1 p_val…    1.02     0.0745     0.238 0.812      0.880     1.18       827
2 Study2 p_val…    0.758    0.221     -1.25  0.211      0.478     1.18       992
3 Study3 p_val…    1.21     0.161      1.17  0.242      0.886     1.68       996
4 Study4 p_val…    0.824    0.284     -0.683 0.495      0.471     1.46       570
5 Study5 p_val…    0.618    0.159     -3.04  0.00239    0.449     0.838      612
6 Study6 p_val…    0.957    0.109     -0.407 0.684      0.773     1.19       996
# ℹ 1 more variable: n <int>

Example Setup: Forest Plots

  • We could hack our way to a forest plot in a single figure, but it never looks as nice as if we do it in two
    • the forest plot itself
    • the table of values

Example Setup: Forest Plot (P1)

p1 <- nested_m %>% 
  select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1") %>%
  ggplot(aes(x = estimate, y = fct_rev(study))) + 
    labs(
      x = "Model Estimated OR (CI)"
      , y = NULL
      ) + 
    my_theme()
p1

Example Setup: Forest Plot (P1)

Let’s add our point estimates and uncertainty intervals

p1 <- p1 + 
  stat_gradientinterval(
    aes(
      xdist = dist_student_t(
        df = df.resid
        , mu = estimate
        , sigma = std.error
        ))
    , .width = c(.95, .99)
    , shape = "square"
  ) 
p1

Example Setup: Forest Plot (P1)

But we want to order the points by the effect sizes:

p1 <- nested_m %>% 
  select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1") %>%
  arrange(desc(estimate)) %>%
  mutate(study = fct_inorder(study)) %>%
  ggplot(aes(x = estimate, y = study)) + 
    labs(
      x = "Model Estimated OR (CI)"
      , y = NULL
      ) + 
    my_theme()
p1

Example Setup: Forest Plot (P1)

Let’s add our point estimates and uncertainty intervals back in

p1 <- p1 + 
  stat_gradientinterval(
    aes(
      xdist = dist_student_t(
        df = df.resid
        , mu = estimate
        , sigma = std.error
        ))
    , .width = c(.95, .99)
    , shape = "square"
  ) 
p1

Example Setup: Forest Plot (P1)

And add in a vertical line at OR = 1:

p1 <- p1 + 
  geom_vline(
    aes(xintercept = 1)
    , linetype = "dashed"
    ) 
p1 

Example Setup: Forest Plot Table (P2)

  • In a forest plot, we don’t just show estimates, we print them with the sample size
  • Let’s build a table with those!
  • There are packages to do this, but I like to build them myself because it helps them play nicer with cowplot or patchwork

Example Setup: Forest Plot Table (P2)

  • To figure out how to make it, it’s easiest to figure where you want to end up and work backward.

Example Setup: Forest Plot Table (P2)

p2 <- nested_m %>% 
  select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1") %>%
  arrange(desc(estimate)) %>%
  mutate(
    study = fct_inorder(study)
    , study2 = 1:n()
    ) %>%
  mutate_at(vars(estimate, conf.low, conf.high), ~sprintf("%.2f", .)) %>%
  mutate(
    est = sprintf("%s [%s, %s]", estimate, conf.low, conf.high)
    , n = as.character(n)
    ) %>%
  select(study, study2, estimate, n, est) %>%
  pivot_longer(
    cols = c(est, n)
    , values_to = "lab"
    , names_to = "est"
  )
p2
# A tibble: 12 × 5
   study  study2 estimate est   lab              
   <fct>   <int> <chr>    <chr> <chr>            
 1 Study1      1 1.02     est   1.02 [0.88, 1.18]
 2 Study1      1 1.02     n     831              
 3 Study2      2 0.76     est   0.76 [0.48, 1.18]
 4 Study2      2 0.76     n     1000             
 5 Study3      3 1.21     est   1.21 [0.89, 1.68]
 6 Study3      3 1.21     n     1000             
 7 Study4      4 0.82     est   0.82 [0.47, 1.46]
 8 Study4      4 0.82     n     574              
 9 Study5      5 0.62     est   0.62 [0.45, 0.84]
10 Study5      5 0.62     n     616              
11 Study6      6 0.96     est   0.96 [0.77, 1.19]
12 Study6      6 0.96     n     1000             

Example Setup: Forest Plot Table (P2)

Let’s build our base

p2 <- p2 %>%
  ggplot(aes(x = est, y = study2)) + 
    labs(
      x = NULL
      , y = NULL
      ) + 
    my_theme()
p2

Example Setup: Forest Plot Table (P2)

Add in the text:

p2 <- p2 + 
  geom_text(aes(label = lab))
p2

Example Setup: Forest Plot Table (P2)

Set the theme

p2 <- p2 + 
  theme_void()
p2

Example Setup: Forest Plot Table (P2)

We’ll add a horizontal line at the top and bottom to match the forest plot:

p2 <- p2 + 
  geom_hline(aes(yintercept = 6.5)) + 
  theme(axis.line.x = element_line(color = "black"))
p2

Example Setup: Forest Plot Table (P2)

Add the column labels:

p2 <- p2 + 
  annotate("text"
           , x = "est" , y = 7
           , label = "b [CI]"
           , fontface = "bold"
           ) + 
  annotate("text"
           , x = "n", y = 7
           , label = "N"
           , fontface = "bold"
           ) 
p2

Example Setup: Forest Plot Table (P2)

We need a little margin on the top and bottom:

p2 <- p2 + 
  scale_y_continuous(limits = c(.4,7.1))
p2

Example Setup: Back to the Forest Plot (P1)

  • We added an extra row at the top of the table, so we need to do that for the forest plot, too.
  • To do so, we will use the same trick we did for the table, which is “tricking” ggplot into thinking we have a continuous y-axis
p1 <- nested_m %>% select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1") %>%
  arrange(desc(estimate)) %>%
  mutate(study = fct_inorder(study)
         , study2 = 1:n()) %>%
  ggplot(aes(x = estimate, y = study2)) + 
    labs(
      x = "Model Estimated OR (CI)"
      , y = NULL
      ) + 
    my_theme()
p1

Example Setup: Back to the Forest Plot (P1)

Add our point estimates and uncertainty intervals, along with the vertical line at OR = 1

p1 <- p1 + 
  stat_gradientinterval(
    aes(
      xdist = dist_student_t(
        df = df.resid
        , mu = estimate
        , sigma = std.error
        ))
    , .width = c(.95, .99)
    , shape = "square"
  ) + 
  geom_vline(aes(xintercept = 1), linetype = "dashed") 
p1

Example Setup: Back to the Forest Plot (P1)

Change the y scale back

p1 <- p1 + 
  scale_y_continuous(
    limits = c(.4,7.1) # identical to table
    , breaks = seq(1,6,1) # one per study
    , labels = levels(p1$data$study)
    )
p1 

Example Setup: Back to the Forest Plot (P1)

Add in that top bar:

p1 <- p1 + 
  geom_hline(aes(yintercept = 6.5))
p1 

Example Setup: Back to the Forest Plot (P1)

Remove the y axis line

p1 <- p1 + 
  theme(
    axis.line.x = element_line()
    , axis.ticks.y = element_blank()
    , panel.border = element_blank()
    )
p1 

Example Setup: Back to the Forest Plot (P1)

  • Remember that ggplot is layered.
  • So sometimes, you have to hack ggplot and use annotate() rectangles to block out portions of the plot.
  • Let’s block out where the dashed line touches the top:
p1 <- p1 + 
  annotate(
    "rect"
    , xmin = -Inf
    , xmax = Inf
    , ymin = 6.51
    , ymax = Inf
    , fill = "white"
    )
p1

patchwork

patchwork: Piecing the Plots Together

  • I know that was a lot, but such is the reality of ggplot – we have to hack it!
    • annotate() is a great tool for this
    • so are our scale_[map]_[type] functions, especially given the labels can be anything we want!
    • and our theme elements also let us hack many more parts!
  • The biggest trick to ggplot2 is simply having lots of tricks up your sleeve, which come from knowledge (and StackOverflow)

patchwork: Piecing the Plots Together

  • patchwork is great, and a little more intuitive for simple use cases
  • (We’ll still talk some about cowplot and a more full demo of it is at the end of the slides and in the workbook)
  • patchwork allows you to use the + to piece plots together and makes a lot of default assumptions about alignment
  • It also let’s you continue to layer on top of figures that are pieced together, which cowplot doesn’t do (easily)

patchwork: Piecing the Plots Together

We can just use the + operator!

p1 + p2

patchwork: Piecing the Plots Together

We can also add rows using the /

p1 / p2

patchwork: Piecing the Plots Together

And change their arrangement using plot_layout()

p1 / p2 + 
  plot_layout(heights = c(3,7))

patchwork: Piecing the Plots Together

And change their arrangement using plot_layout()

p1 + p2 + 
  plot_layout(widths = c(6,4))

patchwork: Piecing the Plots Together

We can add titles using plot_annotation()

p1 + p2 + 
  plot_layout(widths = c(6,4)) + 
  plot_annotation(
    title = "Mortality Odds"
    , subtitle = "Conscientiousness x Marital Status"
    , theme = my_theme()
    ) 

patchwork: Piecing the Plots Together

We can add labels to plot using plot_annotation()

p1 + p2 + 
  plot_layout(widths = c(6,4)) + 
  plot_annotation(
    title = "Mortality Odds"
    , subtitle = "Conscientiousness x Marital Status"
    , theme = my_theme()
    , tag_levels = 'A'
    ) 

patchwork: Piecing the Plots Together

We can add labels to plot using plot_annotation()

p1 + p2 + 
  plot_layout(widths = c(6,4)) + 
  plot_annotation(
    title = "Mortality Odds"
    , subtitle = "Conscientiousness x Marital Status"
    , theme = my_theme()
    , tag_levels = 'A'
    , tag_prefix = 'Fig. '
    , tag_suffix = ':'
    ) & 
  theme(plot.tag = element_text(size = 8, face = "bold"))

Example 2 Setup: Simple Effects

pred_fun <- function(m){
  m$data %>%
    data_grid(married, p_value = seq_range(p_value, n = 100)) %>%
    drop_na() %>%
    augment(m
            , newdata = .
            , se_fit = T
            , type.predict = "response"
            )
}

nested_m <- nested_m %>%
  mutate(pred = map(m, pred_fun)) 
nested_m
# A tibble: 6 × 5
  study  data                  m      tidy             pred              
  <chr>  <list>                <list> <list>           <list>            
1 Study1 <tibble [831 × 24]>   <glm>  <tibble [4 × 9]> <tibble [200 × 4]>
2 Study2 <tibble [1,000 × 24]> <glm>  <tibble [4 × 9]> <tibble [200 × 4]>
3 Study3 <tibble [1,000 × 24]> <glm>  <tibble [4 × 9]> <tibble [200 × 4]>
4 Study4 <tibble [574 × 24]>   <glm>  <tibble [4 × 9]> <tibble [200 × 4]>
5 Study5 <tibble [616 × 24]>   <glm>  <tibble [4 × 9]> <tibble [200 × 4]>
6 Study6 <tibble [1,000 × 24]> <glm>  <tibble [4 × 9]> <tibble [200 × 4]>

Example 2 Setup: Simple Effects

nested_m %>% 
  mutate(df.resid = map_dbl(m, df.residual)) %>%
  select(study, pred, df.resid) %>%
  unnest(pred) 
# A tibble: 1,200 × 6
   study  married p_value .fitted .se.fit df.resid
   <chr>  <fct>     <dbl>   <dbl>   <dbl>    <dbl>
 1 Study1 0         0       0.571  0.0957      827
 2 Study1 0         0.101   0.568  0.0945      827
 3 Study1 0         0.202   0.565  0.0933      827
 4 Study1 0         0.303   0.563  0.0920      827
 5 Study1 0         0.404   0.560  0.0908      827
 6 Study1 0         0.505   0.557  0.0896      827
 7 Study1 0         0.606   0.555  0.0883      827
 8 Study1 0         0.707   0.552  0.0871      827
 9 Study1 0         0.808   0.550  0.0858      827
10 Study1 0         0.909   0.547  0.0845      827
# ℹ 1,190 more rows

Example 2 Setup: Simple Effects

Let’s set up the core part of the simple effects

p3 <- nested_m %>% 
  mutate(df.resid = map_dbl(m, df.residual)) %>%
  select(study, pred, df.resid) %>%
  unnest(pred) %>%
  mutate(married = factor(married, c(0,1), c("Never Married", "Married"))) %>%
  ggplot(aes(x = p_value, y = .fitted, fill = study, color = study)) + 
  labs(x = "Conscientiousness (POMP, 0-10)"
       , y = "Predicted Odds Ratio\nof Mortality (95% CI)"
       , fill = NULL
       , color = NULL) + 
  facet_grid(~married) + 
  my_theme()  
p3

Example 2 Setup: Simple Effects

Add in our lineribbon:

p3 <- p3 + 
  stat_lineribbon(
      aes(
        ydist = dist_student_t(
          df = df.resid
          , mu = .fitted
          , sigma = .se.fit
          ))
      , alpha = .25
      , .width = c(.95,.99)
      ) + 
    scale_fill_brewer(palette = "Set2") +
    scale_color_brewer(palette = "Dark2") 
p3

Examples 1 + 2 Combined

(p1 + p2) / p3

Examples 1 + 2 Combined

(p1 + p2) / p3 + 
  plot_layout(widths = c(6,4)) + 
  plot_annotation(
    title = "Mortality Odds"
    , subtitle = "Conscientiousness x Marital Status"
    , theme = my_theme()
    ) 

Examples 1 + 2 Combined

(p1 + p2) / p3 + 
  plot_layout(widths = c(6,4)) + 
  plot_annotation(
    title = "Mortality Odds"
    , subtitle = "Conscientiousness x Marital Status"
    , theme = my_theme()
    ) + 
  plot_layout(guides = 'collect')

cowplot

New grobs for drawing on our plots

  • Relative to patchwork, cowplot also adds some other new tools to our repertoire:
    • ggdraw()
    • draw_label()
    • draw_plot_label()
    • draw_grob()
    • draw_image()

New grobs: ggdraw() + draw_label()

  • ggdraw() is more or a setup function that allows us to add grobs on top
  • We’ll use it with draw_label() to make our title (just some text to put on the plot)

New grobs: ggdraw() + draw_label()

It’d be nice if the title was centered, right?

(p1 +
  labs(
    subtitle = "Conscientiousness x Marital Status"
    , title = "Mortality Odds"
    )) + 
  p2 + 
  plot_layout(widths = c(6,4))

New grobs: ggdraw() + draw_label()

It’d be nice if the title was centered, right?

p1 + p2 + plot_layout(widths = c(6,4))

New grobs: ggdraw() + draw_label()

We could use cowplot::draw_label() to add a title and subtitle to our plot:

title <- ggdraw() + 
  draw_label(
    "Mortality Odds"
    , fontface = 'bold'
    , x = .5
    , hjust = .5
    , y = .8
  ) +
  draw_label(
    "Conscientiousness x Marital Status"
    , fontface = 'italic'
    , x = .5
    , hjust = .5
    , y = .2
  ) +
  theme(plot.margin = margin(0, 0, 0, 7))
title

New grobs: ggdraw() + draw_label()

p <- title / (p1 + p2) + 
  plot_layout(
    widths = c(6,4)
    , heights = c(1,9)
    )
p

New grobs:draw_label()

  • draw_label() is meant to be a better wrapper for geom_text() that requires less customization
  • Say for example, we want to put a wordmark on our plots (there are journals that require this!)
  • Doing this with geom_text() would require 10+ arguments and has no easy application to figures put together with cowplot (or other packages for doing so)

New grobs:draw_label()

ggdraw(p) + 
  draw_label("Draft", color = "grey80", size = 100, angle = 45)

New grobs:draw_label()

Imagine you want to put a plot inside of another

inset <- 
  pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(y = gender, x = SRhealth, fill = gender)) + 
    scale_fill_manual(values = c("cornflowerblue", "coral")) + 
    scale_y_discrete(labels = c("Male", "Female")) + 
    stat_halfeye(alpha = .8) + 
    my_theme() + 
    theme(legend.position = "none") + theme_half_open(12)
p4 <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = p_value, SRhealth, fill = gender)) + 
    geom_point(shape = 21, color = "grey20", size = 2, alpha = .5) + 
    scale_fill_manual(values = c("cornflowerblue", "coral"), labels = c("Male", "Female")) + 
    my_theme()

New grobs:draw_label()

Imagine you want to put a plot inside of another

ggdraw(p4) + 
  draw_plot(inset, .1, .2, .6, .4)

New grobs:draw_label()

We can also add images!

p

New grobs:draw_label()

We can also add images!

ggdraw() + 
  draw_plot(p) + 
  draw_image(
    "https://github.com/emoriebeck/psc290-data-viz-2022/raw/main/01-week1-intro/02-code/02-images/ucdavis_logo_blue.png"
    , x = 1
    , y = 0.05
    , hjust = 1
    , vjust = 1
    , halign = 1
    , valign = 1
    , width = 0.15
  )

Extra Slides: cowplot::plot_grid()

plot_grid()

  • The core function of cowplot is plot_grid(), which allows us to place differnt figures within the same figure in a grid, and it has a lot of useful arguments
  • It’s the alternative to +, / in `patchwork
  • plotlist = NULL
  • align = c("none", "h", "v", "hv")
  • axis = c("none", "l", "r", "t", "b", "lr", "tb", "tblr")
  • nrow = NULL
  • ncol = NULL
  • rel_widths = 1
  • rel_heights = 1
  • labels = NULL
  • label_size = 14
  • label_fontfamily = NULL
  • label_fontface = "bold"
  • label_colour = NULL
  • label_x = 0
  • label_y = 1
  • hjust = -0.5
  • vjust = 1.5
  • scale = 1
  • greedy = TRUE
  • byrow = TRUE
  • cols = NULL
  • rows = NULL

plot_grid()

  • But now that we have our plot, we want to put it together! Remember these?
  • plotlist = NULL
  • align = c("none", "h", "v", "hv")
  • axis = c("none", "l", "r", "t", "b", "lr", "tb", "tblr")
  • nrow = NULL
  • ncol = NULL
  • rel_widths = 1
  • rel_heights = 1
  • labels = NULL
  • label_size = 14
  • label_fontfamily = NULL
  • label_fontface = "bold"
  • label_colour = NULL
  • label_x = 0
  • label_y = 1
  • hjust = -0.5
  • vjust = 1.5
  • scale = 1
  • greedy = TRUE
  • byrow = TRUE
  • cols = NULL
  • rows = NULL

plot_grid()

plot_grid(
  p1, p2
)

Not bad, but we want to align our plots

plot_grid()

plot_grid(p1, p2, align = "h")

plot_grid(p1, p2, align = "v")

plot_grid(p1, p2, align = "hv")

Similar behavior, but "hv" leads to odd spacing

plot_grid()

plot_grid(p1, p2, axis = "t")

plot_grid(p1, p2, axis = "b")

plot_grid(p1, p2, axis = "tblr")

Doesn’t properly align our bottom because it’s not optimized for labels

plot_grid()

plot_grid(
  p1, p2
  , align = "h"
  , nrow = 1
  , rel_widths = c(.6, .4)
  )

Let our interval estimates shine

plot_grid()

plot_grid(
  p1, p2
  , align = "hv"
  , nrow = 2
  , rel_heights = c(.6, .4)
  )

We wouldn’t do this, but note that when we have rows, we use rel_heights

plot_grid(): Labels

plot_grid(
  p1, p2
  , align = "h"
  , nrow = 1
  , rel_widths = c(.6, .4)
  , labels = "auto"
  )

plot_grid(): Labels

plot_grid(
  p1, p2
  , align = "h"
  , nrow = 1
  , rel_widths = c(.6, .4)
  , labels = "AUTO"
  , label_size = 18 # 14 default
  , label_fontface = "bold.italic"
  , label_fontfamily = "Times"
  , label_colour = "purple" # u is sensitive
  )

plot_grid(): Labels

plot_grid(
  p1, p2
  , align = "h"
  , nrow = 1
  , rel_widths = c(.6, .4)
  , labels = "AUTO"
  , label_size = 18 # 14 default
  , label_fontface = "bold.italic"
  , label_fontfamily = "Times"
  , label_colour = "purple" # u is sensitive
  , label_x = .5
  , label_y = .5
  )

plot_grid(): Labels

plot_grid(
  p1, p2
  , align = "h"
  , nrow = 1
  , rel_widths = c(.6, .4)
  , labels = "AUTO"
  , label_size = 18 # 14 default
  , label_fontface = "bold.italic"
  , label_fontfamily = "Times"
  , label_colour = "purple" # u is sensitive
  , label_x = c(.1,.85)
  , label_y = c(.95,.1)
  )

plot_grid(): Labels

plot_grid(
  p1, p2
  , align = "h"
  , nrow = 1
  , rel_widths = c(.6, .4)
  , labels = "AUTO"
  , label_size = 18 # 14 default
  , label_fontface = "bold.italic"
  , label_fontfamily = "Times"
  , label_colour = "purple" # u is sensitive
  , hjust = .5
  , vjust = .5
  )