How to represent the difference between two time series with a bar plot in the same chart?

Viewed 861

For example, if we have two time series a and b:

time <- seq(as.Date("1999-06-15"),as.Date("2008-06-15") , by= "years")
a <- c(22.3,24.1,35,35,35.9,39.2,34.8,31.5,29.1,25.8)    
b <- c(22,24.9,31,34,37.5,36.3,32.1,29.7,28.6,23.9)
plot(as.Date(time),a,type="l",xlab="Date",ylab="T(°C)")
lines(as.Date(time),b,col=2)

Is there a way that my plot will look like the image example:

enter image description here

2 Answers

You could use ggplot2's geom_line and geom_col.

library(tidyverse)
DF_bar <- mutate(DF, diff_a_b = a - b)

DF %>% 
  gather(key, value, a, b) %>% 
  ggplot(., aes(time)) +
  geom_line(aes(y = value, col = key)) + 
  geom_col(data = DF_bar, aes(y = diff_a_b)) # or geom_bar(data = DF_bar, aes(y = diff_a_b), stat = "identity")

In the first step I created a new data set that contains the variable, diff_a_b, which is, well, the difference between a and b.

Next, I reshaped your data from wide to long such that we can map column key to the colour aesthetic in geom_line. Finally, I use DF_bar in geom_col to plot diff_a_b.

data

DF <- data.frame(time = seq(as.Date("1999-06-15"),as.Date("2008-06-15"), by= "years"),
                 a = c(22.3,24.1,35,35,35.9,39.2,34.8,31.5,29.1,25.8),
                 b = c(22,24.9,31,34,37.5,36.3,32.1,29.7,28.6,23.9))

enter image description here

Unfortunately, the first answer by markus (before the edit) contained a major flaw which has caused the bars showing the residuals to be twice as high as expected. This will become immediately visible when the filling of the bars is coloured according to key:

library(dplyr)
library(tidyr)
library(ggplot2)

data_frame(time, a, b) %>%
  mutate(diff_a_b = a - b) %>% 
  gather(key, value, a, b) %>% 
  ggplot(., aes(time)) +
  geom_line(aes(y = value, color = key)) + 
  geom_col(aes(y = diff_a_b, fill = key))

enter image description here

The root cause is that diff_a_b is not treated as variable when reshaping from wide to long format:

data_frame(time, a, b) %>%
  mutate(diff_a_b = a - b) %>% 
  gather(key, value, a, b)

So diff_a_b appears twice for each time value:

# A tibble: 20 x 4
   time       diff_a_b key   value
   <date>        <dbl> <chr> <dbl>
 1 1999-06-15    0.3   a      22.3
 2 2000-06-15   -0.800 a      24.1
 3 2001-06-15    4     a      35  
 4 2002-06-15    1     a      35  
 5 2003-06-15   -1.6   a      35.9
 6 2004-06-15    2.9   a      39.2
 7 2005-06-15    2.70  a      34.8
 8 2006-06-15    1.8   a      31.5
 9 2007-06-15    0.5   a      29.1
10 2008-06-15    1.9   a      25.8
11 1999-06-15    0.3   b      22  
12 2000-06-15   -0.800 b      24.9
13 2001-06-15    4     b      31  
14 2002-06-15    1     b      34  
15 2003-06-15   -1.6   b      37.5
16 2004-06-15    2.9   b      36.3
17 2005-06-15    2.70  b      32.1
18 2006-06-15    1.8   b      29.7
19 2007-06-15    0.5   b      28.6
20 2008-06-15    1.9   b      23.9

As the default for geom_col() is position = "stack" the two values are stacked on top of each other.

Quick fixes for markus' answer

If position is changed to "dodge" then markus' answer would show the expected result

data_frame(time, a, b) %>%
  mutate(diff_a_b = a - b) %>% 
  gather(key, value, a, b) %>% 
  ggplot(., aes(time)) +
  geom_line(aes(y = value, color = key)) + 
  geom_col(aes(y = diff_a_b), position = "dodge")

enter image description here

Another fix would be to use geom_linerange() where each segment will be plotted twice:

data_frame(time, a, b) %>%
  mutate(diff_a_b = a - b) %>% 
  gather(key, value, a, b) %>% 
  ggplot(., aes(time)) +
  geom_line(aes(y = value, color = key)) + 
  geom_linerange(aes(ymin = 0, ymax = diff_a_b), size = 3)

enter image description here

"Tidy" approach

IMHO, the correct ("tidy") approach is to treat diff_a_b as third variable / time series when reshaping and to use the data parameter when creating the geoms:

data_frame(time, a, b) %>%
  mutate(diff_a_b = a - b) %>% 
  gather(, , -time) %>%
  ggplot(aes(x = time, y = value)) +
  geom_line(aes(col = key), data = function(x) filter(x, key != "diff_a_b")) + 
  geom_col(data = function(x) filter(x, key == "diff_a_b"))

enter image description here

data.table and ggplot2

For those who prefer data.table for data munging:

library(data.table)
library(ggplot2)
long <- data.table(time, a, b)[
  , diff_a_b := a - b][
    , melt(.SD, "time")]
ggplot() + aes(time, value) + 
  geom_line(aes(color = variable), data = long[variable != "diff_a_b"]) + 
  geom_col(data = long[variable == "diff_a_b"])
Related