Manolo
Manolo

Reputation: 73

lubridate convert decimals into months

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

Answers (2)

G. Grothendieck
G. Grothendieck

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"

Days

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

Maurits Evers
Maurits Evers

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"

Update

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

Related Questions