rggplot2facet-grid

ggplot: adding data from a different data frame as an additional facet grid


I have the following data and code:

#first data set
channel <- c("Channel 1", "Channel 1", "Channel 1", "Channel 2", "Channel 2", "Channel 2", "Channel 2", "Channel 3", "Channel 3", "Channel 3")
start_time <- c(0.000, 9.719, 11.735, 14.183, 16.554, 18.482, 0.000, 11.693, 12.310, 13.912)
stop_time <- c(9.719, 11.735, 14.183, 16.554, 18.482, 19.553, 11.693, 12.310, 13.912, 15.406)
character <- c("A", "B", "C", "C", "B", "A", "A", "B", "C", "B")

df1 <- data.frame(channel, start_time, stop_time, character)

library(ggplot2)
ggplot(df1, aes(fill = character, color = character)) +
  geom_rect(color = NA, aes(xmin = start_time, xmax = stop_time, ymin = -0.5, ymax = 0.5)) +
  scale_x_continuous(name = "Time (sec)", breaks = scales::pretty_breaks(n = 10)) +
  facet_grid(channel ~ .) +
  theme(axis.text.y=element_blank(),
        axis.ticks.y=element_blank() ) + theme(legend.position="bottom") 

This produces a plot that looks like this:

enter image description here

I would like to add, another row (facet?) to the grid. The data for that row looks like this:

#second data set
start_time_ep <- c(0, 5, 10, 15)
stop_time_ep <- c(5, 10, 15, 20)
episode <- c("episode 1", "episode 2", "episode 3", "episode 4")

df2 <- data.frame(start_time_ep, stop_time_ep, episode)

I want this data to be its own row with the label "Episode", it would likewise show a set of coloured blocks.

At, first, I tried just integrating this data into the first data frame, and giving them all the same "channel" vector value, as follows:

#trying all together

channel <- c("Channel 1", "Channel 1", "Channel 1", "Channel 2", "Channel 2", "Channel 2", "Channel 2", "Channel 3", "Channel 3", "Channel 3", "Episode", "Episode", "Episode", "Episode")
start_time <- c(0.000, 9.719, 11.735, 14.183, 16.554, 18.482, 0.000, 11.693, 12.310, 13.912, 0, 5, 10, 15)
stop_time <- c(9.719, 11.735, 14.183, 16.554, 18.482, 19.553, 11.693, 12.310, 13.912, 15.406, 5, 10, 15, 20)
character <- c("A", "B", "C", "C", "B", "A", "A", "B", "C", "B", "episode 1", "episode 2", "episode 3", "episode 4")

df1 <- data.frame(channel, start_time, stop_time, character)

library(ggplot2)
ggplot(df1, aes(fill = character, color = character)) +
  geom_rect(color = NA, aes(xmin = start_time, xmax = stop_time, ymin = -0.5, ymax = 0.5)) +
  scale_x_continuous(name = "Time (sec)", breaks = scales::pretty_breaks(n = 10)) +
  facet_grid(channel ~ .) +
  theme(axis.text.y=element_blank(),
        axis.ticks.y=element_blank() ) + theme(legend.position="bottom") 

This produces the following plot:

another plot

This is kind of what I want, but I don't like that it treats the "episode" values the same as the "character" values. I want them in their own legends.

My own reserach suggests using either geom_step or geom_line, specifiying different data frames for each lines data argument. (Cf. this post). But I cannot, for the life of me, get the syntax right with a facet_grid style plot like I'm shooting for here.

What is the the best way to do this?


Solution

  • If you want to avoid other packages, you could use a combination of geom_linerange with a colour aesthetic and geom_rect with a fill aesthetic to get two separate legends:

    df1$episode <- NA
    df3 <- rbind(df1, cbind(channel = "Episode", df2[1:2], char = NA, df2[3]) |>
               setNames(names(df1)))
    df3 <- within(df3, channel <- factor(channel, rev(unique(channel))))
    
    ggplot(df3, aes(y = channel)) +
      geom_blank() +
      geom_linerange(aes(xmin = start_time, xmax = stop_time,
                         color = character),
                data = subset(df3, !is.na(character)), linewidth = 18) +
      geom_rect(aes(xmin = start_time, xmax = stop_time, 
                    ymin = 0.8, ymax = 1.2,
                    fill = episode), 
                data = subset(df3, is.na(character))) +
      scale_x_continuous(name = "Time (sec)", 
                         breaks = scales::pretty_breaks(n = 10)) +
      scale_fill_manual(values = c( "yellow", "orange", "red", "purple")) +
      guides(color = guide_legend(override.aes = list(linewidth = 7),
                                  title.position = "top", direction = "vertical"),
             fill = guide_legend(title.position = "top", direction = "vertical")) +
      theme_minimal(base_size = 16) +
      theme(legend.position = "bottom")
    

    enter image description here