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"
问题是因为对于 %u
,1
是 Monday
并且 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
)。
我正在将基于周的日期转换为基于月的日期。
在检查我的工作时,我在我的数据中发现了以下问题,这是对 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"
问题是因为对于 %u
,1
是 Monday
并且 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
)。