Передо мной стоит общая задача расчета возраста (в годах, месяцах или неделях) с учетом даты рождения и произвольной даты. Дело в том, что довольно часто мне приходится делать это на множестве записей (> 300 миллионов), поэтому ключевым моментом здесь является производительность.
После быстрого поиска в SO и Google я нашел 3 варианта:
- Общая арифметическая процедура (/365.25) (ссылка)
- Использование функций
new_interval()
иduration()
из пакетаlubridate
(ссылка) - Функция
age_calc()
из пакетаeeptools
(ссылка, ссылка, ссылка)
Итак, вот мой игрушечный код:
# Some toy birthdates
birthdate <- as.Date(c("1978-12-30", "1978-12-31", "1979-01-01",
"1962-12-30", "1962-12-31", "1963-01-01",
"2000-06-16", "2000-06-17", "2000-06-18",
"2007-03-18", "2007-03-19", "2007-03-20",
"1968-02-29", "1968-02-29", "1968-02-29"))
# Given dates to calculate the age
givendate <- as.Date(c("2015-12-31", "2015-12-31", "2015-12-31",
"2015-12-31", "2015-12-31", "2015-12-31",
"2050-06-17", "2050-06-17", "2050-06-17",
"2008-03-19", "2008-03-19", "2008-03-19",
"2015-02-28", "2015-03-01", "2015-03-02"))
# Using a common arithmetic procedure ("Time differences in days"/365.25)
(givendate-birthdate)/365.25
# Use the package lubridate
require(lubridate)
new_interval(start = birthdate, end = givendate) /
duration(num = 1, units = "years")
# Use the package eeptools
library(eeptools)
age_calc(dob = birthdate, enddate = givendate, units = "years")
Давайте поговорим позже о точности и сосредоточимся в первую очередь на производительности. Вот код:
# Now let compare the performance of the alternatives using microbenchmark
library(microbenchmark)
mbm <- microbenchmark(
arithmetic = (givendate - birthdate) / 365.25,
lubridate = new_interval(start = birthdate, end = givendate) /
duration(num = 1, units = "years"),
eeptools = age_calc(dob = birthdate, enddate = givendate,
units = "years"),
times = 1000
)
# And examine the results
mbm
autoplot(mbm)
Вот результаты:
Итог: производительность функций lubridate
и eeptools
намного хуже, чем арифметический метод (/365.25 как минимум в 10 раз быстрее). К сожалению, арифметический метод недостаточно точен, и я не могу позволить себе несколько ошибок, которые этот метод допустит.
из-за того, как современный григорианский календарь построен, нет простой арифметики метод, который определяет возраст человека, установленный согласно общее использование - общее использование, означающее, что люди возраст всегда должен быть целым числом, которое увеличивается ровно на день рождения ". (ссылка)
Как я читал в некоторых сообщениях, lubridate
и eeptools
не делают таких ошибок (хотя я не смотрел код/читал больше об этих функциях, чтобы узнать, какой метод они используют) и поэтому я хотел их использовать., но их производительность не работает для моего реального приложения.
Есть идеи по поводу эффективного и точного метода расчета возраста?
EDIT
Опс, кажется, lubridate
также делает ошибки. И, очевидно, основываясь на этом игрушечном примере, он делает больше ошибок, чем арифметический метод (см. строки 3, 6, 9, 12). (я что-то не так делаю?)
toy_df <- data.frame(
birthdate = birthdate,
givendate = givendate,
arithmetic = as.numeric((givendate - birthdate) / 365.25),
lubridate = new_interval(start = birthdate, end = givendate) /
duration(num = 1, units = "years"),
eeptools = age_calc(dob = birthdate, enddate = givendate,
units = "years")
)
toy_df[, 3:5] <- floor(toy_df[, 3:5])
toy_df
birthdate givendate arithmetic lubridate eeptools
1 1978-12-30 2015-12-31 37 37 37
2 1978-12-31 2015-12-31 36 37 37
3 1979-01-01 2015-12-31 36 37 36
4 1962-12-30 2015-12-31 53 53 53
5 1962-12-31 2015-12-31 52 53 53
6 1963-01-01 2015-12-31 52 53 52
7 2000-06-16 2050-06-17 50 50 50
8 2000-06-17 2050-06-17 49 50 50
9 2000-06-18 2050-06-17 49 50 49
10 2007-03-18 2008-03-19 1 1 1
11 2007-03-19 2008-03-19 1 1 1
12 2007-03-20 2008-03-19 0 1 0
13 1968-02-29 2015-02-28 46 47 46
14 1968-02-29 2015-03-01 47 47 47
15 1968-02-29 2015-03-02 47 47 47