File: fractional_years.R

package info (click to toggle)
r-cran-popepi 0.4.13%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,656 kB
  • sloc: sh: 13; makefile: 2
file content (157 lines) | stat: -rw-r--r-- 4,687 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157


#' @title Fractional Years
#' @author Joonas Miettinen
#' @description Using Date objects, calculates given
#' dates as fractional years.
#' @param x a `Date` object, or anything that `[as.Date]`
#' accepts
#' @param year.length character string, either `"actual"` or
#' `"approx"`; can be abbreviated; see **Details**
#' @param ... additional arguments passed on to `[as.Date]`;
#' typically `format` when `x` is a character string variable,
#' and `origin` when `x` is numeric
#' @import data.table
#' @export
#' @details
#'
#' `x` should preferably be a `Date` or `IDate`
#' object, although it can also be a character string variable
#' which is coerced internally to `Date` format
#' using `[as.Date.character]`.
#'
#' When ` year.length = 'actual' `, fractional years are calculated as
#' ` year + (day_in_year-1)/365 ` for non-leap-years
#' and as ` year + (day_in_year-1)/366 ` for leap years.
#' If ` year.length = 'approx' `, fractional years are always
#' calculated as in ` year + (day_in_year-1)/365.242199 `.
#'
#' There is a slight difference, then, between the two methods
#' when calculating durations between fractional years. For
#' meticulous accuracy one might instead want to calculate durations using
#' dates (days) and convert the results to fractional years.
#'
#' Note that dates are effectively converted to fractional years at
#' ` 00:00:01 ` o'clock:
#'
#'
#' ` get.yrs("2000-01-01") = 2000 `, and
#' ` get.yrs("2000-01-02") = 2000 + 1/365.242199 `.
#'
#'
#' @seealso
#' `[Epi::cal.yr]`, `[as.Date.yrs]`, `[as.Date]`
#'
#' @return
#' A numeric vector of fractional years.
#'
#' @examples
#'
#' data("sire")
#' sire$dg_yrs <- get.yrs(sire$dg_date)
#' summary(sire$dg_yrs)
#'
#' ## see: ?as.Date.yrs
#' dg_date2 <- as.Date(sire$dg_yrs)
#' summary(as.numeric(dg_date2 - as.Date(sire$dg_date)))
#'
#' ## Epi's cal.yr versus get.yrs
#' d <- as.Date("2000-01-01")
#' Epi::cal.yr(d) ## 1999.999
#' get.yrs(d) ## 2000
#'
#' ## "..." passed on to as.Date, so character / numeric also accepted as input
#' ## (and whatever else as.Date accepts)
#' get.yrs("2000-06-01")
#' get.yrs("20000601", format = "%Y%m%d")
#' get.yrs("1/6/00", format = "%d/%m/%y")
#'
#' get.yrs(100, origin = "1970-01-01")
#'
#'
get.yrs <- function(x, year.length = "approx", ...) {
  as.yrs(x, year.length = year.length, ...)
}


as.yrs <- function(x, year.length, ...) {
  UseMethod("as.yrs")
}

#' @export
as.yrs.Date <- function(x, year.length = "approx", ...) {
  year.length <- match.arg(year.length, c("actual", "approx"))

  yl <- 365.242199
  y <- year(x)
  if (year.length == "actual") {
    yl <- ifelse(is_leap_year(y), 366L, 365L)
  }
  d <- yday(x)

  yrs <- y + (d - 1L)/yl
  setattr(yrs, "year.length", year.length)
  setattr(yrs, "class", c("yrs", "numeric"))
  yrs
}

#' @export
as.yrs.default <- function(x, year.length = "approx", ...) {

  x <- as.Date(x, ...)
  as.yrs(x, year.length = year.length)

}



#' @title Coerce Fractional Year Values to Date Values
#' @author Joonas Miettinen
#' @param x an `yrs` object created by `get.yrs`
#' @param ... unused, included for compatibility with other `as.Date`
#' methods
#' @description Coerces an `yrs` object to a `Date` object.
#' Some loss of information comes if `year.length = "approx"`
#' was set when using `[get.yrs]`, so the transformation back
#' to `Date` will not be perfect there. With `year.length = "actual"`
#' the original values are perfectly retrieved.
#' @examples
#' data("sire", package = "popEpi")
#'
#' ## approximate year lengths: here 20 % have an extra day added
#' sire$dg_yrs <- get.yrs(sire$dg_date)
#' summary(sire$dg_yrs)
#' dg_date2 <- as.Date(sire$dg_yrs)
#' summary(as.numeric(dg_date2 - as.Date(sire$dg_date)))
#'
#' ## using actual year lengths
#' sire$dg_yrs <- get.yrs(sire$dg_date, year.length = "actual")
#' summary(sire$dg_yrs)
#' dg_date2 <- as.Date(sire$dg_yrs)
#' summary(as.numeric(dg_date2 - as.Date(sire$dg_date)))
#' @seealso `[get.yrs]`
#' @return
#' A vector of `Date` values based on the input fractional years.
#' @export
as.Date.yrs <- function(x, ...) {

  yl <- attr(x, "year.length")
  if (is.null(yl)) {
    warning("x did not contain meta information about year length used ",
            "when forming the yrs object. Assuming 'approx'.")
    yl <- "approx"
  }

  y <- as.integer(x)

  mu <- 365.242199
  if (yl == "actual") {
    mu <- ifelse(is_leap_year(y), rep(365L, length(x)), rep(364L, length(x)))
  }
  x <- x + 1L/mu
  yd <- as.integer((x-y)*mu)
  d <- as.Date(paste0(y, "-01-01")) + yd
  d
}