9

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

在检查我的工作时,我在我的数据中发现了以下问题,这是一个简单调用的结果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"

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

4

3 回答 3

9

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

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

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

关于ISOweek包裹

早在 2011 年,%GWindows版本的 R 中还没有%g%u、 和格式规范。这很烦人,因为我必须准备包括每周比较在内的每周报告。我花了几个小时寻找处理 ISO 周、ISO 工作日和 ISO 年的解决方案。最后,我最终创建了包并将其发布到 CRAN 上。今天,该软件包仍然有其优点,因为上述格式在输入时会被忽略(详情请参阅)。 %Vstrptime()ISOweek?strptime

于 2017-01-18T18:13:49.067 回答
5

正如@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它的解释的最后是“接受但在输入时忽略”意味着它不起作用。

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

# 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"
于 2017-01-18T17:16:29.023 回答
2

问题是因为 for %u, 1isMonday7isSunday是一周的。%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 的回答中建议的方法。"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"

我不太确定如何在星期天结束一年。

于 2017-01-18T17:21:15.807 回答