Search code examples
rggplot2scale-color-manual

ggplot2 stacked column: How do I sort by date, use custom colors and change the legend?


Separately these seem easy, but I can only complete two of the requirements before breaking the third. What I want to do with a stacked column chart:

  1. Group by week and order the stacked bar chart with the earliest date (Thursday) on the bottom and the last date (Wed) on top.

  2. Custom color each day.

  3. Label the legend with the tp_codes.

Here is the data and an example of a regular bar chart that I would like to "flip" into a stacked bar chart with 3 columns - one for each week - with the bottom most data the first day of the week (Thur).


# DATA -----------------------------------------------------------------------------------------------------------------
session_df <- structure(list(date = structure(c(19570, 19571, 19572, 19573, 
  19574, 19575, 19576, 19577, 19578, 19579, 19580, 19581, 19582, 
  19583), class = "Date"), metric = c(2, 8, 12, 18, 6, 12, 20, 
  2, 6, 9, 7, 19, 4, 17), tp_code = c("Off", "ReEntry", "Strength", 
  "Match 1", "Recovery", "Activation", "Match 2", "Off", "Strength", 
  "Speed", "Activation", "Match 1", "ReEntry-Activation", "Match 2"
  ), week = structure(c(19566, 19566, 19566, 19573, 19573, 19573, 
  19573, 19573, 19573, 19573, 19580, 19580, 19580, 19580), class = "Date"), 
      tp_color = c("grey", "lightgreen", "blue", "darkgreen", "chartreuse", 
      "lightyellow", "darkgreen", "grey", "blue", "yellow", "lightyellow", 
      "darkgreen", "lightgreen", "darkgreen")), row.names = c(NA, 
  -14L), class = c("tbl_df", "tbl", "data.frame"))

# ALMOST CORRECT PLOT --------------------------------------------------------------------------------------------------

# Pull out custom colors + codes to pass to ggplot scale_color_manual()
# via https://stackoverflow.com/questions/68557812/use-custom-color-and-custom-label-from-dataframe-columns-in-ggplot2
cols <- distinct(session_df, tp_code, tp_color) |> deframe()

session_df |>
  ggplot(aes(x = date, y = metric, fill = tp_code)) +
  geom_bar(position='stack', stat='identity') +
  scale_color_manual(aesthetics = 'fill',
                     values = cols
                     )+
  facet_wrap(~week)

bar chart intended to turn into stacked bar chart I've been using this plot to see if I can tease out why things aren't displaying in the correct order. For instance, Attempt 2 below colors each day correctly, stacks Week 1 (07-28) in the correct order, but Week 2 (08-04) and Week 3 (08-11) are stacked in reverse order. Comments in code hopefully give enough evidence of what is wrong with each chart.


# PRE-PLOT -------------------------------------------------------------------------------------------------------------

cols <- distinct(session_df, date, tp_color) |> deframe()
labs <- distinct(session_df, date, tp_code) |> deframe() # not necessary here but may come in handy to change label name

# ATTEMPT 1 ------------------------------------------------------------------------------------------------------------
# Dates are stacked in the correct order but colors do not match 
# Attempting to reverse the colors does not seem to work. Note: colors are reversed for entire df, but not for each week
# Legend needs to have tp_code, not factor(date)
session_df |>
  ggplot(aes(x = week, y = metric, fill = factor(rev(date)))) +
  geom_bar(position='stack', stat='identity') +
  scale_color_manual(aesthetics = 'fill',
                     values = cols # this doesn't work
                   # values = rev(cols) # this doesn't work either
                     ) +
  theme_minimal()

stacked bar incorrect color


# ATTEMPT 2 ------------------------------------------------------------------------------------------------------------

# Only change was the remove rev() from `factor(rev(date))` above
# This correctly colors the dates, but days are stacked in the rev order in all three weeks 
  # (Thur is on top, should be on bottom)
# Attempting to rev `cols` does not change the color
# Legend needs to have tp_code, not factor(date)

session_df |>
  ggplot(aes(x = week, y = metric, fill = factor(date))) + #
  geom_bar(position='stack', stat='identity') +
  scale_color_manual(aesthetics = 'fill',
                     values = cols # this doesn't work
                     ) +
  theme_minimal()

stacked bar stack order incorrect

# ATTEMPT 3 ------------------------------------------------------------------------------------------------------------

# Instead of `fill = date` changed to `fill = tp_code`

# Colors attached to `tp_code` here. Above examples colors were attached to date
cols <- distinct(session_df, tp_code, tp_color) |> deframe()

# This displays the legend correctly with tp_code
# This is colored correctly
# The stack order is incorrect for the second and third week only, which are randomly stacked...
session_df |>
  mutate(tp_code = factor(tp_code, levels = rev(unique(tp_code)))) |>
  ggplot(aes(x = week, y = metric, fill = tp_code)) +
  geom_bar(position='stack', stat='identity') +
  scale_color_manual(aesthetics = 'fill',
                     values = cols
                     )+
  theme_minimal()

stacked bar order incorrect

I am guessing my lack of understanding of the underlying structure of the stacks and the parameters needed to be passed into scale_color_manual are to be blamed.


Solution

  • To achieve your desired result map tp_code on the fill aes but map date on the group aes to order or stack your bars by date. Finally use position = position_stack(reverse = TRUE) to reverse the order of the stack if desired.

    Note: I added a geom_label layer to check that the dates are in the desired order.

    library(ggplot2)
    library(dplyr)
    library(tibble)
    
    cols <- distinct(session_df, tp_code, tp_color) |> deframe()
    
    session_df |>
      ggplot(aes(x = week, y = metric, fill = tp_code, group = date)) +
      geom_col(position = position_stack(reverse = TRUE)) +
      geom_label(aes(label = date),
        position = position_stack(reverse = TRUE),
        size = 8 / .pt, fill = "white"
      ) +
      scale_fill_manual(
        values = cols
      ) +
      theme_minimal()
    

    enter image description here