Let's say I need to plot two sets ("types") of data with the same units, where each type has a group a and group b. I want group (a,b) to correspond to color, but I want the type (A, B) to correspond to the geom (say, path for A and point for B). How can I build a plot like this?
library(ggplot2)
library(dplyr)
set.seed(22)
# Example data
df <- data.frame(
x = rep(1:10, 2),
y = c(runif(10, 1, 3), runif(10, 3, 5)),
pVar = rep(c("A", "B"), each = 10),
cVar = rep(c("a", "b"), 10)
) %>%
mutate(grp = paste(pVar, cVar, sep = "_"))
Let's say I need to plot two sets ("types") of data with the same units, where each type has a group a and group b. I want group (a,b) to correspond to color, but I want the type (A, B) to correspond to the geom (say, path for A and point for B). How can I build a plot like this?
library(ggplot2)
library(dplyr)
set.seed(22)
# Example data
df <- data.frame(
x = rep(1:10, 2),
y = c(runif(10, 1, 3), runif(10, 3, 5)),
pVar = rep(c("A", "B"), each = 10),
cVar = rep(c("a", "b"), 10)
) %>%
mutate(grp = paste(pVar, cVar, sep = "_"))
Share
Improve this question
asked Jan 29 at 20:19
Michael RoswellMichael Roswell
1,48215 silver badges38 bronze badges
2 Answers
Reset to default 4I struggled with this for quite a while, and stumbled on a solution that feels pretty good. Rather than subsetting the data argument for each geom, I use the alpha
aesthetic to render the off-type invisible, and then construct the appropriate legend inside scale_alpha_identity()
.
library(ggplot2)
library(dplyr)
set.seed(22)
# Example data
df <- data.frame(
x = rep(1:10, 2),
y = c(runif(10, 1, 3), runif(10, 3, 5)),
pVar = rep(c("A", "B"), each = 10),
cVar = rep(c("a", "b"), 10)
) %>%
mutate(grp = paste(pVar, cVar, sep = "_"))
# set point size
sz <- 4
# Base plot
ggplot(df, aes(x, y, group = grp)) +
# Path for group A (cVar) with color mapping for group (cVar)
geom_path(aes(color = cVar
, alpha = as.numeric(pVar == "A")
, group = grp)
, linewidth = 1) +
# Points for group B (pVar) with fill based on cVar
geom_point(aes(fill = cVar
, alpha = as.numeric(pVar == "B")
, group = grp
)
, shape = 21
, size = sz
, color = scales::alpha("white", alpha = 0)
) +
# set up a guide for the plotting type, using the alpha scale that renders
# outside type invisible
scale_alpha_identity(breaks = c(0,1)
, labels = c("A", "B")
, guide = guide_legend(title = "type"
, override.aes = list(
linewidth = c(0.8, 0)
, shape = c(15, 21)
, linetype = c("solid", "blank")
, fill = c(NA, "grey")
, size = c(0, sz)
, alpha = c(1)
)
)
) +
theme_classic()
I would also use a fake alpha scale to get the nice legend, but I would first try pivoting to make two y-variables:
library(ggplot2); library(tidyr)
df |>
pivot_wider(id_cols = c(x, cVar), names_from = pVar, values_from = y) |>
ggplot(aes(x, color = cVar)) +
geom_path(aes(y = A, alpha = 'A')) +
geom_point(aes(y = B, alpha = 'B')) +
scale_alpha_discrete(range = c(1, 1))
Otherwise I still find the data subsetting easier in general:
ggplot(df, aes(x, y, color = cVar, alpha = pVar)) +
geom_path(data = \(d) filter(d, pVar == 'A')) +
geom_point(data = \(d) filter(d, pVar == 'B')) +
scale_alpha_discrete(range = c(1, 1))