How to add legend to plot with multiple lines using ggplot

So, this is the data that I have:

x <- seq(-5, 5, length=1000)
y1 <- dnorm(x, 0, 0.2)
y2 <- dnorm(x, 0, 1)

data1 <- data.frame(x, y1, y2)

I generate a graph with an example of the distribution of two variables, and I generate the graph like so:

p <- data1 %>% ggplot()
p + geom_line(aes(x, y1), linetype = 2) + geom_line(aes(x, y2)) + geom_line(aes(x, y= 0)) + 
  geom_text(aes(x = 0, y = -0.05, label = "Party A"), nudge_x = 0.5) + 
  geom_text(aes(x = 0, y = -0.05, label = "Party B"), nudge_x = - 0.5) +
  xlab("Ideological scale: Left to Right") +  
  ylab("Prabability") 

I am trying to add the legend showing that the dashed line is the distribution of party A and the solid line is the distribution of party B. How can I add the legend?

>Solution :

ggplot2 really prefers "long data". I’ll use tidyr::pivot_longer here (as well as dplyr for some massaging) to convert from wide to long, but it can also easily be done with data.table or (with a touch more work) base R.

A quick demo of wide-to-long:

library(tidyr)
library(dplyr)
pivot_longer(data1, -x, names_to = "y", values_to = "val") |>
  mutate(lbl = if_else(y == "y1", "Party A", "Party B"))
# # A tibble: 2,000 × 4
#        x y           val lbl    
#    <dbl> <chr>     <dbl> <chr>  
#  1 -5    y1    3.83e-136 Party A
#  2 -5    y2    1.49e-  6 Party B
#  3 -4.99 y1    1.34e-135 Party A
#  4 -4.99 y2    1.56e-  6 Party B
#  5 -4.98 y1    4.65e-135 Party A
#  6 -4.98 y2    1.64e-  6 Party B
#  7 -4.97 y1    1.62e-134 Party A
#  8 -4.97 y2    1.73e-  6 Party B
#  9 -4.96 y1    5.59e-134 Party A
# 10 -4.96 y2    1.81e-  6 Party B
# # ℹ 1,990 more rows
# # ℹ Use `print(n = ...)` to see more rows

This allows us to have simpler geom_* calls, typically down to a single. I’ll also infer that we can replace your geom_line(aes(x, y=0)) to geom_hline, in order to add the horizontal line.

library(ggplot2)
pivot_longer(data1, -x, names_to = "y", values_to = "val") |>
  mutate(lbl = if_else(y == "y1", "Party A", "Party B")) |>
  ggplot(aes(x, val)) +
  geom_line(aes(group = y, linetype = lbl)) +
  geom_hline(yintercept = 0) +
  geom_text(
    aes(label = lbl),
    data = ~ filter(., val > max(val)/2, .by = y) %>% slice_head(n = 1, by = lbl),
    hjust = 1.2
  ) +
  scale_linetype_manual(values = c("Party A"=2, "Party B"=1))

ggplot2 with labels and legend

I also add the label programmatically by updating the data= inside the geom_text call, using the ~-style "function" expression.

Leave a Reply