【问题标题】:as.Date produces unexpected result in a sequence of week-based datesas.Date 在一系列基于周的日期中产生意外结果
【发布时间】:2017-06-03 03:03:06
【问题描述】:

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

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

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

【问题讨论】:

  • 问题是 %u 从星期一开始,%U 从星期日开始,所以 as.Date("2016-50-7", format = "%Y-%U-%u") 被解释为第 50 周的第一天(星期日)。请参阅 ?strptime 进行验证。
  • 在@Imo 的评论之后,as.Date("2016-50-7", format = "%Y-%V-%u") 似乎有效,但在几年内会产生与您的示例不同的结果 - 这有多重要可能取决于您的应用程序
  • @Miff 在我的计算机上导致"2016-01-18" 这不是正确的日期
  • @Miff 在我的电脑上我得到相同的结果 Jaap 提到的“2016-01-18”,对于所有输入行,即:“2016-50-4”等等
  • @lmo,我想说as.Date("2016-50-7", format = "%Y-%U-%u") 被解释为第 50 周的第七天(星期日)

标签: r date as.date


【解决方案1】:

正如@lmo 在 cmets 中所说,%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"

【讨论】:

  • 谢谢。这似乎工作正常,除了一个例外,当一周中的天数等于 1 时,如“2016-50-1”,导致 NA。
  • @KoenV 已修复,我认为;在演示中添加了一个带有1 的示例。
  • @Frank。再次感谢你。这似乎完美无缺。稍后我将对更大的数据集进行一些额外的测试,并在此论坛上进行报告。
  • @Jaap @Frank 正确的格式字符串是"%G-%V-%u"%G 是基于周的年份。如前所述,这仅适用于输出,因为这些格式说明符接受但在输入时被忽略。
  • @Frank 您的建议在大多数日期都有效,但在接近年底的一些日期无效。它不工作的日期是(不详尽):"2014-52-6" "2015-53-6" "2015-53-4" "2015-53-1" "2015-53-2" 这会生成 NA。
【解决方案2】:

问题是因为%u1Monday7Sunday 的一周。 %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"

我不太确定如何在周日结束一年。

【讨论】:

  • 谢谢。我也会测试你的方法。为了测试以星期日结束的年份,我需要包含 2006。在进行这样的测试之前,我需要时间来获取这些原始数据。目前,我正在 2014-2017 年进行第一次 EDA。
  • @KoenV @Darshan 正确的格式字符串是"%G-%V-%u"%G 是基于周的年份。如前所述,这仅适用于输出,因为这些格式说明符接受但在输入时被忽略。
【解决方案3】:

处理一年中的一周可能会变得非常棘手。您可以尝试使用 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 年,%G%g%u%V 格式规范在 Windows 版本的 R 中不适用于 strptime()。这很烦人,因为我必须每周准备包括每周比较的报告。我花了几个小时寻找处理 ISO 周、ISO 工作日和 ISO 年的解决方案。最后,我最终创建了ISOweek 包并发布了on CRAN。今天,该软件包仍然有其优点,因为上述格式在输入时被忽略(有关详细信息,请参阅?strptime)。

【讨论】:

  • 感谢您的贡献和时间。我将与 Darshan Barans 一起对你的帖子进行阐述。
  • 我对您的建议进行了一些手动测试:即使用 ISOweek 包的一行代码。到目前为止,这似乎工作得很好。我现在将运行这是我的完整数据集并报告。
  • 虽然简单,但对于日期为 2014 年至 2017 年(现在)的数据集,这仍然可以正常工作。非常感谢!
  • 我已经在 2004 年到 2017 年 1 月中旬的数据集上测试了 Uwe Block 的解决方案。这种方法非常有效。
  • @KoenV 很高兴听到这个消息并感谢所有的测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多