我需要完成一个常见任务,即根据出生日期和任意日期计算年龄(以年、月或周为单位)。然而,我通常需要对许多记录(> 3亿)执行此操作,因此性能是一个关键问题。
经过在SO和Google上的快速搜索,我找到了三种选择:
- 使用常见的算术过程(/365.25)(link)
- 使用包
lubridate
中的函数new_interval()
和duration()
(link) - 使用包
eeptools
中的函数age_calc()
(link, link, link)
下面是我的示例代码:
# 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's 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
没有这样的错误(尽管我还没有查看代码/阅读更多关于这些函数使用的方法),这就是为什么我想使用它们,但它们的性能对我的实际应用程序并不起作用。
是否有任何有效和准确计算年龄的方法?
编辑
糟糕,似乎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
lubridate
更快/更简单的东西,我会感到惊讶。如果你真的需要提高性能,我的建议是首先使用算术方法,然后再使用lubridate
方法重新处理所有“接近的调用”(例如,如果abs(floor(age) - age) < 0.01
,那么使用lubridate
)。 - Señor Olubridate
的经验用户?正如我在编辑后的问题中所提到的,我发现它会出错(也许比算术方法更多),但我已经在几篇帖子中读到,lubridate
确实是能够准确计算年龄的R包之一。所以现在我想知道我是否做错了什么。(我认为没有,我基本上是按照示例进行操作,这很简单,但只是再次确认一下) - Hernando Casasdifftime(givendate, birthdate) / 365.25
而不是(givendate - birthdate) / 365.25)
可以快约5%,但这不是一种解决方案。如果你需要进行算术运算,这可能会有用。请注意,不要改变原文的意思。 - Molx-.Date
更健壮地调用difftime
。更快的应该是(unclass(givendate) - unclass(birthdate)) / 365.25
,因为它可以跳过difftime
的开销。 - MichaelChirico