18

I want to have both month and day in the x-axis of the time series plot when using facet for years in ggplot2. My MWE is below:

set.seed(12345)
Date <- seq(as.Date("2010/1/1"), as.Date("2014/1/1"), "week")
Y <- rnorm(n=length(Date), mean=100, sd=1)
df <- data.frame(Date, Y)

df$Year <- format(df$Date, "%Y")
df$Month <- format(df$Date, "%b")
df$Day <- format(df$Date, "%d")

df$MonthDay <- format(df$Date, "%d-%b")


p <- ggplot(data=df, mapping=aes(x=MonthDay, y=Y, shape=Year, color=Year)) + geom_point() +geom_line(aes(group = 1))
p <- p + facet_grid(facets = Year ~ ., margins = FALSE) + theme_bw()
print(p)

enter image description here

I tried to control the x-axis labels with the following command

p + scale_y_continuous() + scale_x_date(labels = date_format("%d-%b"))

But it throws the following error message.

Error: Invalid input: date_trans works with objects of class Date only
halfer
  • 19,824
  • 17
  • 99
  • 186
MYaseen208
  • 22,666
  • 37
  • 165
  • 309
  • Thanks @G.Grothendieck for your comment and showing interest in my problem. Using `x=Date` will have unnecessarily all four years in x-axis which is not required. – MYaseen208 Apr 30 '15 at 18:07

4 Answers4

29

You are very close. You want the x-axis to be a measure of where in the year you are, but you have it as a character vector and so are getting every single point labelled. If you instead make a continuous variable represent this, you could have better results. One continuous variable would be the day of the year.

df$DayOfYear <- as.numeric(format(df$Date, "%j"))
ggplot(data = df,
       mapping = aes(x = DayOfYear, y = Y, shape = Year, colour = Year)) +
  geom_point() +
  geom_line() +
  facet_grid(facets = Year ~ .) +
  theme_bw()

enter image description here

The axis could be formatted more date-like with an appropriate label function, but the breaks are still not being found in a very date-aware way. (And on top of that, there is an NA problem as well.)

ggplot(data = df,
       mapping = aes(x = DayOfYear, y = Y, shape = Year, colour = Year)) +
  geom_point() +
  geom_line() +
  facet_grid(facets = Year ~ .) +
  scale_x_continuous(labels = function(x) format(as.Date(as.character(x), "%j"), "%d-%b")) +
  theme_bw()

enter image description here

To get the goodness of nice date breaks, a different variable can be used. One that has the same day-of-the-year as the original data, but just one year. In this case, 2000 since it was a leap year. The problems with this have mostly to do with leap days, but if you don't care about that (March 1st of a non-leap year would align with February 29th of a leap year, etc.) you can use:

df$CommonDate <- as.Date(paste0("2000-",format(df$Date, "%j")), "%Y-%j")
ggplot(data = df,
       mapping = aes(x = CommonDate, y = Y, shape = Year, colour = Year)) +
  geom_point() +
  geom_line() +
  facet_grid(facets = Year ~ .) +
  scale_x_date(labels = function(x) format(x, "%d-%b")) +
  theme_bw()

enter image description here

Brian Diggs
  • 57,757
  • 13
  • 166
  • 188
  • Excellent answer. @Brian, having daily data for several years and wanting to plot years on top of each other seems to me a very common task. Do you know of any package that takes a date variable, uses the 'CommonDate' approach without the leap days problem? – Dan Nov 05 '16 at 16:27
  • 1
    @Dan Sorry, I do not know of a package that does that. There are several partial dates/partial times that would be useful in contexts such as these, but I don't know of any place they are collected. – Brian Diggs Nov 15 '16 at 19:54
8

Sticking with your code @MYaseen208 for creating data.

When you plot it use x = Date and use the below

p <- ggplot(data = df, aes(x = Date, y = Y, shape = Year, color = Year)) +
  geom_point() + geom_line(aes(group = 1))
  #adding facet and using facet_wrap with free x scales
  p <- p + facet_wrap(~Year,ncol=1, scales = "free_x") + theme_bw()+
scale_y_continuous() + 
scale_x_date(labels = date_format("%d-%b"), breaks = date_breaks("2 weeks")) +
 theme(axis.text.x = element_text(angle = 90, vjust = 0.5, size = 8))

I used facet_wrap, to get free x_axis scales. When you divide you data up, we can't get the same day-month combination for each year.

enter image description here

infominer
  • 1,981
  • 13
  • 17
3

This seems to do it... I just manually created the labels...

library("ggplot2")
library("scales")
set.seed(12345)
Date <- seq(as.Date("2010/1/1"), as.Date("2014/1/1"), "week")
Y <- rnorm(n=length(Date), mean=100, sd=1)
df <- data.frame(Date, Y)

df$Year <- format(df$Date, "%Y")
df$Month <- format(df$Date, "%b")
df$Day <- format(df$Date, "%d")

df$MonthDay <- format(df$Date, "%d-%b")
df$MonthDay2 <- df$MonthDay
# only show every third label... otherwise it's too crowded
df$MonthDay2[as.numeric(row.names(df))%%3!=0] <- ""
labels <- df$MonthDay2

p <- ggplot(data=df, mapping=aes(x=MonthDay, y=Y, shape=Year, color=Year)) + geom_point() +geom_line(aes(group = 1))
p <- p + facet_grid(facets = Year ~ ., margins = FALSE) + theme_bw()
p + scale_y_continuous() + scale_x_discrete(labels=labels) + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, size = 8))

plot

cory
  • 6,529
  • 3
  • 21
  • 41
2

A modification to @Brian Diggs' approach that preserves the original day and month (March 1st is preserved as March 1st rather than Feb 29th) is to coerce the date into a string, then replace the year:

library(lubridate)
library(stringr)
df$CommonDate <- ymd(paste0("2000-",str_sub(as.character(df$Date),-5)))

You can then proceed with:

ggplot(data = df,
   mapping = aes(x = CommonDate, y = Y, shape = Year, colour = Year)) +
  geom_point() +
  geom_line() +
  facet_grid(facets = Year ~ .) +
  scale_x_date(labels = function(x) format(x, "%d-%b")) +
  theme_bw()
Brian Fisher
  • 1,305
  • 7
  • 17
  • Alternatively, you can replace the year directly with lubridate `year(df$CommonDate)<-2000` and then proceeding. – Brian Fisher Feb 13 '19 at 18:11
  • Is it possible to plot using the CommonDate but from november to february? (to capture winter seasons across years) – user3386170 Sep 20 '21 at 15:57
  • That might be best as a new question. You should be able to create a plotting variable that crosses the year, so Nov/Dec dates are assigned to 1999 and Jan/Feb are assigned to 2000. I would also create a variable for season that puts your Nov 2019 with your Jan 2020, instead of Jan 2019. Then when you plot, it should span across years, and you base your facets off the new season variable. Hydrologists often do something similar with "Water Years," which in my area runs from Oct 1 through Sept 30. – Brian Fisher Sep 22 '21 at 18:01