as.Date在一系列基于周的日期中产生意外结果

时间:2017-01-18 16:23:35

标签: r date as.date

我正在努力将基于周的日期转换为基于月份的日期。

检查我的工作时,我在数据中发现了以下问题,这是对as.Date()的简单调用的结果

as.Date("2016-50-4", format = "%Y-%U-%u")
as.Date("2016-50-5", format = "%Y-%U-%u")
as.Date("2016-50-6", format = "%Y-%U-%u")
as.Date("2016-50-7", format = "%Y-%U-%u") # this is the problem

前面的代码会产生前3行的正确日期:

"2016-12-15"
"2016-12-16"
"2016-12-17"  

然而,最后一行代码可以追溯到1周:

 "2016-12-11"

有人能解释一下这里发生了什么吗?

3 个答案:

答案 0 :(得分:7)

一年中的一周工作变得非常棘手。您可以尝试使用ISOweek包转换日期:

# create date strings in the format given by the OP
wd <- c("2016-50-4","2016-50-5","2016-50-6","2016-50-7", "2016-51-1", "2016-52-7")
# convert to "normal" dates
ISOweek::ISOweek2date(stringr::str_replace(wd, "-", "-W"))

结果

#[1] "2016-12-15" "2016-12-16" "2016-12-17" "2016-12-18" "2016-12-19" "2017-01-01"

属于Date类。

请注意,基于ISO周的日期格式为yyyy-Www-d,在周数之前有一个大写W。这需要将其与标准的基于月份的日期格式yyyy-mm-dd区分开来。

因此,为了使用ISOweek2date()转换OP提供的日期字符串,必须在第一个连字符之后插入W,这是通过替换第一个-来实现的。每个字符串中都有-W

另请注意,ISO周从星期一开始,一周的日期编号为1到7.属于ISO周的年份可能与日历年不同。从上面的示例日期可以看出,基于周的日期2016-W52-7转换为2017-01-01

关于ISOweek

早在2011年,Windows版R中的%G无法使用%g%u%Vstrptime()格式规范这很烦人,因为我不得不准备每周报告,包括每周比较。我花了几个小时找到一个解决方案来处理ISO周,ISO工作日和ISO年。最后,我最终创建了ISOweek包并将其发布为on CRAN。今天,该软件包仍有其优点,因为输入时忽略了上述格式(有关详细信息,请参阅?strptime)。

答案 1 :(得分:5)

正如@lmo在评论中所说,%u代表工作日的十进制数字(1-7,星期一为1),%U代表一年中的星期几十进制数(00-53)使用星期日作为第一天。因此,as.Date("2016-50-7", format = "%Y-%U-%u")将导致"2016-12-11"

但是,如果应该提供"2016-12-18",那么您应该使用星期一作为开始日的周格式。根据{{​​1}}的文档,您可以期望格式?strptime因此给出正确的输出,其中"%Y-%V-%u"代表一年中的星期,作为十进制数(01-53)与星期一作为第一天。

不幸的是,它没有:

%V

然而,在> as.Date("2016-50-7", format = "%Y-%V-%u") [1] "2016-01-18" 的解释结束时,它已接受“接受但在输入时忽略意味着它将无效。

您可以按照以下方式规避此行为,以获取正确的日期:

%V

给出:

# create a vector of dates
d <- c("2016-50-4","2016-50-5","2016-50-6","2016-50-7", "2016-51-1")

# convert to the correct dates
as.Date(paste0(substr(d,1,8), as.integer(substring(d,9))-1), "%Y-%U-%w") + 1

答案 2 :(得分:2)

问题是因为对于%u1 Monday7是本周的Sunday。由于%U假设周从星期日开始,这个问题变得更加复杂。

对于format = "%Y-%U-%u"的给定输入和预期行为,第4行的输出与前3行的输出一致。

也就是说,如果您想使用format = "%Y-%U-%u",则应预先处理您的输入。在这种情况下,第四行必须是

所揭示的as.Date("2016-51-7", format = "%Y-%U-%u")
format(as.Date("2016-12-18"), "%Y-%U-%u")
# "2016-51-7"

相反,您目前正在通过"2016-50-7"

更好的方法可能是使用Uwe Block's回答中建议的方法。由于您对"2016-50-4"转换为"2016-12-15"感到满意,我怀疑您的原始数据中,周一也被计为1。您还可以创建一个自定义函数,更改%U的值以计算周数,就像星期一开始一周,以便输出符合您的预期。

#Function to change value of %U so that the week begins on Monday
pre_process = function(x, delim = "-"){
    y = unlist(strsplit(x,delim))
    # If the last day of the year is 7 (Sunday for %u),
    # add 1 to the week to make it the week 00 of the next year
    # I think there might be a better solution for this
    if (y[2] == "53" & y[3] == "7"){
        x = paste(as.integer(y[1])+1,"00",y[3],sep = delim)
    } else if (y[3] == "7"){
    # If the day is 7 (Sunday for %u), add 1 to the week 
        x = paste(y[1],as.integer(y[2])+1,y[3],sep = delim)
    }
    return(x)
}

用法是

as.Date(pre_process("2016-50-7"), format = "%Y-%U-%u")
# [1] "2016-12-18"

我不太确定如何在周日结束时如何处理。