Reputation: 73
i have estimated a variable age.first.union
as a time difference using lubridate
by subracting the date of wedding wdow
from the date of birth wdob
. I got the following numeric vector
head(wm$age.first.union, 3)
[1] 15.43014 12.67123 17.34247
I would like to have the decimals converted into months (and possibly also into days, but that's a minor detail), so the first value would be 15 years and 5 months. What I did was to create a series of new variables and then perform some calculations. To get the number of months, first, I duplicated and truncated the age.first.union
variable. Then I estimated the difference between the two to get only the decimal part and then used proportions (e.g. 0.43 : 10 = x : 12
) to get the months.
I looked into the lubridate
documentation but I could not find much on this. I tried the following
years(floor(dseconds(15.43014)))
but I got only the years
[1] "15y 0m 0d 0H 0M 0S"
One idea would be to get the durations in seconds
seconds(floor(dyears(15.43014)))
[1] "486604895S"
but then the challenge would be that months have difference lengths. Even an approximation of years = 365 days, and months = 30 days would be more then perfect, but I do not know how to do it apart from lengthy calculations.
One final idea would be to have years and month using the calculation as described at the beginning of this post, and then merge the two variables into the final one using something similar to make_date
(but it looks like a make_duration
does not seem to exist yet).
The whole process looks quite cumbersome to me, anyone has a different take?
Many thanks
Manolo
Upvotes: 0
Views: 1223
Reputation: 269441
We can define our own ym
S3 class to represent year/month objects. Here we define several ym
methods as well as extractor functions for years and months. The as.data.frame.ym
method is a partial implementation. We have defined a month to be 1/12th of a year.
as.ym <- function(x, ...) structure(x, class = "ym")
as.data.frame.ym <- function(x, ...)
structure(list(x), row.names = seq_along(x), class = "data.frame")
years.ym <- as.integer
months.ym <- function(x) 12 * as.numeric(x) %% 1
format.ym <- function(x, ...) paste0(years.ym(x), "Y ", round(months.ym(x)), "M")
print.ym <- function(x, ...) print(format(x), ...)
# test
x <- c(15.43014, 12.67123, 17.34247) # test input
xx <- as.ym(x)
xx
## [1] "15Y 5M" "12Y 8M" "17Y 4M"
DF <- data.frame(x, xx)
DF
x xx
1 15.43014 15Y 5M
2 12.67123 12Y 8M
3 17.34247 17Y 4M
years.ym(xx)
## [1] 15 12 17
months.ym(xx)
## [1] 5.16168 8.05476 4.10964
class(xx)
## [1] "ym"
To extend this to include days, as well, we assume that there are 365.25 days in a year and, again, we use 12 months in a year. We create a ymd
S3 class for this.
as.ymd <- function(x, ...) structure(x, class = "ymd")
as.data.frame.ymd <- function(x, ...)
structure(list(x), row.names = seq_along(x), class = "data.frame")
years.ymd <- as.integer
months.ymd <- function(x) as.integer(12 * as.numeric(x) %% 1)
days.ymd <- function(x) (365.25 * as.numeric(x)) %% (365.25 / 12)
format.ymd <- function(x, ...)
paste0(years.ymd(x), "Y ", as.integer(months.ymd(x)), "M ", round(days.ymd(x), 1), "D")
print.ymd <- function(x, ...) print(format(x), ...)
xx <- as.ymd(x)
xx
## [1] "15Y 5M 4.9D" "12Y 8M 1.7D" "17Y 4M 3.3D"
DF <- data.frame(x, xx)
DF
x xx
1 15.43014 15Y 5M 4.9D
2 12.67123 12Y 8M 1.7D
3 17.34247 17Y 4M 3.3D
years.ymd(xx)
## [1] 15 12 17
months.ymd(xx)
## [1] 5 8 4
days.ymd(xx)
## [1] 4.921135 1.666758 3.337167
class(xx)
## [1] "ymd"
Upvotes: 0
Reputation: 50668
While lubridate
provides a function decimal_date
to convert a fractional date to D-M-Y date, you seem to be dealing with durations. So this won't work.
However, you can quite easily define a custom function to extract the integer year, month and fractional day (based on an average 30.42
days per month in a regular year):
age <- c(15.43014 12.67123 17.34247)
f <- function(x) {
year <- floor(x);
month <- floor((x - year) * 12);
day <- ((x - year) * 12 - month) * 30.42;
return(sprintf("%i years, %i months, %3.2f days", year, month, day))
}
lapply(age, f);
#[[1]]
#[1] "15 years, 5 months, 4.92 days"
#
#[[2]]
#[1] "12 years, 8 months, 1.67 days"
#
#[[3]]
#[1] "17 years, 4 months, 3.34 days"
If you want to return the integer year, month and fractional day you can define f
as
f <- function(x) {
year <- floor(x);
month <- floor((x - year) * 12);
day <- ((x - year) * 12 - month) * 30.42;
return(list(year = year, month = month, day = day))
}
which gives you e.g.
sapply(age, f);
# [,1] [,2] [,3]
#year 15 12 17
#month 5 8 4
#day 4.918306 1.665799 3.335249
Upvotes: 2