Robert Hickman
Robert Hickman

Reputation: 907

Dodge two different geoms apart in ggplot2

Let's say I have two different sources of data. One is of repeated observations, and one is just a mean +/- standard error predicted by a model.

n <- 30
obs <- data.frame(
  group = rep(c("A", "B"), each = n*3),
  level = rep(rep(c("low", "med", "high"), each = n), 2),
  yval = c(
    rnorm(n, 30), rnorm(n, 50), rnorm(n, 90),
    rnorm(n, 40), rnorm(n, 55), rnorm(n, 70)
  )
) %>%
  mutate(level = factor(level, levels = c("low", "med", "high")))

model_preds <- data.frame(
  group = c("A", "A", "A", "B", "B", "B"),
  level = rep(c("low", "med", "high"), 2),
  mean = c(32,56,87,42,51,74),
  sem = runif(6, min = 2, max = 5)
)

now I can plot these on the same graph easily enough

p <- ggplot(obs, aes(x = level, y = yval, fill = group)) +
  geom_boxplot() +
  geom_point(data = model_preds, aes(x = level, y = mean), size = 2, colour = "forestgreen") +
  geom_errorbar(data = model_preds, aes(x = level, y = mean, ymax = mean + sem, ymin = mean - sem), colour = "forestgreen", size = 1) +
  facet_wrap(~group)

enter image description here

and use that the visually look at the difference between the model predictions and the observed results.

But I think this looks a bit ugly, so ideally would want to 'dodge' the point-and-errorbars geom(s) from the boxplot geom.

If you'll forgive my quick paint drawing, something like this: enter image description here

It seems like position_dodge() might be the way to go but I haven't figured out how to combine two different geoms this way and the docs don't have any examples.

Might be that it's impossible, but thought I'd ask to check

Upvotes: 5

Views: 1302

Answers (1)

teunbrand
teunbrand

Reputation: 38003

As a consequence of the grammer of graphics, which clearly separates various aspects of plotting, there is no way to communicate information between different layers (geoms and stats) of a plot. This also means that a position adjustment cannot be shared across layers, such that they can be dodged in a multi-layer fashion.

The next best thing you could do, is to use position = position_nudge() in every layer, so that across the layers they seem dodged. You might also want to adjust the width parameter of the boxplot and errorbar for this. Example below:

library(tidyverse)

n <- 30
obs <- data.frame(
  group = rep(c("A", "B"), each = n*3),
  level = rep(rep(c("low", "med", "high"), each = n), 2),
  yval = c(
    rnorm(n, 30), rnorm(n, 50), rnorm(n, 90),
    rnorm(n, 40), rnorm(n, 55), rnorm(n, 70)
  )
) %>%
  mutate(level = factor(level, levels = c("low", "med", "high")))

model_preds <- data.frame(
  group = c("A", "A", "A", "B", "B", "B"),
  level = rep(c("low", "med", "high"), 2),
  mean = c(32,56,87,42,51,74),
  sem = runif(6, min = 2, max = 5)
)

ggplot(obs, aes(x = level, y = yval, fill = group)) +
  geom_boxplot(position = position_nudge(x = -0.3),
               width = 0.5) +
  geom_point(data = model_preds, aes(x = level, y = mean), 
             size = 2, colour = "forestgreen",
             position = position_nudge(x = 0.3)) +
  geom_errorbar(data = model_preds, 
                aes(x = level, y = mean, ymax = mean + sem, ymin = mean - sem), 
                colour = "forestgreen", size = 1, width = 0.5,
                position = position_nudge(x = 0.3)) +
  facet_wrap(~group)

Created on 2021-01-17 by the reprex package (v0.3.0)

Upvotes: 5

Related Questions