10

I would like a function that counts the number of specific days per month..

i.e.. Nov '13 -> 5 fridays.. while Dec'13 would return 4 Fridays..

Is there an elegant function that would return this?

library(lubridate)

num_days <- function(date){
x <- as.Date(date)  
start = floor_date(x, "month")
count = days_in_month(x)

d = wday(start) 
sol = ifelse(d > 4, 5, 4) #estimate that is the first day of the month is after Thu or Fri then the week will have 5 Fridays
sol
}

num_days("2013-08-01")
num_days(today())

What would be a better way to do this?

vinchinzu
  • 604
  • 1
  • 7
  • 16

3 Answers3

12

1) Here d is the input, a Date class object, e.g. d <- Sys.Date(). The result gives the number of Fridays in the year/month that contains d. Replace 5 with 1 to get the number of Mondays:

first <- as.Date(cut(d, "month"))
last <- as.Date(cut(first + 31, "month")) - 1
sum(format(seq(first, last, "day"), "%w") == 5)

2) Alternately replace the last line with the following line. Here, the first term is the number of Fridays from the Epoch to the next Friday on or after the first of the next month and the second term is the number of Fridays from the Epoch to the next Friday on or after the first of d's month. Again, we replace all 5's with 1's to get the count of Mondays.

ceiling(as.numeric(last + 1 - 5 + 4) / 7) - ceiling(as.numeric(first - 5 + 4) / 7)

The second solution is slightly longer (although it has the same number of lines) but it has the advantage of being vectorized, i.e. d could be a vector of dates.

UPDATE: Added second solution.

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • The update works great: I was trying to use the previous function in `df <- mutate(d, countFridays(Month))` and now it works great as a vector. – vinchinzu Nov 11 '13 at 19:39
  • 1
    @vinchinzu, If we wanted to vectorize the first then one assuming that we make it into a function, `countFri`, we could do this: `sapply(d, countFri)` or this `Vectorize(countFri)(d)` – G. Grothendieck Nov 11 '13 at 19:48
  • An alternative to **2** is `ceiling((as.numeric(last-first)+1-(5-as.numeric(first+4))%%7)/7)`, where `as.numeric(last-first)+1` is the number of days between the two dates. Here, the number corresponding to the weekday, `5`, only needs updating once. – wjchulme Mar 11 '15 at 16:56
2

There are a number of ways to do it. Here is one:

countFridays <- function(y, m) { 
    fr <- as.Date(paste(y, m, "01", sep="-")) 
    to <- fr + 31       
    dt <- seq(fr, to, by="1 day")      
    df <- data.frame(date=dt, mon=as.POSIXlt(dt)$mon, wday=as.POSIXlt(dt)$wday)  
    df <- subset(df, df$wday==5 & df$mon==df[1,"mon"])         
    return(nrow(df))     
}

It creates the first of the months, and a day in the next months.

It then creates a data frame of month index (on a 0 to 11 range, but we only use this for comparison) and weekday.

We then subset to a) be in the same month and b) on a Friday. That is your result set, and we return the number of rows as your anwser.

Note that this only uses base R code.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
2

Without using lubridate -

#arguments to pass to function:
whichweekday <- 5
whichmonth <- 11
whichyear <- 2013

#function code:    
firstday <- as.Date(paste('01',whichmonth,whichyear,sep="-"),'%d-%m-%Y')
lastday <- if(whichmonth == 12) { '31-12-2013' } else {seq(as.Date(firstday,'%d-%m-%Y'), length=2, by="1 month")[2]-1}
sum(
  strftime(
seq.Date(
  from = firstday,
  to = lastday,
  by = "day"),
'%w'
) == whichweekday)
TheComeOnMan
  • 12,535
  • 8
  • 39
  • 54