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

as.Date produces unexpected result in a sequence of week-based dates

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

在检查我的工作时,我在我的数据中发现了以下问题,这是对 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"

谁能解释一下这里发生了什么?

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

但是,如果那应该给出 "2016-12-18",那么您应该使用也以星期一作为起始日的周格式。根据 ?strptime 的文档,您会期望 "%Y-%V-%u" 格式给出正确的输出,其中 %V 代表一年中的星期,十进制数 (01–53) 和星期一作为第一天。

不幸的是,它没有:

> as.Date("2016-50-7", format = "%Y-%V-%u")
[1] "2016-01-18"

然而,在%V的解释末尾说"Accepted but ignored on input"意思是它不会起作用。

您可以通过以下方式规避此行为以获得正确的日期:

# 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

给出:

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

问题是因为对于 %u1Monday 并且 7 是一周的 Sunday。由于 %U 假设一周从星期日开始,问题变得更加复杂。

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

也就是说,如果你想使用format = "%Y-%U-%u",你应该pre-process你的输入。在这种情况下,第四行必须是as.Date("2016-51-7", format = "%Y-%U-%u"),如

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

相反,您目前正在传递 "2016-50-7"

更好的方法可能是使用 答案中建议的方法。由于您对 "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"

我不太清楚年末在星期天怎么处理。

处理一年中的一周可能会变得非常棘手。您可以尝试使用 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"

属于 class Date.

请注意,ISO week-based 日期格式是 yyyy-Www-d,周数前有大写字母 W。这是将其与标准 month-based 日期格式 yyyy-mm-dd 区分开来所必需的。

因此,为了使用 ISOweek2date() 转换 OP 提供的日期字符串,需要在第一个连字符后插入一个 W,这是通过替换第一个 - 在每个字符串中 -W

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

关于 ISOweek

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