I am working with a client that wants to provide an input spreadsheet with a text description of when certain events should occur in a given year. Each event (and there are at least 200 of them) is a separate row, containing a complex rule set about when it should occur, e.g., "the first Saturday before October 1st" or "the Friday closest to December 1st". There are also a few times when the event simply occurs on a particular date, but this is rare. However, the actual spreadsheet has about 15 columns that control the start dates of each event, so the logic I need to use to compute the start dates gets pretty in-depth.
I've come up with a way to compute the start dates using a function and a loop that iterates over each row of my data.frame, but I'm wondering whether there is a more efficient tidyverse or purrr solution to this problem. Is it possible (or advisable) to vectorize a solution to this problem?
This is my current (working) solution for the smallest, most compact example I could imagine. Can I make this more efficient, and readable for my more complex real-world input?
library(tidyverse)
library(lubridate)
# Bring in demo data that describes 3 events, and when they should each start.
demo <- structure(list(Event = c("Gala", "Celebration", "Wrap-up"), date_start
= structure(c(18871, NA, NA), class = "Date"), weekday_near = c(NA,
"Saturday", "Friday" ), near_description = c(NA, "before", "closest to"),
near_date = structure(c(NA, 18901, 18962), class = "Date")), row.names = c(NA,
-3L), class = c("tbl_df", "tbl", "data.frame"))
This is what the demo data look like:
Event date_start weekday_near near_description near_date
Gala 2021-09-01 NA NA NA
Celebration NA Saturday before 2021-10-01
Wrap-up NA Friday closest to 2021-12-01
Now, determine start dates for each event - the Gala, the Celebration, and the Wrap-up.
# Create a tibble that contains all possible dates for these events this year.
datedb <- tibble(date = seq(make_date(2021, 9, 1), make_date(2021, 12, 31), by = 1),
wday = wday(date, label = TRUE, abbr = FALSE))
# Write function meant to determine event date for each row of the dataframe.
determine_date <- function(df){
# define variables that are easier to read
# this part makes me squeamish -
# there's gotta be a better way to do this with the tidyverse
event_date_exact <- df[["date_start"]]
event_near_wday <- df[["weekday_near"]]
event_near_desc <- df[["near_description"]]
event_near_date <- df[["near_date"]]
# Event date - if there is an exact date for the event, choose it as the event date.
if (!is.na(event_date_exact)) {
event_date <- event_date_exact
# Otherwise, if the date is dependent on another date, figure out when it should be:
} else {
event_date_vec <- datedb %>% filter(wday == event_near_wday) %>% pull(date)
event_date <-
case_when(
# If you're looking for the closest weekday to a particular date:
event_near_desc == "closest to" ~ event_date_vec[which(abs(event_date_vec - event_near_date) ==
min(abs(event_date_vec - event_near_date), na.rm = TRUE))],
# If you're looking for the first weekday before that weekday
event_near_desc == "before" ~ rev(event_date_vec[which(event_date_vec - event_near_date < 0)])[1],
# If neither of these worked, output NA to check why
TRUE ~ NA_Date_
)
}
}
# create empty vector to store results
start_dates <- lubridate::ymd()
for (i in 1:nrow(demo)) {
start_dates[i] <- determine_date(demo[i,])
}
# add start dates back to original demo dataframe
demo$start_date <- start_dates
Desired output:
Note new start_date column
demo
Event date_start weekday_near near_description near_date start_date
Gala 2021-09-01 NA NA NA 2021-09-01
Celebration NA Saturday before 2021-10-01 2021-09-25
Wrap-up NA Friday closest to 2021-12-01 2021-12-03