【问题标题】:How to get week numbers from dates?如何从日期中获取周数?
【发布时间】:2014-04-21 18:47:42
【问题描述】:

在 R 中寻找将日期转换为周数(年份)的函数,我从包 data.table 中选择了 week。 但是,我观察到一些奇怪的行为:

> week("2014-03-16") # Sun, expecting 11
[1] 11
> week("2014-03-17") # Mon, expecting 12
[1] 11
> week("2014-03-18") # Tue, expecting 12
[1] 12

为什么星期数在星期二而不是星期一变为 12?我错过了什么? (时区应该无关紧要,因为只有日期?!)

对于(基本)R 函数的其他建议也很受欢迎。

【问题讨论】:

  • 试试format(as.Date("2014-03-16"), "%U")format(as.Date("2014-03-16"), "%W")
  • @GSee 谢谢,但返回 11 而不是 12 用于以下内容:format(as.Date("2014-03-17"), "%U")format(as.Date("2014-03-17"), "%W") !?
  • 所以,转换为整数并加1。见?strptime
  • 这就是我现在正在做的事情,实际上。我只是想知道,为什么我必须做这个解决方法?我希望本周从星期一(欧盟)或星期日(美国)开始,而不是星期二?

标签: r date data.table week-number


【解决方案1】:

如果您想获得年份的周数,Grant Shannon 使用 strftime 的解决方案有效,但您需要对 1 月 1 日左右的日期进行一些更正。例如,2016-01-03 (yyyy-mm-dd) 是 2015 年的第 53 周,而不是 2016 年。而 2018-12-31 是 2019 年的第 1 周,而不是 2018 年。此代码提供了一些示例和解决方案。在“yearweek”列中,年份有时是错误的,在“yearweek2”中它们被更正(第 2 行和第 5 行)。

library(dplyr)
library(lubridate)

# create a testset
test <- data.frame(matrix(data = c("2015-12-31",
                                   "2016-01-03",
                                   "2016-01-04",
                                   "2018-12-30",
                                   "2018-12-31",
                                   "2019-01-01") , ncol=1, nrow = 6 ))
# add a colname
colnames(test) <- "date_txt"

# this codes provides correct year-week numbers
test <- test %>%
        mutate(date = as.Date(date_txt, format = "%Y-%m-%d")) %>%
        mutate(yearweek = as.integer(strftime(date, format = "%Y%V"))) %>%
        mutate(yearweek2 = ifelse(test = day(date) > 7 & substr(yearweek, 5, 6) == '01',
                                 yes  = yearweek + 100,
                                 no   = ifelse(test = month(date) == 1 & as.integer(substr(yearweek, 5, 6)) > 51,
                                               yes  = yearweek - 100,
                                               no   = yearweek)))
# print the result
print(test)

    date_txt       date yearweek yearweek2
1 2015-12-31 2015-12-31   201553    201553
2 2016-01-03 2016-01-03   201653    201553
3 2016-01-04 2016-01-04   201601    201601
4 2018-12-30 2018-12-30   201852    201852
5 2018-12-31 2018-12-31   201801    201901
6 2019-01-01 2019-01-01   201901    201901

【讨论】:

    【解决方案2】:

    我了解在某些情况下需要包,但基础语言非常优雅且经过验证(并且经过调试和优化)。

    为什么不:

    dt <- as.Date("2014-03-16")
    dt2 <- as.POSIXlt(dt)
    dt2$yday
    [1] 74
    

    然后您可以选择一年中的第一周是零(如 C 中的索引)还是 1(如 R 中的索引)。

    没有要学习、更新、担心错误的包。

    【讨论】:

    • 我总是先尝试解决基本 R 的问题。所以,我和你在一起。但是您的答案错过了我正在寻找的(日历)周数!?例如,(dt2$yday-1)%/%7 +1 只有在 1 月 1 日是星期一时才有效。
    • @ChristianBorck - 不要进一步混淆事情,但“正确”取决于您对“周”的定义。 ISO-8601 standard 定义从星期一开始的一周,尽管周编号取决于 1 月 1 日的哪一天。 week(...) 函数并未声称使用此标准。我的观点是week(...) 似乎并没有遵守它自己的定义。如果您想要 ISO-8601 周(顺便说一句,这是一个很好的做法),请使用 isoweek(...)
    • 上述 user3229754 的解决方案返回以 index=0 开头的天数,我想你可以试试:( dt$yday ) %/%7 +1
    【解决方案3】:

    如果您想获取带有年份的周数,请使用:"%Y-W%V":

    e.g    yearAndweeks <- strftime(dates, format = "%Y-W%V")
    

    所以

    > strftime(c("2014-03-16", "2014-03-17","2014-03-18", "2014-01-01"), format = "%Y-W%V")
    

    变成:

    [1] "2014-W11" "2014-W12" "2014-W12" "2014-W01"

    【讨论】:

    【解决方案4】:

    基础包

    使用函数 strftime 传递参数 %V 以获取 ISO 8601 中定义的十进制数 (01–53) 形式的一年中的第几周。(文档中的更多详细信息:?strftime)

    strftime(c("2014-03-16", "2014-03-17","2014-03-18", "2014-01-01"), format = "%V")
    

    输出:

    [1] "11" "12" "12" "01"
    

    【讨论】:

    • 2014-01-012014-12-29将同时获得01
    • @giordano 这在 ISO 8601 中的定义是正确的。如果包含 1 月 1 日的一周(从星期一开始)在新年中有四天或更多天,那么它被认为是第一周。你可以加倍- 在任何在线 ISO 8601 周计算器中检查。
    • 应该是2015-01-01吧?如何解决这个问题?
    【解决方案5】:

    仅使用base,我编写了以下函数。

    注意:

    1. 假设星期一是一周中的第 1 天
    2. 第一周是第 1 周
    3. 如果周是去年的 52,则返回 0

    根据您的需要进行微调。

    findWeekNo <- function(myDate){
      # Find out the start day of week 1; that is the date of first Mon in the year
      weekday <- switch(weekdays(as.Date(paste(format(as.Date(myDate),"%Y"),"01-01", sep = "-"))),
                        "Monday"={1},
                        "Tuesday"={2},
                        "Wednesday"={3},
                        "Thursday"={4},
                        "Friday"={5},
                        "Saturday"={6},
                        "Sunday"={7}
      )
    
      firstMon <- ifelse(weekday==1,1, 9 - weekday )
    
      weekNo <- floor((as.POSIXlt(myDate)$yday - (firstMon-1))/7)+1
      return(weekNo)
    }
    
    
    findWeekNo("2017-01-15") # 2
    

    【讨论】:

    • 您的代码并没有完全消除它。例如findWeekNo("2015-01-01") 返回 0,应该是第 1 周。
    • @ekstroem:取决于你想如何计算你的周数。请参阅代码上方的注释。此代码假定第 1 周从一年中的第一个星期一开始,类似于 timeanddate.com 日历中使用的标准。第一个星期一之前的一年中的日子属于上一年的最后一周。我故意没有将其编码为显示 52 以避免将其与相关年份的第 52 周混淆。
    • ISO 标准以first week on the first Thursday 为基础:“周从星期一开始。每一周的年份是公历年,星期四所在的位置。因此,一年中的第一周总是包含1 月 4 日。因此,ISO 周年编号在接近 1 月 1 日的某些日子与公历略有不同。”。您引用的页面timeanddate.com 还将 2015 年 1 月 1 日列为第 1 周。
    【解决方案6】:

    我认为问题在于week 的计算不知何故使用了一年中的第一天。我不明白内部机制,但你可以通过这个例子明白我的意思:

    library(data.table)
    
    dd <- seq(as.IDate("2013-12-20"), as.IDate("2014-01-20"), 1)
    # dd <- seq(as.IDate("2013-12-01"), as.IDate("2014-03-31"), 1)
    
    dt <- data.table(i = 1:length(dd),
                     day = dd,
                     weekday = weekdays(dd),
                     day_rounded = round(dd, "weeks"))
    ## Now let's add the weekdays for the "rounded" date
    dt[ , weekday_rounded := weekdays(day_rounded)]
    ## This seems to make internal sense with the "week" calculation
    dt[ , weeknumber := week(day)]
    dt 
    
        i        day   weekday day_rounded weekday_rounded weeknumber
    1:  1 2013-12-20    Friday  2013-12-17         Tuesday         51
    2:  2 2013-12-21  Saturday  2013-12-17         Tuesday         51
    3:  3 2013-12-22    Sunday  2013-12-17         Tuesday         51
    4:  4 2013-12-23    Monday  2013-12-24         Tuesday         52
    5:  5 2013-12-24   Tuesday  2013-12-24         Tuesday         52
    6:  6 2013-12-25 Wednesday  2013-12-24         Tuesday         52
    7:  7 2013-12-26  Thursday  2013-12-24         Tuesday         52
    8:  8 2013-12-27    Friday  2013-12-24         Tuesday         52
    9:  9 2013-12-28  Saturday  2013-12-24         Tuesday         52
    10: 10 2013-12-29    Sunday  2013-12-24         Tuesday         52
    11: 11 2013-12-30    Monday  2013-12-31         Tuesday         53
    12: 12 2013-12-31   Tuesday  2013-12-31         Tuesday         53
    13: 13 2014-01-01 Wednesday  2014-01-01       Wednesday          1
    14: 14 2014-01-02  Thursday  2014-01-01       Wednesday          1
    15: 15 2014-01-03    Friday  2014-01-01       Wednesday          1
    16: 16 2014-01-04  Saturday  2014-01-01       Wednesday          1
    17: 17 2014-01-05    Sunday  2014-01-01       Wednesday          1
    18: 18 2014-01-06    Monday  2014-01-01       Wednesday          1
    19: 19 2014-01-07   Tuesday  2014-01-08       Wednesday          2
    20: 20 2014-01-08 Wednesday  2014-01-08       Wednesday          2
    21: 21 2014-01-09  Thursday  2014-01-08       Wednesday          2
    22: 22 2014-01-10    Friday  2014-01-08       Wednesday          2
    23: 23 2014-01-11  Saturday  2014-01-08       Wednesday          2
    24: 24 2014-01-12    Sunday  2014-01-08       Wednesday          2
    25: 25 2014-01-13    Monday  2014-01-08       Wednesday          2
    26: 26 2014-01-14   Tuesday  2014-01-15       Wednesday          3
    27: 27 2014-01-15 Wednesday  2014-01-15       Wednesday          3
    28: 28 2014-01-16  Thursday  2014-01-15       Wednesday          3
    29: 29 2014-01-17    Friday  2014-01-15       Wednesday          3
    30: 30 2014-01-18  Saturday  2014-01-15       Wednesday          3
    31: 31 2014-01-19    Sunday  2014-01-15       Wednesday          3
    32: 32 2014-01-20    Monday  2014-01-15       Wednesday          3
         i        day   weekday day_rounded weekday_rounded weeknumber
    

    我的解决方法是这个函数: https://github.com/geneorama/geneorama/blob/master/R/round_weeks.R

    round_weeks <- function(x){
        require(data.table)
        dt <- data.table(i = 1:length(x),
                         day = x,
                         weekday = weekdays(x))
        offset <- data.table(weekday = c('Sunday', 'Monday', 'Tuesday', 'Wednesday', 
                                         'Thursday', 'Friday', 'Saturday'),
                             offset = -(0:6))
        dt <- merge(dt, offset, by="weekday")
        dt[ , day_adj := day + offset]
        setkey(dt, i)
        return(dt[ , day_adj])
    }
    

    当然,您可以轻松更改偏移量以使星期一优先或其他。最好的方法是在偏移量上添加一个偏移量……但我还没有这样做。

    我提供了一个指向我的简单geneorama 包的链接,但请不要过分依赖它,因为它可能会发生变化并且没有太多文档记录。

    【讨论】:

      【解决方案7】:

      如果您尝试使用 lubridate:

      library(lubridate)
      lubridate::week(ymd("2014-03-16", "2014-03-17","2014-03-18", '2014-01-01'))
      
      [1] 11 11 12  1
      

      模式是一样的。试试isoweek

      lubridate::isoweek(ymd("2014-03-16", "2014-03-17","2014-03-18", '2014-01-01'))
      [1] 11 12 12  1
      

      【讨论】:

      • ?week (lubridate) 声明:周数是从该日期到 1 月 1 日之间发生的完整 7 天周期数加一。
      • @ChristianBorck isoweek 是你需要的吗?
      • 看起来不错,但我的 lubridate (v 1.3.1) 包似乎缺少 isoweek 功能?你用的是哪个版本?
      • @ChristianBorck 我正在运行 lubridate_1.3.3 更新它。
      【解决方案8】:

      实际上,我认为您可能已经在week(...) 函数中发现了一个错误,或者至少在文档中发现了一个错误。希望有人能解释我为什么错了。

      看代码:

      library(lubridate)
      > week
      function (x) 
      yday(x)%/%7 + 1
      <environment: namespace:lubridate>
      

      文档说明:

      周数是从日期到 1 月 1 日之间的完整 7 天周期数,再加一。

      但由于 1 月 1 日是一年中的第一天(不是第零天),所以第一个“周”将是六天的时间段。代码应该 (??) 是

      (yday(x)-1)%/%7 + 1
      

      注意:您在data.table 包中使用week(...),它与lubridate::week 的代码相同,除了它将所有内容强制为整数而不是数字以提高效率。所以这个函数也有同样的问题(??)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-19
        相关资源
        最近更新 更多