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

10

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

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

前面的代码对前三行给出了正确的日期:

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

然而,最后一行代码会回溯1周:

 "2016-12-11"

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


3
问题在于:%u 以周一为起始,而 %U 以周日为起始,因此 as.Date("2016-50-7", format = "%Y-%U-%u") 被解释为第50周的第一天(即周日)。请参考 ?strptime 进行验证。 - lmo
根据@Imo的评论,as.Date("2016-50-7", format = "%Y-%V-%u")似乎可以工作,但对于某些年份,它会产生与您的示例不同的结果 - 这有多重要可能取决于您的应用程序。 - Miff
在我的电脑上,@Miff 的结果是"2016-01-18",这不是正确的日期。 - Jaap
在我的电脑上,对于所有输入的行,即“2016-50-4”等,我得到了Jaap提到的相同结果“2016-01-18”。@Miff - KoenV
@lmo,我会说as.Date("2016-50-7", format = "%Y-%U-%u")被解释为第50周的第七天(星期日)。 - d.b
@DarshanBaral 仔细查看原帖的输出,或者复制粘贴每行代码,你就会明白我的意思。 - lmo
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区分开来。

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

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

关于ISOweek

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


感谢您的贡献和时间。我将会结合Darshan Barans的阐述来处理您的帖子。 - KoenV
我对你的提议进行了一些手动测试:即使用ISOweek包的一行代码。到目前为止,这似乎完美地运作。我现在将在我的完整数据集上运行它并报告结果。 - KoenV
在其所有的简单性中,这个程序继续为我的数据集(包含2014-2017年的日期)无问题地工作。非常感谢! - KoenV
我已经在一个包含从2004年到2017年1月中旬的日期的数据集上测试了Uwe Block的解决方案。这种方法完美地运作。 - KoenV
@KoenV 很高兴听到这个好消息,也感谢您进行的所有测试。 - Uwe

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"

谢谢。这似乎运行良好,但有一个例外,当一周中的天数等于1时,例如“2016-50-1”,会导致NA。 - KoenV
1
@KoenV 已修复,我认为;添加了一个带有 1 的示例进行演示。 - Frank
@Frank。再次感谢您。这似乎完美地运作。稍后我将在更大的数据集上进行一些额外的测试,并在此论坛上报告结果。 - KoenV
@Jaap @Frank 正确的格式字符串应该是 "%G-%V-%u"%G 是基于周的年份。正如已经提到的,这仅适用于输出,因为这些格式说明符在输入时被接受但被忽略。 - Uwe
1
@Frank,你的提议对大多数日期有效,但在年底附近的一些日期上无效。它不适用于以下日期(不全):"2014-52-6" "2015-53-6" "2015-53-4" "2015-53-1" "2015-53-2" 这会生成NAs。 - KoenV

2
问题出在对于%u1代表周一,7代表周日。而问题更加复杂的是,%U默认把周日作为一周的第一天。
对于给定的输入和期望的format = "%Y-%U-%u"的行为,第四行的输出与前三行的输出一致。
也就是说,如果你想使用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"

我不太确定如何处理当年底是星期日的情况。


1
谢谢。我也会测试你的方法。为了测试以星期日结束的年份,我需要包括2006年。在我能够运行这样的测试之前,我需要时间来获取这些原始数据。目前,我正在使用2014-2017年的数据进行第一次探索性数据分析。 - KoenV
1
@KoenV @Darshan 正确的格式字符串应该是 "%G-%V-%u"%G 是基于周的年份。正如已经提到的,这仅适用于输出,因为这些格式说明符在输入时被接受但被忽略。 - Uwe

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