1

This question builds on How can i provide shades between dates on x axis in R plot? and How to align multiple ggplot2 plots and add shadows over all of them.

Before I go into details, I want to be able to condense the amount of code I would have to write in order to shade many different sections. I hope to be able to put all of this information into one piece of code.

Here is my MWE:

library(ggplot2)
library(scales)
set.seed(1)
data <- data.frame(Date = seq(as.Date('2000-01-01'), 
                   len= 23, by="1 day"), 
                   Value = sample(1:50, 23, replace=TRUE))
rect1 <- data.frame(xmin=as.Date('2000-01-03 12:00:00'), 
                    xmax=as.Date('2000-01-04 12:00:00'), 
                    ymin=-Inf, 
                    ymax=Inf)
rect2 <- data.frame(xmin=as.Date('2000-01-10'), 
                    xmax=as.Date('2000-01-11'), 
                    ymin=-Inf, 
                    ymax=Inf)
rect3 <- data.frame(xmin=as.Date('2000-01-17 00:00:00'), 
                    xmax=as.Date('2000-01-18 00:00:00'), 
                    ymin=-Inf, 
                    ymax=Inf)
ggplot() + 
  geom_line(data=data, aes(x = Date, y = Value)) + 
  geom_rect(data = rect1, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax), alpha = 0.4) + 
  geom_rect(data = rect2, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax), alpha = 0.4) + 
  geom_rect(data = rect3, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax), alpha = 0.4) + 
  scale_x_date(breaks = date_breaks("1 day"), labels = date_format("%d"))

Thus, I get a plot with 3 different shaded regions:

Plot of MWE

Having 3 sections to shade isn't too bad, but what if I needed to shade 100? Is there a way to not define 100 data frames and then add all these shadings one-by-one?

And secondly, I want to have my shadings be over the middle of the value. But in all three specifications (rect1, rect2, and rect3), each shading starts at the value for that date and ends at the value for the next date. Any tips on how to do this?

Community
  • 1
  • 1
bill999
  • 2,147
  • 8
  • 51
  • 103

2 Answers2

5

You're closer than you think!

Instead of making one data frame for each rectangle, you can just make a single data frame where each row is a date range to be shaded. Here's a single-dataframe version of your example above (with data being the same as yours).

dateRanges <- data.frame(
    from=as.Date(c('2000-01-03 12:00:00', '2000-01-10', '2000-01-17 00:00:00')), 
    to=as.Date(c('2000-01-04 12:00:00', '2000-01-11', '2000-01-18 00:00:00'))
)
ggplot() + geom_line(data=data, aes(x = Date, y = Value)) + 
    geom_rect(data = dateRanges, aes(xmin = from - 1, xmax = to, ymin = -Inf, ymax = Inf), alpha = 0.4)

And the resulting image:

plot with rectangles shaded

Couple things to notice:

  1. Using xmin = from - 1 shifts the left boundary so that the shaded rectangle starts before the date specified in the from column.
  2. I've renamed it from rect to dateRanges to show that you can really pull the rectangle data out of any data frame--it doesn't have to be so closely tied to the plot you'll be doing (and things might be easier to keep track of down the line if you name the dataframe more descriptively).
  3. Also in your case, you don't need the ymin and ymax columns in your data frame, as you can reference -Inf and Inf directly.

UPDATE

It occurs to me that, if you don't need variable-width ranges, you could actually just use a single column for your data frame (importantDates = data.frame(Date=as.Date(c(...)))) and then use xmin = Date - 1, xmax = Date + 1` in the plot.

Moreover, if it were appropriate to your situation, you could even generate the importantDates data frame based on one or more columns of the original data with something like importantDates <- data[data$Value > 40,] (or whatever).

Community
  • 1
  • 1
anandthakker
  • 628
  • 6
  • 15
  • Thanks @anandthakker This works. One question on your point 1 - `xmin = from -1` in effect shades 2 days instead of one. What if I wanted to shade only one day, but shift this so that it is centered over this day. For example, for Jan 03, I would have the bar as you have it, but have your bar be half the size and still centered over Jan 03. In effect, I would go from Jan 02 noon - Jan 03 noon. The other answer does this, but is it possible to do it without using the POSIXct class? – bill999 Dec 05 '14 at 02:58
  • Figured it out - use `xmin = from - .5` and `xmax = from + .5`. – bill999 Dec 05 '14 at 03:09
1

You can have the shade plotted in the middle using POSIXct objects.

You can include any number of shades defining a list containing all the coordinates.

library(ggplot2)
library(scales)
set.seed(1)
data <- data.frame(
  Date = as.POSIXct(seq(as.Date('2000-01-01'), len= 23, by="1 day")), 
  Value = sample(1:50, 23, replace=TRUE)
)

You can define a vector containing all the dates to start with, assuming that the band width is always one day. Alternatively, you can define a dataframe containing the two dates and use apply or dlply (from plyr).

vectorDates <-seq(
  as.POSIXct('2000-01-03 12:00:00'),
  as.POSIXct('2000-01-20 12:00:00'),
  length.out = 10
)
list_geom_rect <- lapply(vectorDates, function(date1)
{
  date2 <- date1 + 3600 * 24
  rect <- data.frame(
    xmin = date1, xmax = date2, 
    ymin = -Inf,  ymax = Inf
  )
  geom_rect(
    data = rect,
    mapping = aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
    alpha = 0.4
  )
})

Now you can build the plot and add all the bands.

 p <- ggplot() + geom_line(data=data, aes(x = Date, y = Value))
    for(geom in list_geom_rect){
      p <- p + geom
    }
 p
Michele Usuelli
  • 1,970
  • 13
  • 15