8

I started to make a calendar in Excel, but thought it might be easy to do in R instead. (Note: I'm aware of various heatmap calendar packages but I prefer to use ggplot2.) Ultimately I want to make a calendar that shows 12 months starting from a given date, with each month as a small box showing 4 or 5 weeks on the y-axis (week 1 at the top, week 4 or 5 at the bottom) and the days of the week (starting from Monday) along the x-axis.

I thought this would be a 15-minute job (create data for all months, format a bit using reshape then use facet_wrap) but ran into problems almost immediately as the plot below shows - the days of the month are not in order, although the days of the week seem okay. Ordering data in R is my nemesis; I really should get a handle on this. Anyway, the image below shows what I have so far and the code is below that. This is just a fun project, not urgent, but any help and/or embellishments welcome.

calendar

require(ggplot2)
require(scales)
require(lubridate)

date.start <- as.Date('2013-09-01')
date.end <- date.start + months(1)

mydf <- data.frame(mydate = seq(as.Date(date.start),
                       as.Date(date.end) - days(1),
                       by = 'day'))

mydf$month <- month(mydf$mydate)
mydf$week <- week(mydf$mydate)
mydf$day <- day(mydf$mydate)
mydf$dow <- as.factor(format(mydf$mydate, format = "%a"))
levels(mydf$dow) <- c('Mon','Tue','Wed','Thu','Fri','Sat','Sun')

ggplot(mydf, aes(x = dow, y = as.factor(week))) +
    geom_tile(colour = "black", fill = "white", label = mydf$day) +
    geom_text(label = mydf$day, size = 4, colour = "black") +
    scale_x_discrete(expand = c(0,0)) +
    theme(axis.ticks = element_blank()) +
    theme(axis.title.y = element_blank()) +
    theme(axis.title.x = element_blank()) +
    theme(panel.background = element_rect(fill = "transparent"))+
    theme(legend.position = "none") +
    theme()
SlowLearner
  • 7,907
  • 11
  • 49
  • 80
  • http://stackoverflow.com/questions/22815688/calendar-time-series-with-r - something I had written around April 2014. – TheComeOnMan Oct 06 '14 at 23:07

3 Answers3

6

Create the week.f column with levels in reverse order. We have used "%U" (assuming US convention but if you want the UK convention use "%W" and also change the order of the factor levels of dow). Also we have allowed arbitrary date input calculating the start of the month and have simplified the ggplot call slightly.

library(ggplot2)

input <- as.Date("2013-09-11") # input date

st <- as.Date(cut(input, "month")) # calculate start of month
dates31 <- st + 0:30 # st and next 30 dates (31 in all)
dates <- dates31[months(dates31) == months(st)] # keep only dates in st's month

week.ch <- format(dates, "%U") # week numbers

mydf <- data.frame(
   day = as.numeric(format(dates, "%d")),
   week.f = factor(week.ch, rev(unique(week.ch))),
   dow = factor(format(dates, "%a"), c("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"))
)

ggplot(mydf, aes(x = dow, y = week.f)) +
    geom_tile(colour = "black", fill = "white") +
    geom_text(label = mydf$day, size = 4) + 
    scale_x_discrete(expand = c(0,0)) +
    theme(axis.ticks = element_blank()) +
    theme(axis.title = element_blank()) +
    theme(panel.background = element_rect(fill = "transparent"))+
    theme(legend.position = "none")

enter image description here

By the way, there is a cal function in the TeachingDemos package that can construct a one month calendar using classic graphics.

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Thanks - chosen as this was the answer that showed me how to order the days of the month (and weeks) from 1 to 31, top to bottom, but I learnt something from each answer. – SlowLearner Sep 12 '13 at 17:44
  • Seems to work over year change +1, how can you color only certain tiles with different colors? – hhh Dec 04 '18 at 09:44
  • Replace `"white"` in `fill="white"` with a vector of `nrow(mydf)` elements specifying the colors to fill the tiles with. – G. Grothendieck Dec 04 '18 at 12:56
6

This line of your code is wrong:

levels(mydf$dow) <- c('Mon','Tue','Wed','Thu','Fri','Sat','Sun')

It is because mydf$dow is already a factor and by using levels you are just renaming current levels. In this case you are just renaming "Fri" "Mon" "Sat" "Sun" "Thu" "Tue" "Wed" to 'Mon','Tue','Wed','Thu','Fri','Sat','Sun' and this will create mess you get at the end.

If you look at mydf in your example, you will see:

       mydate month week day dow
1  2013-09-01     9   35   1 Thu
2  2013-09-02     9   36   2 Tue
3  2013-09-03     9   36   3 Sat
4  2013-09-04     9   36   4 Sun
5  2013-09-05     9   36   5 Fri
6  2013-09-06     9   36   6 Mon
7  2013-09-07     9   36   7 Wed

Because of renaming, names of days do not match to dates anymore.

Once corrected, everything works as expected:

require(ggplot2)
require(scales)
require(lubridate)


date.start <- as.Date('2013-09-01')
date.end <- date.start + months(1)

mydf <- data.frame(mydate = seq(as.Date(date.start),
                                as.Date(date.end) - days(1),
                                by = 'day'))

mydf$month <- month(mydf$mydate)
mydf$week <- week(mydf$mydate)
mydf$day <- day(mydf$mydate)
mydf$dow <- as.factor(format(mydf$mydate, format = "%a"))

mydf$dow <- factor(mydf$dow, levels=c('Mon','Tue','Wed','Thu','Fri','Sat','Sun'))

ggplot(mydf, aes(x = dow, y = as.factor(week))) +
  geom_tile(colour = "black", fill = "white", label = mydf$day) +
  geom_text(label = mydf$day, size = 4, colour = "black") +
  scale_x_discrete(expand = c(0,0)) +
  theme(axis.ticks = element_blank()) +
  theme(axis.title.y = element_blank()) +
  theme(axis.title.x = element_blank()) +
  theme(panel.background = element_rect(fill = "transparent"))+
  theme(legend.position = "none") +
  theme()

enter image description here

Tomas Greif
  • 21,685
  • 23
  • 106
  • 155
  • There is a bug in this code when you a year change, picture [here](https://i.stack.imgur.com/2Wz3t.png). – hhh Dec 04 '18 at 07:55
4

Here you go:

start <- as.Date("2013-09-1")
numdays <- 30

weeknum <- function(date){
  z <- as.Date(date, format="%Y-%m-%d")
  as.numeric( format(z-1, "%U"))
}


dates <- data.frame(date=seq(start, length.out=numdays, by="1 day"))
dates <- within(dates, {
  weeknum <- weeknum(date)
  month   <- format(date, "%m")
  weekday <- format(date, "%a")
  day     <- format(date, "%d")
})

dates$weekday <- factor(dates$weekday, levels=c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"))

library(ggplot2)
ggplot(dates, aes(x=weekday, y=weeknum)) + 
  geom_tile(fill="blue", col="white") +
  geom_text(aes(label=day)) 

enter image description here

Andrie
  • 176,377
  • 47
  • 447
  • 496
  • Thanks for this. The `weeknum` function exists to generate week numbers for weeks starting on a Monday? – SlowLearner Sep 12 '13 at 17:51
  • There is some problem in this calendar chart during the year change, picture [here](https://i.stack.imgur.com/v2cuQ.png). – hhh Dec 04 '18 at 08:14