28

I am trying to shade a certain section of a time series plot (a bit like recession shading - similarly to the graph at the bottom of this article on recession shading in excel). I have put a little, possibly clumsy, sample together to illustrate. I first create a time series, plot it with ggplot2 and then want to use geom_rect to provide the shading. But I must get something wrong in the arguments.

a<-rnorm(300)
a_ts<-ts(a, start=c(1910, 1), frequency=12)
a_time<-time(a_ts)
a_series<-ts.union(big=a_ts, month=a_time)
a_series_df<-as.data.frame(a_series)
ggplot(a_series)+
  geom_line(mapping=aes_string(x="month", y="big"))+
  geom_rect(
    fill="red",alpha=0.5, 
    mapping=aes_string(x="month", y="big"), 
    xmin=as.numeric(as.Date(c("1924-01-01"))),
    xmax=as.numeric(as.Date(c("1928-12-31"))),
    ymin=0,
    ymax=2
    )

Note that I have also tried which also did not work.

geom_rect(
        fill="red",alpha=0.5, 
        mapping=aes_string(x="month", y="big"), 
        aes(
           xmin=as.numeric(as.Date(c("1924-01-01"))),
           xmax=as.numeric(as.Date(c("1928-12-31"))),
           ymin=0,
           ymax=2)
        )

enter image description here

zx8754
  • 52,746
  • 12
  • 114
  • 209
toksing
  • 325
  • 1
  • 3
  • 7

4 Answers4

24

Its a bit easier using annotate and also note that the bounds for the rectange can be specified as shown:

ggplot(a_series_df, aes(month, big)) + 
    geom_line() +
    annotate("rect", fill = "red", alpha = 0.5, 
        xmin = 1924, xmax = 1928 + 11/12,
        ymin = -Inf, ymax = Inf) +
    xlab("time")

This would also work:

library(zoo)

z <- read.zoo(a_series_df, index = 2)
autoplot(z) + 
    annotate("rect", fill = "red", alpha = 0.5, 
        xmin = 1924, xmax = 1928 + 11/12,
        ymin = -Inf, ymax = Inf) + 
    xlab("time") +
    ylab("big")

Either one gives this:

enter image description here

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • does `annotate("rect",...)` takes in border parameter? I can not find the documentation for this. I am trying to get only a red rectangle with only the border, no fill. – Urvah Shabbir Aug 04 '18 at 18:30
  • 1
    @urwaCFC, Use `col=` instead of `fill=` . – G. Grothendieck Aug 04 '18 at 19:16
  • I ended up using 4 `annotate("segment",...)`. Still thanks, Ill try it out for future usage. – Urvah Shabbir Aug 04 '18 at 21:24
  • This answer has the added benefit of working with unusual geoms that don't have an explicit x and y mapping (e.g., `geom_errorbar()`). – Rich Pauloo Jul 06 '20 at 00:58
  • 1
    @Urvah, If the reason you used multiple annotates was to get multiple shaded rectangles then note that xmin and ymin can be vectors allowing a single annotate statement to be used. e.g. `library(ggplot2) ggplot(a_series_df, aes(month, big)) + geom_line() + annotate("rect", fill = "red", alpha = 0.5, xmin = c(1915, 1924), xmax = c(1920, 1928) + 11/12, ymin = -Inf, ymax = Inf) + xlab("time")` – G. Grothendieck Mar 21 '21 at 14:31
21

Code works fine, conversion to decimal date is needed for xmin and xmax, see below, requires lubridate package.

library("lubridate")
library("ggplot2")

ggplot(a_series_df)+
  geom_line(mapping = aes_string(x = "month", y = "big")) +
  geom_rect(
    fill = "red", alpha = 0.5, 
    mapping = aes_string(x = "month", y = "big"), 
    xmin = decimal_date(as.Date(c("1924-01-01"))),
    xmax = decimal_date(as.Date(c("1928-12-31"))),
    ymin = 0,
    ymax = 2
  )

Cleaner version, shading plotted first so the line colour doesn't change.

ggplot() +
  geom_rect(data = data.frame(xmin = decimal_date(as.Date(c("1924-01-01"))),
                              xmax = decimal_date(as.Date(c("1928-12-31"))),
                              ymin = -Inf,
                              ymax = Inf),
            aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
            fill = "grey", alpha = 0.5) +
  geom_line(data = a_series_df,aes(month, big), colour = "blue") +
  theme_classic()

enter image description here

zx8754
  • 52,746
  • 12
  • 114
  • 209
  • 2
    Note that this works because `aes()` was passed directly to each geom, and not to `ggplot()`. Wierdness ensues if you supply `aes(x=Date,...)` to `ggplot()` and use `geom_rect()` with any other geom... – Jthorpe Jan 11 '18 at 18:45
  • @Jthorpe it is about order of plotting, I could put aes inside ggplot and plot lines then rect, but then lines will be behind the rect. Of course we could use alpha. Preferences. – zx8754 Jan 11 '18 at 18:50
  • 1
    Ok to be specific, If you supply `aes()` to `ggplot()`, you'll need to supply all the variables in that call to `aes()` in the data passed to `geom_rect()` and the classes of the fields will have to be consistent. Much easier to move the call to `aes()` to the individual geoms than construct the required variables with the right formats in the data frame passed to `geom_rect()`. – Jthorpe Jan 11 '18 at 18:57
11

To use geom_rect you need to define your rectangle coordinate through a data.frame:

shade = data.frame(x1=c(1918,1930), x2=c(1921,1932), y1=c(-3,-3), y2=c(4,4))

#    x1   x2 y1 y2
#1 1918 1921 -3  4
#2 1930 1932 -3  4

Then you give ggplot your data and the shade data.frame:

ggplot() + 
  geom_line(aes(x=month, y=big), color='red',data=a_series_df)+
  geom_rect(data=shade, 
            mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color='grey', alpha=0.2)

enter image description here

Colonel Beauvel
  • 30,423
  • 11
  • 47
  • 87
0
library(xts)
library(zoo)
library(ggts)

Creating an xts object

data<-as.xts(x=runif(228,20,40),order.by = seq(as.Date("2000/01/01"), by = "month", length.out = 228))

Creating data frame of dates for which you want to crate shades

date<-data.frame(as.Date("2008-01-01"),as.Date("2009-01-01"))

Now create plot with shaded area

plot_data<-ggts(data)+geom_cycle(date)
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
simar
  • 605
  • 4
  • 12