在R中计算年龄

19

我在R中有两个数据框,其中一个数据框包含人的出生年份:

YEAR
/1931
/1924

然后另一列显示较新的时间。

RECENT
09/08/2005
11/08/2005

我想做的是减去年份,以便可以计算他们的年龄,但我不确定如何处理。 请帮忙吗?


8个回答

51
以下函数接受日期对象向量并计算年龄,正确地考虑闰年。看起来比任何其他答案都要简单。
age = function(from, to) {
  from_lt = as.POSIXlt(from)
  to_lt = as.POSIXlt(to)

  age = to_lt$year - from_lt$year

  ifelse(to_lt$mon < from_lt$mon |
         (to_lt$mon == from_lt$mon & to_lt$mday < from_lt$mday),
         age - 1, age)
}

6
清晰、快速,仅使用基本函数。同时能正确处理闰年。应该是最受欢迎的答案。 - nograpes
为了避免使用ifelse(https://dev59.com/fGQo5IYBdhLWcg3wKcrH),代码如下:`out <- integer(length(year)); out[idx <- to_lt$mon < from_lt$mon] <- age - 1; out[!idx] <- age]` - MichaelChirico
@MichaelChirico 在提交之前请检查您的语法。有一个多余的],即使这样也不能正常工作。想象一下你是1980年12月出生的,现在是2018年12月...你自己算吧。 - MS Berends

9

我使用自定义函数,如下所示的代码,方便在 mutate 中使用,而且非常灵活(需要 lubridate 包)。

例子

get_age("2000-01-01")
# [1] 17
get_age(lubridate::as_date("2000-01-01"))
# [1] 17
get_age("2000-01-01","2015-06-15")
# [1] 15
get_age("2000-01-01",dec = TRUE)
# [1] 17.92175
get_age(c("2000-01-01","2003-04-12"))
# [1] 17 14
get_age(c("2000-01-01","2003-04-12"),dec = TRUE)
# [1] 17.92176 14.64231

功能

#' Get age
#' 
#' Returns age, decimal or not, from single value or vector of strings
#' or dates, compared to a reference date defaulting to now. Note that
#' default is NOT the rounded value of decimal age.
#' @param from_date vector or single value of dates or characters
#' @param to_date date when age is to be computed
#' @param dec return decimal age or not
#' @examples
#' get_age("2000-01-01")
#' get_age(lubridate::as_date("2000-01-01"))
#' get_age("2000-01-01","2015-06-15")
#' get_age("2000-01-01",dec = TRUE)
#' get_age(c("2000-01-01","2003-04-12"))
#' get_age(c("2000-01-01","2003-04-12"),dec = TRUE)
get_age <- function(from_date,to_date = lubridate::now(),dec = FALSE){
  if(is.character(from_date)) from_date <- lubridate::as_date(from_date)
  if(is.character(to_date))   to_date   <- lubridate::as_date(to_date)
  if (dec) { age <- lubridate::interval(start = from_date, end = to_date)/(lubridate::days(365)+lubridate::hours(6))
  } else   { age <- lubridate::year(lubridate::as.period(lubridate::interval(start = from_date, end = to_date)))}
  age
}

这是一个很好的函数。但是,为什么你决定在 if(dec) 中使用 /dyears(1)?年龄不更像是一个时间段吗?应该使用 /years(1) 才更合适吧? - tjebo
1
这是一个错误,但你的建议也不是很有效,我改为(lubridate :: days(365)+ lubridate :: hours(6)) - moodymudskipper
有趣的是将 days()hours() 结合起来。请问为什么选择 hours(6)?啊,糟了,我想我明白了。把它转换成小数。不过这样能处理闰年吗? - tjebo
1
这个想法是十进制年龄是“真实年龄”,它考虑了一年的恒定长度。因此,为了考虑闰年,一年应该是365.25天长,0.25天是6小时:)。我对dyears的作用有了错误的理解。尝试:lubridate::interval(start = "2000-01-01", end = "2001-01-01")/(lubridate::days(365)+lubridate::hours(6))然后lubridate::interval(start = "2000-03-01", end = "2001-03-01")/(lubridate::days(365)+lubridate::hours(6)) - moodymudskipper

9
你可以使用lubridate包来解决这个问题。
> library(lubridate)

我认为/1931不是一个常见的日期类,因此我假设所有条目都是字符字符串。

> RECENT <- data.frame(recent = c("09/08/2005", "11/08/2005"))
> YEAR <- data.frame(year = c("/1931", "/1924"))

首先,让我们告诉R最近的日期是日期。我假设日期是按照月/日/年的顺序排列的,因此我使用mdy()。如果它们按照日/月/年的顺序排列,只需使用dmy()。

> RECENT$recent <- mdy(RECENT$recent)
      recent
1 2005-09-08
2 2005-11-08

现在,让我们把年份转换成数字,这样我们就可以进行一些数学运算。
> YEAR$year <- as.numeric(substr(YEAR$year, 2, 5))

现在只需要做简单的数学计算。year()函数可以提取最近日期的年份值。
> year(RECENT$recent) - YEAR
  year
1   74
2   81

如果你的年份实际上是完整的日期,你可以使用以下代码获取年份差:

> YEAR1 <- data.frame(year = mdy("01/08/1931","01/08/1924"))
> as.period(RECENT$recent - YEAR1$year, units = "year")
[1] 74 years and 8 months   81 years and 10 months

1
按照你的示例,我得到了“天”而不是“年”。 - dpel
1
文档中说应该使用as.period(..., unit = "years")而不是units = "year" - user10915156

2

您可以进行一些格式化操作:

as.numeric(format(as.Date("01/01/2010", format="%m/%d/%Y"), format="%Y")) - 1930

使用您的数据:

> yr <- c(1931, 1924)
> recent <- c("09/08/2005", "11/08/2005")
> as.numeric(format(as.Date(recent, format="%m/%d/%Y"), format="%Y")) - yr
[1] 74 81

由于你的数据在一个数据框中(我假设它叫做df),所以代码应该更像这样:

as.numeric(format(as.Date(df$recent, format="%m/%d/%Y"), format="%Y")) - df$year

這段程式碼對我所貼出的資料有效,但我的資料集實際上有更多列。是否有辦法直接呼叫資料框架來完成這個任務? - Brian
以相同的方式进行操作。只需将recent和yr替换为您的df列即可。 - Shane

2

考虑您提供的数据示例:

> m <- data.frame(YEAR=c("/1931", "/1924"),RECENT=c("09/08/2005","11/08/2005"))
> m
   YEAR     RECENT
1 /1931 09/08/2005
2 /1924 11/08/2005

使用 strptime 函数提取年份:

> strptime(m[,2], format = "%m/%d/%Y")$year - strptime(m[,1], format = "/%Y")$year
[1] 74 81

1
为什么?面向对象编程的美妙之处在于拥有能够识别日期对象的方法,因此您不必自己处理这些问题。 - Vince

1
根据之前的答案,将您的列转换为日期对象并进行减法运算。需要在字符和数字之间进行一些类型转换:
> foo=data.frame(RECENT=c("09/08/2005","11/08/2005"),YEAR=c("/1931","/1924"))
> foo
      RECENT  YEAR
1 09/08/2005 /1931
2 11/08/2005 /1924
> foo$RECENTd = as.Date(foo$RECENT, format="%m/%d/%Y")
> foo$YEARn = as.numeric(substr(foo$YEAR,2,999))
> foo$AGE = as.numeric(format(foo$RECENTd,"%Y")) - foo$YEARn
> foo
      RECENT  YEAR    RECENTd YEARn AGE
1 09/08/2005 /1931 2005-09-08  1931  74
2 11/08/2005 /1924 2005-11-08  1924  81

请注意,我假设您的年份列中有那个斜杠。

此外,在询问日期相关问题时的提示是包括一个超过12号的日期,这样我们就知道您是按照月/日/年还是日/月/年的格式。


1
我认为这可能更直观,不需要格式化或剥离:

as.numeric(as.Date("2002-02-02") - as.Date("1924-08-03")) / 365

给出输出:

77.55342

然后您可以使用floor(),round()或ceiling()将其舍入为整数。

这并没有考虑闰年。 - nograpes
你可以使用365.25,这应该足够接近了。如果你在寻找年龄,实际(天数)年龄比日历年龄更重要吧? - Allen Wang
2
有时候,实际生活的天数完全没问题(也许更好),但在其他情况下,你真的想知道过去了多少个日历年。虽然两个按照通用定义为65岁的人可能活了不同数量的天数,但我们通常不想做出这种区分。例如,如果你正在计算某人是否有资格退休,几乎所有人都使用整年而不是天数来进行计算。 - nograpes

0

非常实用的方法,还支持使用lubridate包的向量:

age <- function(date.birth, date.ref = Sys.Date()) {
  if (length(date.birth) > 1 & length(date.ref) == 1) {
    date.ref <- rep(date.ref, length(date.birth))
  }

  date.birth.monthdays <- paste0(month(date.birth), day(date.birth)) %>% as.integer()
  date.ref.monthdays <- paste0(month(date.ref), day(date.ref)) %>% as.integer()

  age.calc <- 0

  for (i in 1:length(date.birth)) {
    if (date.birth.monthdays[i] <= date.ref.monthdays[i]) {
      # didn't had birthday
      age.calc[i] <- year(date.ref[i]) - year(date.birth[i])
    } else {
      age.calc[i] <- year(date.ref[i]) - year(date.birth[i]) - 1
    }
  }
  age.calc
}

这也包括闰年。我只是检查是否有人已经过生日了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接