Piecing Plots Together

Author

Emorie D Beck

Piecing Plots Together

Packages

Code
# | 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:

Code
my_theme <- function(){
  theme_classic() + 
  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 **
    • 11/21: Interactive Visualizations (shiny)

Today

  • There are lots of packages for piecing visualizations together
  • I have used lots and the only one that I can say I’ve actually liked in cowplot, so I’m going to teach you that
  • There are other more specialized packages worth mentioning
  • Here is a short list of some core ggplot2 extensions: https://exts.ggplot2.tidyverse.org/gallery/
  • We’ll cover:
    • ggExtra
    • cowplot (and lots of assortments)
    • patchwork

ggExtra

  • We’ll start with ggExtra because it will help us create plots with distributions in the margins.
  • After, we’ll move to cowplot, where there will be lots of little odds and ends to step through
  • Remember these data?
Code
load(url("https://github.com/emoriebeck/psc290-data-viz-2022/blob/main/04-week4-associations/04-data/week4-data.RData?raw=true"))
pred_data

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

Code
p <- pred_data %>% 
  filter(study == "Study1") %>%
  ggplot(aes(x = p_value, y = SRhealth, color = gender, fill = gender)) + 
    geom_point(
      , color = "grey20"
      , shape = 21, size = 3
      ) + 
    scale_fill_manual(
      values = c("cornflowerblue", "coral")
      , labels = c("Male", "Female")
      ) + 
    labs(
      x = "Conscientiousness (POMP, 0-10)"
      , y = "Self-Rated Health (POMP, 0-10)"
      , fill = "Gender"
    ) + 
    my_theme()
p

And add in a smoothed association and custom colors and labels

Code
p <- p + 
  geom_smooth(
    , method = "lm"
    ) + 
  scale_color_manual(
    values = c("cornflowerblue", "coral")
    , labels = c("Male", "Female")
    ) + 
  labs(color = "Gender")
p

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

Code
ggMarginal(p)

This is fine, but we can do better!

Let’s try color

Code
ggMarginal(p, color = "seagreen")

Let’s try fill

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

Let’s try a histogram

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

And group-based fill and color

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

This is not expected behavior, so let’s move on to patchwork and cowplot where we can make these much more flexibly with just a few extra lines of code

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

Piecing the Plots Together

First, let’s build the x and y marignals

Code
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

Code
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

With patchwork, we can use the + and / operators to arrange them:

Code
px / (p + py)

That arrangement isn’t quite right, so let’s use plot_layout() to create a custom layout:

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

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

Those legends are messing us up! Let’s use guides = "collect" within plot_layout(). Then we’ll use the & to add a theme to the whole plot:

Code
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

So let’s remove it:

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

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

Let’s do the same thing but with geom_boxplot() and geom_jitter()

Code
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

Code
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

And let’s put it back together

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

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

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

cowplot

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

From Beck & Jackson (2020):

From Beck et al. (under review):

From Beck & Jackson (2022):

Example: Forest Plots

  • Let’s build up our use cases incrementally!
  • But first, we need some plots to plot!

Models

And remember these models?

Code
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

Let’s do one small change

Code
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

Here’s our unnested model terms

Code
nested_m %>% select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp)

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

Code
nested_m %>% select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1")
  • 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

Forest Plot

Code
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

Let’s add our point estimates and uncertainty intervals

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

Code
p1

But we want to order the terms by their effect size:

Code
p1 <- nested_m %>% select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1")

p1 <- p1 %>% 
  mutate(study = factor(study, (p1 %>% arrange(desc(estimate)))$study)) %>%
  ggplot(aes(x = estimate, y = study)) + 
    labs(
      x = "Model Estimated OR (CI)"
      , y = NULL
      ) + 
    my_theme()
p1

Now, let’s re-add our point estimates and uncertainty intervals

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

And add a vertical line at one (no higher or lower odds)

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

Forest Plot Table

In a forest plot, we don’t just show estimates, we print them with the sample size

Code
p2 <- nested_m %>% select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1")

stdy_levs <-  tibble(num = 1:6, new = (p2 %>% arrange(desc(estimate)))$study)

p2 <- p2 %>%
  arrange(desc(estimate)) %>%
  mutate(study = factor(study, stdy_levs$new)
         , 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

Add labels and themes:

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

Add the label text:

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

Change to theme_void()

Code
p2 <- p2 + 
  theme_void()
p2

Add a top line and re-add the bottom axis line:

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

And annotate() the column levels back in

Code
# "My~bold(Partly~Bold)~and~italic(Partly~Italic)~Text"
p2 <- p2 + 
  annotate("text"
           , x = "est" , y = 7
           , label = "b [CI]"
           , fontface = "bold"
           ) + 
  annotate("text"
           , x = "n", y = 7
           , label = "N"
           , fontface = "bold"
           ) 
p2

Change the scale limits to improve the figure

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

Back to the Forest Plot

We added an extra row at the top of the table, so we need to do that for the forest plot, too

Code
p1 <- nested_m %>% select(study, tidy) %>%
  unnest(tidy) %>%
  mutate_at(vars(estimate, conf.low, conf.high), exp) %>%
  filter(term == "p_value:married1")

stdy_levs <-  tibble(num = 1:6, new = (p1 %>% arrange(desc(estimate)))$study)

p1 <- p1 %>%
  arrange(desc(estimate)) %>%
  mutate(study = factor(study, stdy_levs$new)
         , study2 = 1:n()) %>%
  ggplot(aes(x = estimate, y = study2)) + 
    labs(
      x = "Model Estimated OR (CI)"
      , y = NULL
      ) + 
    my_theme()
p1

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

Code
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

Change the y scale back to match the study labels

Code
p1 <- p1 + 
  scale_y_continuous(limits = c(.4,7.1)
                     , breaks = seq(1,6,1)
                     , labels = stdy_levs$new)
p1 

Add in that top bar to match the table

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

Remove the y axis line

Code
p1 <- p1 + 
  theme(axis.line.y = element_blank(), 
        axis.ticks.y = element_blank())
p1 

And let’s block out where the dashed line touches the top:

Code
p1 <- p1 + 
  annotate("rect"
           , xmin = -Inf
           , xmax = Inf
           , ymin = 6.51
           , ymax = Inf
           , fill = "white")
p1

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 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)

We can just use the + operator!

Code
p1 + p2

We can also add rows using the /

Code
p1 / p2

And change their arrangement using plot_layout()

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

And change their arrangement using plot_layout()

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

We can add titles using plot_annotation()

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

We can add labels to plot using plot_annotation()

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

And control additional elements with tag_prefix and tag_suffix()

Code
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 Setup: Simple Effects

But maybe we want to add the simple effects along with the forest plots of the interaction. Let’s set that up.

Code
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

We also want to bring the residual degrees of freedom in so that we can use them later in stat_lineribbon():

Code
nested_m %>% 
  mutate(df.resid = map_dbl(m, df.residual)) %>%
  select(study, pred, df.resid) %>%
  unnest(pred) 

Let’s save that and set up the basic core of the plot

Code
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()  

Now let’s use stat_lineribbon() and add in the color palettes for fill and color

Code
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

We can then use + to bring together the forest plot and table on the same row and / to put the simple effects plot on the next row.

Code
(p1 + p2) / p3

And use plot annotation to add in the title and subtitle

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

Let’s collect the legend to the bottom of the plot

Code
(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

cowplot provides lots of tools for sprucing up these plots.

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()

ggdraw() + draw_label()

*ggdraw()is more or a setup function that allows us to add grobs on top * We'll use it withdraw_label()` to make our title (just some text to put on the plot)

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

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

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

Code
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(
    # add margin on the left of the drawing canvas,
    # so title is aligned with left edge of first plot
    plot.margin = margin(0, 0, 0, 7)
  )
title

Now we can add that title in using patchwork (see below fo the cowplot version)

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

  • 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)
Code
ggdraw(p) + 
  draw_label("Draft", color = "grey80", size = 100, angle = 45)

  • Imagine you want to put a plot inside of another
  • First let’s set up the examples
Code
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 = 3) + 
    scale_fill_manual(values = c("cornflowerblue", "coral"), labels = c("Male", "Female")) + 
    my_theme()

And add the inset plot on (note, this is not a great example)

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

We can also add images!

Code
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

Let’s revisit how to put together our forest plot using cowplot instead of patchwork:

Code
plot_grid(
  p1, p2
)

Not bad, but we want to align our plots. We can do that with either align or axis:

align

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

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

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

Similar behavior, but "hv" leads to odd spacing

axis

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

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

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

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

Now let’s change the widths of the plots:

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

Let our interval estimates shine

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

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

plot_grid(): Labels

We can do lots with labels using cowplot::plot_grid()

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

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

Let’s have some fun and add some chaos: label_size, label_fontface, label_fontfamily, and label_colour (note the spelling)

Code
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
  )

And set the location using label_x and label_y

Code
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
  )

And let’s move them some more

Code
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)
  )