--- title: "Classifying free days: weekends and public holidays" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Classifying free days: weekends and public holidays} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 9, fig.height = 5, dpi = 150, out.width = "100%" ) library(zeitR) library(dplyr) ``` Free-day classification sits at the heart of every chronotype metric that zeitR computes. MSF (mid-sleep on free days), MSW (mid-sleep on workdays), SJL (social jet lag), and CPD all depend on correctly labelling which nights precede a free day and which do not. By default, only **Saturdays and Sundays** are treated as free days. For studies conducted in countries with public holidays this is usually wrong: participants sleep differently on a Monday that follows a bank holiday compared to a regular Monday. This vignette shows how to supply holidays, the three supported input formats, and the mechanics of how holidays flow through the pipeline. --- ## Holiday formats The `holidays` parameter accepted by `run_pipeline_native()`, `compute_sleep_metrics()`, and `compute_cpd_metrics()` understands three forms, which can be freely combined in the same vector. **Specific dates** — `Date` objects or `"YYYY-MM-DD"` character strings. Use these for holidays whose calendar date changes from year to year (Carnival, Good Friday, Easter Monday). ```{r formats-specific, eval = FALSE} specific <- as.Date(c( "2019-03-04", # Carnaval "2019-03-05", # Carnaval "2019-04-19" # Sexta-feira Santa )) # character strings work too: specific <- c("2019-03-04", "2019-03-05", "2019-04-19") ``` **Recurring holidays** — `"DD-MM"` character strings. Use these for fixed-date holidays that fall on the same calendar day every year (Christmas, New Year, Independence Day). The year is ignored; any date whose day and month match is treated as a free day. ```{r formats-recurring, eval = FALSE} recurring <- c( "07-09", # Independencia do Brasil "12-10", # N. Sra. Aparecida "02-11", # Finados "15-11", # Proclamacao da Republica "20-11", # Consciencia Negra (SP municipal) "25-12", # Natal "01-01", # Confraternizacao Universal "25-01", # Aniversario de Sao Paulo (SP municipal) "21-04", # Tiradentes "01-05" # Dia do Trabalho ) ``` **Mixed** — both forms in one `c()` call. This is the typical setup for a national holiday calendar: fixed-date holidays as `"DD-MM"` and variable-date ones as `"YYYY-MM-DD"`. ```{r formats-mixed, eval = FALSE} sp_holidays <- c( # ── Recurring fixed-date holidays ──────────────────────────────────────── "07-09", "12-10", "02-11", "15-11", "20-11", "25-12", "01-01", "25-01", "21-04", "01-05", # ── Year-specific variable-date holidays ───────────────────────────────── "2019-03-04", "2019-03-05", # Carnaval 2019 "2019-04-19" # Sexta-feira Santa 2019 ) ``` --- ## The no-holidays warning Calling `compute_cpd_metrics()` or `compute_sleep_metrics()` without supplying any holidays emits a warning to alert you that only weekends are being used. ```{r warn-demo, message = TRUE, warning = TRUE} FILE <- system.file("extdata", "input1.txt", package = "zeitR") TZ <- "America/Sao_Paulo" result <- run_pipeline_native(FILE, tz = TZ, quiet = TRUE) # No holidays supplied — warning fires cpd_wd_only <- compute_cpd_metrics(result$nights, tz = TZ) ``` This is intentional: a silent default that misclassifies bank holidays as workdays is hard to catch. Once you have confirmed that weekends-only is correct for your data (e.g. a study where participants had no common holidays), suppress the warning globally for the session: ```{r suppress-warn} options(zeitR.no_holidays_warn = FALSE) ``` The rest of this vignette runs with the warning suppressed. --- ## Passing holidays to the pipeline Supply `holidays` when calling `run_pipeline_native()`. The value is stored in `result$holidays` and does **not** affect the sleep-period detection itself (the Vallim rules are day-of-week agnostic); it is carried through so that downstream metric functions can pick it up automatically. ```{r pipeline} sp_holidays <- c( # Recurring "07-09", "12-10", "02-11", "15-11", "20-11", "25-12", "01-01", "25-01", "21-04", "01-05", # Carnaval + Sexta-feira Santa 2019 (dates change each year) "2019-03-04", "2019-03-05", "2019-04-19" ) result <- run_pipeline_native( FILE, tz = TZ, holidays = sp_holidays, quiet = TRUE ) # holidays are stored on the result object length(result$holidays) head(result$holidays, 5) ``` --- ## Auto-forwarding with S3 dispatch `compute_cpd_metrics()` and `compute_sleep_metrics()` are S3 generics. Passing a `zeitr_result` directly — rather than `result$nights` — picks up `result$holidays` automatically as the default for the `holidays` argument. ```{r s3-dispatch} # S3 dispatch: holidays forwarded automatically cpd <- compute_cpd_metrics(result, tz = TZ) # Equivalent explicit call cpd_explicit <- compute_cpd_metrics(result$nights, tz = TZ, holidays = result$holidays) identical(cpd$msf_h, cpd_explicit$msf_h) ``` The same applies to `compute_sleep_metrics()`: ```{r s3-sm} sm <- compute_sleep_metrics(result, tz = TZ) ``` If you need to override the stored holidays (e.g. to test a different calendar), pass `holidays` explicitly and the explicit value wins: ```{r override, eval = FALSE} cpd_alt <- compute_cpd_metrics(result, tz = TZ, holidays = c("25-12", "01-01")) ``` --- ## Effect on metrics Adding public holidays to the weekend pool changes how nights are split between the free-day and workday groups, which in turn affects MSF, MSW, and SJL. ```{r compare} cpd_we <- compute_cpd_metrics(result$nights, tz = TZ) # weekends only cpd_all <- compute_cpd_metrics(result, tz = TZ) # + SP holidays cat(sprintf(" Weekends only With holidays\n")) cat(sprintf("Free days %13d %d\n", cpd_we$n_free_days, cpd_all$n_free_days)) cat(sprintf("Workdays %13d %d\n", cpd_we$n_workdays, cpd_all$n_workdays)) cat(sprintf("MSW %13s %s\n", cpd_we$msw_hms, cpd_all$msw_hms)) cat(sprintf("MSF %13s %s\n", cpd_we$msf_hms, cpd_all$msf_hms)) cat(sprintf("SJL %10.1f min %.1f min\n", cpd_we$sjl_min, cpd_all$sjl_min)) cat(sprintf("CPD %10.1f min %.1f min\n", cpd_we$cpd_min, cpd_all$cpd_min)) ``` Reclassifying public holidays as free days moves nights from the workday pool into the free-day pool. MSW and MSF shift because the composition of each group changes; SJL reflects the true contrast between unconstrained and constrained sleep across the whole study rather than across weekends alone. The `compute_sleep_metrics()` output shows the same split: ```{r sm-compare} sm_we <- compute_sleep_metrics(result$nights, tz = TZ) sm_all <- compute_sleep_metrics(result, tz = TZ) data.frame( group = c("Overall", "Workday", "Free day"), n_we = c(sm_we$n_overall, sm_we$n_wd, sm_we$n_fd), n_all = c(sm_all$n_overall, sm_all$n_wd, sm_all$n_fd), tst_wd_we = c(NA, round(sm_we$tst_h_wd, 2), NA), tst_fd_we = c(NA, NA, round(sm_we$tst_h_fd, 2)), tst_wd_all = c(NA, round(sm_all$tst_h_wd, 2), NA), tst_fd_all = c(NA, NA, round(sm_all$tst_h_fd, 2)) ) |> knitr::kable( col.names = c("Group", "N (WE only)", "N (+ holidays)", "TST wd", "TST fd", "TST wd", "TST fd"), align = "lrrrrrr", caption = "Night counts and mean TST (h) by group." ) ``` --- ## Batch studies spanning multiple years For a multi-year longitudinal study, use `"DD-MM"` recurring strings for all fixed-date national holidays and add year-specific entries for each year that falls within the recording window. ```{r batch, eval = FALSE} # Brazilian national holidays (recurring every year) br_national <- c( "01-01", # Confraternizacao Universal "21-04", # Tiradentes "01-05", # Dia do Trabalho "07-09", # Independencia do Brasil "12-10", # N. Sra. Aparecida "02-11", # Finados "15-11", # Proclamacao da Republica "25-12" # Natal ) # Variable-date holidays for each study year variable <- c( # Carnaval "2018-02-12", "2018-02-13", "2019-03-04", "2019-03-05", "2020-02-24", "2020-02-25", # Sexta-feira Santa "2018-03-30", "2019-04-19", "2020-04-10" ) study_holidays <- c(br_national, variable) results <- run_pipeline_native_batch( "recordings/", tz = TZ, holidays = study_holidays, quiet = TRUE ) # compute_cpd_metrics(result, tz = TZ) automatically uses each result's holidays cpd_list <- lapply(results, compute_cpd_metrics, tz = TZ) ``` Because the `"DD-MM"` entries match any year, you only need to update `variable` when a new study year is added — the recurring national holidays require no changes. --- ## Non-standard schedules with `free_days` Not every participant works Monday-to-Friday. The `free_days` parameter lets you specify which days of the week are unconditionally treated as free days, replacing the default Saturday + Sunday. Accepted inputs are English day names (case-insensitive) or ISO integers (1 = Monday, ..., 6 = Saturday, 7 = Sunday). ```{r free-days-formats, eval = FALSE} # Compressed work week: Monday off, works Saturday compute_cpd_metrics(result$nights, tz = TZ, holidays = sp_holidays, free_days = c("Sunday", "Monday")) # Middle-East schedule: Friday + Saturday as weekend compute_cpd_metrics(result$nights, tz = TZ, free_days = c("Friday", "Saturday")) # ISO integers work too (5 = Friday, 6 = Saturday) compute_cpd_metrics(result$nights, tz = TZ, free_days = c(5L, 6L)) ``` Passing `free_days` to `run_pipeline_native()` stores it in `result$free_days` so the S3 dispatch picks it up automatically: ```{r free-days-pipeline} result_mon <- run_pipeline_native( FILE, tz = TZ, holidays = sp_holidays, free_days = c("Saturday", "Sunday", "Monday"), quiet = TRUE ) # S3 dispatch forwards both holidays and free_days cpd_mon <- compute_cpd_metrics(result_mon, tz = TZ) cat(sprintf("Free days (Sat+Sun): %d\n", cpd_all$n_free_days)) cat(sprintf("Free days (Sat+Sun+Mon): %d\n", cpd_mon$n_free_days)) cat(sprintf("SJL (Sat+Sun): %.1f min\n", cpd_all$sjl_min)) cat(sprintf("SJL (Sat+Sun+Mon): %.1f min\n", cpd_mon$sjl_min)) ``` `free_days` and `holidays` are additive: a night is a free-day night if the get-up date falls on any day in `free_days` **or** matches any entry in `holidays`. --- ## Summary | Format | Example | When to use | |---|---|---| | `Date` / `"YYYY-MM-DD"` | `as.Date("2019-03-04")` | Holidays whose date changes year to year | | `"DD-MM"` | `"25-12"` | Fixed-date holidays that repeat annually | | Mixed | `c("25-12", "2019-03-04")` | Typical national calendar (most common) | | Parameter | Default | Purpose | |---|---|---| | `free_days` | `c("Saturday", "Sunday")` | Days of the week that are always free | | `holidays` | `NULL` | Specific or recurring dates that are also free | Passing `holidays` to `run_pipeline_native()` stores them on the result object; calling `compute_cpd_metrics(result, tz = ...)` or `compute_sleep_metrics(result, tz = ...)` with a `zeitr_result` picks them up automatically. To silence the reminder that fires when no holidays are supplied, use `options(zeitR.no_holidays_warn = FALSE)`. --- ## See also - `vignette("vallim-pipeline")` — the Vallim classification rule set - `?compute_cpd_metrics` — MSF, MSW, SJL, MSFsc, and CPD reference - `?compute_sleep_metrics` — per-day-type sleep summary metrics - `?run_pipeline_native` — full pipeline parameter reference