I am creating a custom S3 class, to_from_fiscal, that extends the "Date" class in order to create fiscal years that display in a format commonly used in the UK. My goal is to implement a custom as.factor method for this class to correctly convert fiscal year objects into factors. However, S3 dispatch does not seem to recognise my custom method (as.factor.to_from_fiscal).
Here is my implementation:
new_to_from_fiscal <- function(date = lubridate::Date(),
fiscal_start = integer(),
sep = character(),
short = logical()) {
stopifnot(lubridate::is.Date(date))
stopifnot(is.character(sep))
stopifnot(is.integer(fiscal_start))
stopifnot(fiscal_start >= 1 & fiscal_start <= 12)
stopifnot(is.logical(short))
structure(
date,
class = c("to_from_fiscal", "Date"),
sep = sep,
fiscal_start = fiscal_start,
short = short
)
}
# Create fiscal year object
to_from_fiscal <- function(date, fiscal_start = 4, sep = "-", short = FALSE) {
date <- as.Date(date)
fiscal_start <- as.integer(fiscal_start)
sep <- as.character(sep)
from_year <- lubridate::year(date) + ifelse(lubridate::month(date) >= fiscal_start, 0, -1)
new_to_from_fiscal(as.Date(paste(from_year, fiscal_start, 1, sep = "-")), fiscal_start, sep, short)
}
# Format method for the custom class
format.to_from_fiscal <- function(x, ...) {
fiscal_start <- attr(x, "fiscal_start")
short <- attr(x, "short")
sep <- attr(x, "sep")
from_year <- lubridate::year(x)
to_year <- from_year + 1
if (isTRUE(short)) {
sprintf("%02d%s%02d", from_year %% 100, sep, to_year %% 100)
} else {
sprintf("%d%s%d", from_year, sep, to_year)
}
}
# Custom as.factor method
as.factor.to_from_fiscal <- function(x, ...) {
factor(format(x, ...))
}
The Problem: When I attempt to call as.factor on an object of class "to_from_fiscal", my custom method is not dispatched, and the output is :
test_fq <- to_from_fiscal(as.Date("2024-05-15"), short = TRUE)
factor(test_fq)
Returns NA
sloop::s3_dispatch(as.factor(test_fq))
The output of s3_dispatch shows:
as.factor.to_from_fiscal
as.factor.Date
as.factor.default
My custom method is recognized but is not used.
What I've Tried:
class(test_fq)
# [1] "to_from_fiscal" "Date"
format(test_fq)
# Returns "24-25"
None of these approaches resolved the issue.
Diagnostics:
attributes(test_fq)
confirms the custom attributes are intact.
sloop::s3_dispatch(as.factor(test_fq))
shows the correct custom method is available but not used.
My Question: Why is the S3 dispatch system not prioritizing as.factor.to_from_fiscal? How can I ensure that my custom method is correctly dispatched when calling as.factor on an object of class "to_from_fiscal"?
I apologise if I'm making an obvious error. This is my first time creating anything with the object oriented systems in R.
Any insights into resolving this issue would be greatly appreciated!
factor
and as.factor
are not S3 generics. You need to define methods for as.character
and unique
if you want this to work.
as.character.to_from_fiscal <- function(x, ...) {
format(x, ...)
}
unique.to_from_fiscal <- function(x, incomparables = FALSE, fromLast = FALSE,
nmax = NA, ...) {
#quick and dirty, might need improvement
att <- attributes(x)
x <- as.vector(x)
y <- unique(x, incomparables = FALSE, fromLast = FALSE,
nmax = NA, ...)
attributes(y) <- att
y
}
library(lubridate)
test_fq <- to_from_fiscal(as.Date(c("2024-05-15", "2024-05-15", "2025-05-15")), short = TRUE)
dput(test_fq)
factor(test_fq)
#[1] 24-25 24-25 25-26
#Levels: 24-25 25-26