将年份和月份("yyyy-mm"格式)转换为日期?

114

我有一个数据集,它看起来像这样:

Month    count
2009-01  12
2009-02  310
2009-03  2379
2009-04  234
2009-05  14
2009-08  1
2009-09  34
2009-10  2386

我想绘制数据(以月份为x轴,计数为y轴)。由于数据中存在间隔,我想将月份信息转换为日期。我尝试了:

as.Date("2009-03", "%Y-%m")

但它没有起作用。怎么了?看起来as.Date()还需要一个日期,不能为日期设置标准值?哪个函数可以解决我的问题?

9个回答

100

由于日期对应于数值和起始日期,因此确实需要天数。如果您确实需要将数据格式化为日期格式,您可以通过手动将其粘贴到日期中来将日期固定为每个月份的第一天:

month <- "2009-03"
as.Date(paste(month, "-01", sep=""))

还有哪些日期格式?我看到了一些 POSIX 和 ISO 的东西,但我不确定它们是否是不同的格式。我认为它们只是函数... - R_User
24
值得注意的是,您可以在格式化程序中将日期指定为相同的日期,因此您可以执行 as.Date(month, format='%Y-%m-01') 并实现相同的结果。这种方法对我来说更加自然,因为在每个月份都指定相同日期更多地涉及到日期格式而不是字符串操作,但也许这是无意义的。 - JBecker
31
您的建议对我无效。> as.Date("2016-01", format="%Y-%m-01") # [1] NA。我正在使用 R 3.3.1版本。 - n8sty

66

试试这个。(在这里我们使用text=Lines来使示例自包含,但实际上我们会用文件名替换它。)

Lines <- "2009-01  12
2009-02  310
2009-03  2379
2009-04  234
2009-05  14
2009-08  1
2009-09  34
2009-10  2386"

library(zoo)
z <- read.zoo(text = Lines, FUN = as.yearmon)
plot(z)

对于这些数据,X轴并不那么好看,但是如果你在现实中有更多的数据,它可能还可以,或者你可以使用在 ?plot.zoo 示例部分展示的高级X轴代码。

上面创建的z zoo系列具有"yearmon"时间索引,看起来像这样:

> z
Jan 2009 Feb 2009 Mar 2009 Apr 2009 May 2009 Aug 2009 Sep 2009 Oct 2009 
      12      310     2379      234       14        1       34     2386 

"yearmon"也可以单独使用:

> as.yearmon("2000-03")
[1] "Mar 2000"

注意:

  1. "yearmon" 类对象按照日历顺序排序。

  2. 这将在等间隔的月度点上绘制图形,这可能是所需的;但是,如果希望按照每个月的天数成比例地分隔不均匀间隔地绘制点,则将 z 的索引转换为 "Date" 类: time(z) <- as.Date(time(z))


37

如果需要将日期格式化为Date格式,最简洁的解决方案如下:

library(zoo)
month <- "2000-03"
as.Date(as.yearmon(month))
[1] "2000-03-01"

as.Date会将每个月的第一天固定为yearmon对象。


32

你还可以使用 lubridate 包中的 parse_date_timefast_strptime 函数来实现:

> parse_date_time(dates1, "ym")
[1] "2009-01-01 UTC" "2009-02-01 UTC" "2009-03-01 UTC"

> fast_strptime(dates1, "%Y-%m")
[1] "2009-01-01 UTC" "2009-02-01 UTC" "2009-03-01 UTC"

两者的区别在于parse_date_time允许使用lubridate风格的格式说明,而fast_strptime需要与strptime相同的格式说明。

要指定时区,可以使用tz参数:

> parse_date_time(dates1, "ym", tz = "CET")
[1] "2009-01-01 CET" "2009-02-01 CET" "2009-03-01 CET"

当您的日期时间数据存在不规则之处时,您可以使用截断参数来指定允许多少个不规则:

> parse_date_time(dates2, "ymdHMS", truncated = 3)
[1] "2012-06-01 12:23:00 UTC" "2012-06-01 12:00:00 UTC" "2012-06-01 00:00:00 UTC"

使用的数据:

dates1 <- c("2009-01","2009-02","2009-03")
dates2 <- c("2012-06-01 12:23","2012-06-01 12",'2012-06-01")

将字符变量转换为date格式后,使用parse_date_time函数,是否有一种方法可以使用lubridate包以不同于"2009-01-01 UTC"的顺序查看它?我希望在我的数据集中首先看到日期,例如01-01-2009 - user63230
1
@user63230 请参阅?format;例如:format(your_date, "%d-%m-%Y")。但是这种方法有一个缺点:它会返回一个字符值而不是日期。 - Jaap
谢谢,但我试图避免使用format,因为你提到的原因。我认为可能有一种方法将其纳入lubridate包中,但似乎并没有。 - user63230

16

使用 anytime 软件包:

library(anytime)

anydate("2009-01")
# [1] "2009-01-01"

1
选择“01-01”有点奇怪,文档中有关于这个选择的说明吗?如果它总是选择月份的第一天,也许更具说明性的是展示anydate("2009-03") - lmo
1
@lmo 没有查看文档,我认为当 dd 缺失时选择第一天是“常见”的做法。 - zx8754
2
有道理。我模糊地记得然后找到了触发评论的原因。从?strptime的注释部分:输入字符串不需要完全指定日期:假定未指定的秒、分钟或小时为零,未指定的年、月或日为当前年、月或日。(但是,如果指定了一个月份,则该月份的日期必须由%d或%e指定,因为该月份的当前日期不一定有效。)看起来megatron的答案包含了as.Date中类似的文档片段。 - lmo
2
在1900年之前的年份,它无法正常工作。例如,我尝试了这个anytime('1870-01') - msh855
2
唯一对我有用的解决方案!使用 anydate() 而不是 anytime()。 - schroederadrian

7
事实上,正如之前(以及其他地方)在SO上提到的那样,为了将字符串转换为日期,您需要一个具体的月份日期。从as.Date()手册页面中可以看到:
如果日期字符串没有完全指定日期,则返回的答案可能是特定于系统的。最常见的行为是假定缺少的年、月或日是当前的年、月或日。如果它错误地指定了一个日期,可靠的实现将会报错并且日期被报告为NA。不幸的是,一些常见的实现(如)是不可靠的,并猜测所需含义。
一个简单的解决方案是将日期"01"粘贴到每个日期中,并使用strptime()将其指示为该月的第一天。
对于那些想要了解更多有关在R中处理日期和时间的背景知识的人:
在R中,时间使用POSIXctPOSIXlt类,而日期使用Date类。
日期存储为自1970年1月1日以来的天数,时间存储为自1970年1月1日以来的秒数。
例如:
d <- as.Date("1971-01-01")
unclass(d)  # one year after 1970-01-01
# [1] 365

pct <- Sys.time()  # in POSIXct
unclass(pct)  # number of seconds since 1970-01-01
# [1] 1450276559
plt <- as.POSIXlt(pct)
up <- unclass(plt)  # up is now a list containing the components of time
names(up)
# [1] "sec"    "min"    "hour"   "mday"   "mon"    "year"   "wday"   "yday"   "isdst"  "zone"  
# [11] "gmtoff"
up$hour
# [1] 9

执行日期和时间操作时:
plt - as.POSIXlt(d)
# Time difference of 16420.61 days

要处理日期,可以使用strptime()函数(以下示例摘自手册页面):

strptime("20/2/06 11:16:16.683", "%d/%m/%y %H:%M:%OS")
# [1] "2006-02-20 11:16:16 EST"

# And in vectorized form:
dates <- c("1jan1960", "2jan1960", "31mar1960", "30jul1960")
strptime(dates, "%d%b%Y")
# [1] "1960-01-01 EST" "1960-01-02 EST" "1960-03-31 EST" "1960-07-30 EDT"

2

我认为@ben-rollert的解决方案是一个好的解决方案。

如果你想在新包的函数中使用这个解决方案,你必须小心。

在开发包时,建议使用语法packagename::function_name()(参见http://kbroman.org/pkg_primer/pages/depends.html)。

在这种情况下,你必须使用由zoo库定义的as.Date()版本。

以下是一个例子:

> devtools::session_info()
Session info ----------------------------------------------------------------------------------------------------------------------------------------------------
 setting  value                       
 version  R version 3.3.1 (2016-06-21)
 system   x86_64, linux-gnu           
 ui       RStudio (1.0.35)            
 language (EN)                        
 collate  C                           
 tz       <NA>                        
 date     2016-11-09                  

Packages --------------------------------------------------------------------------------------------------------------------------------------------------------

 package  * version date       source        
 devtools   1.12.0  2016-06-24 CRAN (R 3.3.1)
 digest     0.6.10  2016-08-02 CRAN (R 3.2.3)
 memoise    1.0.0   2016-01-29 CRAN (R 3.2.3)
 withr      1.0.2   2016-06-20 CRAN (R 3.2.3)

> as.Date(zoo::as.yearmon("1989-10", "%Y-%m")) 
Error in as.Date.default(zoo::as.yearmon("1989-10", "%Y-%m")) : 
  do not know how to convert 'zoo::as.yearmon("1989-10", "%Y-%m")' to class “Date”

> zoo::as.Date(zoo::as.yearmon("1989-10", "%Y-%m"))
[1] "1989-10-01"

所以,如果你正在开发一个软件包,良好的做法是使用:

zoo::as.Date(zoo::as.yearmon("1989-10", "%Y-%m"))

1

tidyverse 最近在 添加 一个名为 clock 的包,除了已经有的 lubridate 包外,它还具有一些不错的功能:

library(clock)

x <- year_month_day_parse(df$Month, format = "%Y-%m", precision = "month") 
# <year_month_day<month>[8]>
# [1] "2009-01" "2009-02" "2009-03" "2009-04" "2009-05" "2009-08" "2009-09" "2009-10"

日期操作和提取

这个输出是一个年-月-日向量,您仍然可以进行日期算术运算并应用其他常见函数,如预期的那样:

sort(x, decreasing = T)
# <year_month_day<month>[8]>
# [1] "2009-10" "2009-09" "2009-08" "2009-05" "2009-04" "2009-03" "2009-02" "2009-01"

add_months(x, 3)
# <year_month_day<month>[8]>
# [1] "2009-04" "2009-05" "2009-06" "2009-07" "2009-08" "2009-11" "2009-12" "2010-01"

add_years(x, -2)
# <year_month_day<month>[8]>
# [1] "2007-01" "2007-02" "2007-03" "2007-04" "2007-05" "2007-08" "2007-09" "2007-10"

get_month(x)
# [1]  1  2  3  4  5  8  9 10

如果需要,您还可以使用set_day来设置日期:

set_day(x, 1)
<year_month_day<day>[8]>
[1] "2009-01-01" "2009-02-01" "2009-03-01" "2009-04-01" "2009-05-01" "2009-08-01"
[7] "2009-09-01" "2009-10-01"

处理无效日期

如果您想要使用此结构干净地获取每个月的最后一天,invalid_* 函数集可以帮助您:

# not 31 days in Feb, Apr, Sep
y <- set_day(x, 31)
# <year_month_day<day>[8]>
# [1] "2009-01-31" "2009-02-31" "2009-03-31" "2009-04-31" "2009-05-31" "2009-08-31"
# [7] "2009-09-31" "2009-10-31"

invalid_any(y)
[1] TRUE

invalid_detect(y)
[1] FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE

您可以使用invalid_resolve处理无效日期,或者使用invalid_remove删除它们:

invalid_resolve(y, invalid = "previous")
<year_month_day<day>[8]>
[1] "2009-01-31" "2009-02-28" "2009-03-31" "2009-04-30" "2009-05-31" "2009-08-31"
[7] "2009-09-30" "2009-10-31"

从文档中,您可以为invalid参数指定以下值来处理无效日期:

"previous":时间上的上一个有效时刻。

"previous-day":时间上的上一天有效日期,保留当天时间。

"next":时间上的下一个有效时刻。

"next-day":时间上的下一天有效日期,保留当天时间。

"overflow":将输入无效的天数溢出。舍弃时间。

"overflow-day":将输入无效的天数溢出。保留时间。

"NA":用NA替换无效日期。

"error":在无效日期上报错。


1

使用 lubridate 中的 ym 方法。

月份可以是数字、缩写或全名,还可以有各种分隔符(甚至没有分隔符),例如:

library(lubridate)

ym(c("2012/September", "2012-Aug", "2012.07", 201204))
[1] "2012-09-01" "2012-08-01" "2012-07-01" "2012-04-01"

在给定的数据上:

ym(dat$Month)
[1] "2009-01-01" "2009-02-01" "2009-03-01" "2009-04-01" "2009-05-01"
[6] "2009-08-01" "2009-09-01" "2009-10-01"

请注意,如果您反过来使用它,例如 Sep/2022,也有my

数据

dat <- structure(list(Month = c("2009-01", "2009-02", "2009-03", "2009-04",
"2009-05", "2009-08", "2009-09", "2009-10"), count = c(12L, 310L,
2379L, 234L, 14L, 1L, 34L, 2386L)), class = "data.frame", row.names = c(NA,
-8L))

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