Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow yyyy-mm-dd formatted character input #101

Merged
merged 11 commits into from
Jan 15, 2019
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ notifications:
on_failure: change

after_success:
- if [[ "${{R_CODECOV}}" ]]; then Rscript -e 'covr::codecov()'; fi
- if [[ "${R_CODECOV}" ]]; then Rscript -e 'covr::codecov()'; fi
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ S3method(group_names,default)
S3method(group_names,incidence)
S3method(incidence,Date)
S3method(incidence,POSIXt)
S3method(incidence,character)
S3method(incidence,default)
S3method(incidence,integer)
S3method(incidence,numeric)
Expand Down
10 changes: 4 additions & 6 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ incidence 1.5.4 unreleased

### BUG FIX

* `incidence()` now returns an error when dates argument is character object.
* `incidence()` now returns an error when supplied a character vector that is
not formatted as (yyyy-mm-dd).
(See https://github.com/reconhub/incidence/issues/88)
* `fit()` now returns correct coefficients when dates is POSIXt by converting to
Date. (See https://github.com/reconhub/incidence/issues/91)
* `plot.incidence()` now plots in UTC by default for POSIXt incidence objects.
this prevents a bug where different time zones would cause a shift in the bars
(See https://github.com/reconhub/incidence/issues/99).

### NOTABLE CHANGES

* `incidence()` no longer accepts characters as input for dates, first_date, or
last_date arguments.

### MISC

* A test that randomly failed on CRAN has been fixed.
(See https://github.com/reconhub/incidence/issues/95).
* Plotting tests have been updated for new version of vdiffr
(See https://github.com/reconhub/incidence/issues/96).
* POSIXct incidence are first passed through POSIXlt when initialized.
* A more informative error message is generated for non ISO 8601 formatted
`first_date` and `last_date` parameters.

incidence 1.5.3 (2018-12-07)
============================
Expand Down
13 changes: 7 additions & 6 deletions R/check_boundaries.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ check_boundaries <- function(dates, boundary = NULL, what = "first") {
MINMAX <- if (what == "first") min else max
boundary <- MINMAX(dates, na.rm = TRUE)
}
if (is.character(boundary)) {
msg <- '%s_date is a character. Did you forget to convert to Date?'
stop(sprintf(msg, what), call. = FALSE)
msg <- "%s_date (%s) could not be converted to Date."
if (is.character(boundary) && !grepl("^[0-9]{4}-[01][0-9]-[0-3][0-9]$", boundary)) {
msg <- paste(msg, 'Dates must be in ISO 8601 standard format (yyyy-mm-dd).')
stop(sprintf(msg, what, boundary), call. = FALSE)
}
res <- try(check_dates(boundary), silent = TRUE)
if (inherits(res, "try-error")) {
msg <- paste("%s_date could not be converted to Date. Accepted formats are:",
"\n Date, POSIXct, integer, numeric.")
stop(sprintf(msg, what), call. = FALSE)
msg <- paste(msg, "Accepted formats are:",
"\n Date, POSIXct, integer, numeric, character.")
stop(sprintf(msg, what, deparse(substitute(boundary))), call. = FALSE)
}
res
}
6 changes: 5 additions & 1 deletion R/check_dates.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ check_dates <- function(x, error_on_NA = FALSE, ...) {
stop("dates is NULL", call. = FALSE)
}

if (is.character(x)) {
x <- as.Date(x, ...)
zkamvar marked this conversation as resolved.
Show resolved Hide resolved
}

not_finite <- !is.finite(x)
if (sum(not_finite) > 0) {
x[not_finite] <- NA
Expand Down Expand Up @@ -55,7 +59,7 @@ check_dates <- function(x, error_on_NA = FALSE, ...) {
}


formats <- c("Date", "POSIXct", "integer", "numeric")
formats <- c("Date", "POSIXct", "integer", "numeric", "character")
msg <- paste0(
"Input could not be converted to date. Accepted formats are:\n",
paste(formats, collapse = ", "))
Expand Down
54 changes: 42 additions & 12 deletions R/incidence.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
#' and 2, the second interval includes 3, 4 and 5 etc.
#'
#' @param dates A vector of dates, which can be provided as objects of the
#' class: integer, numeric, Date, POSIXct. Note that decimal numbers will be
#' floored with a warning.
#' class: integer, numeric, Date, POSIXct, POSIXlt, and character. (See Note
#' about `numeric` and `character` formats)
#'
#' @param interval An integer or character indicating the (fixed) size of the time interval
#' used for computing the incidence; defaults to 1 day. This can also be a text string that corresponds to a valid date
#' interval: day, week, month, quarter, or year. See Note.
#' @param interval An integer or character indicating the (fixed) size of the
#' time interval used for computing the incidence; defaults to 1 day. This can
#' also be a text string that corresponds to a valid date interval: day, week,
#' month, quarter, or year. (See Note)
#'
#' @param groups An optional factor defining groups of observations for which
#' incidence should be computed separately.
Expand Down Expand Up @@ -54,7 +55,14 @@
#' @details For details about the `incidence class`, see the dedicated
#' vignette:\cr `vignette("incidence_class", package = "incidence")`
#'
#' @note If `interval` is a valid character (e.g. "week" or "month"), then
#' @note \subsection{Input data (`dates`)}{
#' - **Decimal (numeric) dates**: will be truncated with a warning
#' - **Character dates** should be in the unambiguous `yyyy-mm-dd` (ISO 8601)
#' format. Any other format will trigger an error.
#' }
#'
#' \subsection{Interval specification (`interval`)}{
#' If `interval` is a valid character (e.g. "week" or "month"), then
#' the bin will start at the beginning of the interval just before the first
#' observation by default. For example, if the first case was recorded on
#' Wednesday, 2018-05-09:
Expand All @@ -74,6 +82,7 @@
#' number of days they encompass and warnings will be generated when the first
#' date falls outside of a calendar date that is easily represented across the
#' interval.
#' }
#'
#' @seealso
#' The main other functions of the package include:
Expand Down Expand Up @@ -155,12 +164,7 @@ incidence <- function(dates, interval = 1L, ...) {
#' @export
#' @rdname incidence
incidence.default <- function(dates, interval = 1L, ...) {
if (is.character(dates)) {
stop('Input is a character. Did you forget to convert to Date?')
}
check_dates(dates)
msg <- "Unknown date input; accepted formats are Date, POSIXct, integer, numeric."
stop(msg)
incidence(check_dates(dates), interval = interval, ...)
}

#' @export
Expand Down Expand Up @@ -200,6 +204,32 @@ incidence.Date <- function(dates, interval = 1L, standard = TRUE, groups = NULL,
}


#' @export
#' @rdname incidence
incidence.character <- function(dates, interval = 1L, standard = TRUE, groups = NULL,
na_as_group = TRUE, first_date = NULL,
last_date = NULL, ...) {
iso_std <- grepl("^[0-9]{4}-[01][0-9]-[0-3][0-9]$", trimws(dates))
iso_std[is.na(dates)] <- TRUE # prevent false alarms
if (!all(iso_std)) {
msg <- paste("Not all dates are in ISO 8601 standard format (yyyy-mm-dd).",
"The first incorrect date is %s"
)
stop(sprintf(msg, dates[!iso_std][1]))
}
dots <- check_dots(list(...), names(formals(incidence.Date)))
dates <- check_dates(dates)

ret <- incidence(as.Date(trimws(dates)),
interval = interval,
standard = standard,
groups = groups,
na_as_group = na_as_group,
first_date = first_date,
last_date = last_date,
...)
ret
}
## The default incidence is designed for dates provided as integers, and a fixed
## time interval defaulting to 1. 'bins' are time intervals, identified by the
## left date, left-inclusive and right-exclusive, i.e. the time interval defined
Expand Down
26 changes: 21 additions & 5 deletions man/incidence.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 38 additions & 20 deletions tests/testthat/test-incidence.R
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ test_that("construction - Date input", {
expect_message(x.i.trim <- incidence(dat, first_date = 0),
"[0-9]+ observations outside of \\[0, [0-9]+\\] were removed."
)
expect_message(x.d.trim <- incidence(dat_dates, first_date = as.Date("2016-01-01")),
expect_message(x.d.trim <- incidence(dat_dates, first_date = "2016-01-01"),
"[0-9]+ observations outside of \\[2016-01-01, [-0-9]{10}\\] were removed."
)
x.7 <- incidence(dat_dates, 7L, standard = FALSE)
Expand Down Expand Up @@ -201,6 +201,21 @@ test_that("construction - POSIXct input", {
expect_is(x.pos$dates, "POSIXct")
})

test_that("construction - character input", {
dats <- Sys.Date() + sample(-100:100, 5)
datc <- as.character(dats)

i.date <- incidence(dats)
i.char <- incidence(datc)
i.chaw <- incidence(paste(datc, " "))
expect_message(i.cham <- incidence(c(datc, NA, NA)), "2 missing observations were removed.")
expect_is(i.date, "incidence")
expect_identical(i.date, i.char)
expect_identical(i.date, i.chaw)
expect_identical(i.date, i.cham)
})


test_that("corner cases", {


Expand All @@ -223,7 +238,7 @@ test_that("corner cases", {
"The interval 'grind' is not valid. Please supply an integer.")

expect_error(incidence(as.Date(Sys.Date()), last_date = "core"),
"last_date is a character. Did you forget to convert to Date?")
"last_date \\(core\\) could not be converted to Date. Dates must be in ISO 8601 standard format \\(yyyy-mm-dd\\)")

expect_error(incidence(1, "week"),
"The interval 'week' can only be used for Dates")
Expand All @@ -240,6 +255,26 @@ test_that("corner cases", {
expect_warning(incidence(c(dat_dates, as.Date("1900-01-01"))),
"greater than 18262 days \\[1900-01-01 to"
)

msg <- 'Not all dates are in ISO 8601 standard format \\(yyyy-mm-dd\\). The first incorrect date is'
expect_error(incidence('daldkadl'), paste(msg, "daldkadl"))

dats <- as.character(Sys.Date() + sample(-10:10, 5))
dats[3] <- "1Q84-04-15"
expect_error(incidence(dats), paste(msg, "1Q84-04-15"))

dats[3] <- "2018-69-11"
expect_error(incidence(dats), paste(msg, "2018-69-11"))

dats[3] <- "01-01-11"
expect_error(incidence(dats), paste(msg, "01-01-11"))

dats[3] <- "01-Apr-11"
expect_error(incidence(dats), paste(msg, "01-Apr-11"))

msg <- paste0("Input could not be converted to date. Accepted formats are:\n",
"Date, POSIXct, integer, numeric, character")
expect_error(incidence(factor("2001-01-01")), msg)
})

test_that("incidence constructor can handle missing data", {
Expand Down Expand Up @@ -318,7 +353,7 @@ test_that("user-defined group levels are preserved", {
test_that("Printing returns the object", {


x <- incidence(as.Date("2001-01-01"))
x <- incidence("2001-01-01")
y <- incidence(1:2, groups = factor(1:2))
z <- incidence(dat_dates, interval = 7)
expect_equal_to_reference(capture.output(print(x)),
Expand All @@ -329,20 +364,3 @@ test_that("Printing returns the object", {
file = "rds/print3.rds")
})

test_that("incidence returns error if input not in accepted format", {

msg <- 'Input is a character. Did you forget to convert to Date?'
expect_error(incidence('daldkadl'), msg)
expect_error(incidence('2001-01-01'), msg)

msg <- paste0("Input could not be converted to date. Accepted formats are:\n",
"Date, POSIXct, integer, numeric")
expect_error(incidence(factor("2001-01-01")), msg)

msg <- 'first_date is a character. Did you forget to convert to Date?'
expect_error(incidence(as.Date('2016-02-29'), "year", first_date = '2016-02-29'), msg)

msg <- 'last_date is a character. Did you forget to convert to Date?'
expect_error(incidence(as.Date('2016-02-29'), "year", last_date = '2016-02-29'), msg)

})