Find which season a particular date belongs to

I have something similarly ugly as Tim:

R> toSeason <- function(dat) {
+ 
+     stopifnot(class(dat) == "Date")
+ 
+     scalarCheck <- function(dat) {
+         m <- as.POSIXlt(dat)$mon + 1        # correct for 0:11 range
+         d <- as.POSIXlt(dat)$mday           # correct for 0:11 range
+         if ((m == 3 & d >= 21) | (m == 4) | (m == 5) | (m == 6 & d < 21)) {
+             r <- 1
+         } else if ((m == 6 & d >= 21) | (m == 7) | (m == 8) | (m == 9 & d < 21)) {
+             r <- 2
+         } else if ((m == 9 & d >= 21) | (m == 10) | (m == 11) | (m == 12 & d < 21)) {
+             r <- 3
+         } else {
+             r <- 4
+         }
+         r
+     }
+ 
+     res <- sapply(dat, scalarCheck)
+     res <- ordered(res, labels=c("Spring", "Summer", "Fall", "Winter"))
+     invisible(res)
+ }
R> 

And here is a test:

R> date <- Sys.Date() + (0:11)*30
R> DF <- data.frame(Date=date, Season=toSeason(date))
R> DF
         Date Season
1  2012-02-29 Winter
2  2012-03-30 Spring
3  2012-04-29 Spring
4  2012-05-29 Spring
5  2012-06-28 Summer
6  2012-07-28 Summer
7  2012-08-27 Summer
8  2012-09-26   Fall
9  2012-10-26   Fall
10 2012-11-25   Fall
11 2012-12-25 Winter
12 2013-01-24 Winter
R> summary(DF)
      Date               Season 
 Min.   :2012-02-29   Spring:3  
 1st Qu.:2012-05-21   Summer:3  
 Median :2012-08-12   Fall  :3  
 Mean   :2012-08-12   Winter:3  
 3rd Qu.:2012-11-02             
 Max.   :2013-01-24             
R> 

I would create a lookup table, and go from there. An example (note the code obfuscation using the d() function and the pragmatic way of filling the lut):

# Making lookup table (lut), only needed once. You can save
# it using save() for later use. Note I take a leap year.
d = function(month_day) which(lut$month_day == month_day)
lut = data.frame(all_dates = as.POSIXct("2012-1-1") + ((0:365) * 3600 * 24),
                 season = NA)
lut = within(lut, { month_day = strftime(all_dates, "%b-%d") })
lut[c(d("Jan-01"):d("Mar-20"), d("Dec-21"):d("Dec-31")), "season"] = "winter"
lut[c(d("Mar-21"):d("Jun-20")), "season"] = "spring"
lut[c(d("Jun-21"):d("Sep-20")), "season"] = "summer"
lut[c(d("Sep-21"):d("Dec-20")), "season"] = "autumn"
rownames(lut) = lut$month_day

After creating the lookup table, you can extract quite easily from it to what season a month/day combination belongs to:

dat = data.frame(dates = Sys.Date() + (0:11)*30)
dat = within(dat, { 
  season =  lut[strftime(dates, "%b-%d"), "season"] 
 })
> dat
        dates season
1  2012-02-29 winter
2  2012-03-30 spring
3  2012-04-29 spring
4  2012-05-29 spring
5  2012-06-28 summer
6  2012-07-28 summer
7  2012-08-27 summer
8  2012-09-26 autumn
9  2012-10-26 autumn
10 2012-11-25 autumn
11 2012-12-25 winter
12 2013-01-24 winter

All nice and vectorized :). I think once the table is created, this is very quick.


How about using something like this:

getSeason <- function(DATES) {
    WS <- as.Date("2012-12-15", format = "%Y-%m-%d") # Winter Solstice
    SE <- as.Date("2012-3-15",  format = "%Y-%m-%d") # Spring Equinox
    SS <- as.Date("2012-6-15",  format = "%Y-%m-%d") # Summer Solstice
    FE <- as.Date("2012-9-15",  format = "%Y-%m-%d") # Fall Equinox

    # Convert dates from any year to 2012 dates
    d <- as.Date(strftime(DATES, format="2012-%m-%d"))

    ifelse (d >= WS | d < SE, "Winter",
      ifelse (d >= SE & d < SS, "Spring",
        ifelse (d >= SS & d < FE, "Summer", "Fall")))
}

my.dates <- as.Date("2011-12-01", format = "%Y-%m-%d") + 0:60
head(getSeason(my.dates), 24)
#  [1] "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"  
#  [8] "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"  
# [15] "Winter" "Winter" "Winter" "Winter" "Winter" "Winter"

One note: 2012 is a good year to which to convert all of the dates; since it is a leap year, any February 29ths in your data set will be handled smoothly.

Tags:

Date

R