Как получить номера недель из дат?

В поисках функции в R для преобразования дат в номера недель (года) я выбрал week из пакета data.table. Однако я заметил странное поведение:

> 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.


person Christian Borck    schedule 16.03.2014    source источник
comment
Попробуйте format(as.Date("2014-03-16"), "%U") или format(as.Date("2014-03-16"), "%W")   -  person GSee    schedule 16.03.2014
comment
@GПосмотрите, спасибо, но это возвращает 11 вместо 12 для следующих значений: format(as.Date("2014-03-17"), "%U") и format(as.Date("2014-03-17"), "%W")!?   -  person Christian Borck    schedule 16.03.2014
comment
Итак, преобразуйте в целое число и добавьте 1. См. ?strptime   -  person GSee    schedule 16.03.2014
comment
Собственно, именно этим я сейчас и занимаюсь. Мне просто интересно, почему я должен искать обходной путь? Я ожидаю, что неделя начнется в понедельник (ЕС) или воскресенье (США), но не во вторник?   -  person Christian Borck    schedule 16.03.2014
comment
И этот ответ содержит сравнение различных week функций нумерации из базовых пакетов R, lubridate, data.table и ISOweek.   -  person Uwe    schedule 09.05.2017
comment
@Uwe, я думаю, вам стоит переместить свой хороший ответ на туда к более каноническим вопросам и ответам   -  person Henrik    schedule 23.06.2018
comment
@Henrik Спасибо за ваше предложение. Я сделаю это, как только позволит время.   -  person Uwe    schedule 23.06.2018


Ответы (8)


если вы попробуете с 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
person Paulo E. Cardoso    schedule 16.03.2014
comment
?week (lubridate) указывает: Недели - это количество полных семидневных периодов, которые произошли между датой и 1 января, плюс один. - person Christian Borck; 16.03.2014
comment
@ChristianBorck isoweek - это то, что тебе нужно? - person Paulo E. Cardoso; 16.03.2014
comment
Выглядит неплохо, но в моем пакете lubridate (v 1.3.1) отсутствует функция isoweek? Какую версию вы используете? - person Christian Borck; 16.03.2014
comment
@ChristianBorck Я запускаю lubridate_1.3.3, обновляю. - person Paulo E. Cardoso; 16.03.2014

если вы хотите получить номер недели через год, используйте: "%Y-W%V":

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

so

> 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"

person Grant Shannon    schedule 24.02.2018
comment
Это опасно: strftime(c(as.Date("2014-01-01"),as.Date("2014-12-29")), format = "%Y-W%V") дает [1] "2014-W01" "2014-W01". - person giordano; 06.09.2019
comment
Это может помочь: stackoverflow.com/questions/49904570/ - person Grant Shannon; 26.02.2021

На самом деле, я думаю, вы могли обнаружить ошибку в функции week(...) или, по крайней мере, ошибку в документации. Надеюсь, кто-нибудь вмешается и объяснит, почему я ошибаюсь.

Смотрим на код:

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

В документации указано:

Недели - это количество полных семидневных периодов между датой и 1 января плюс один.

Но поскольку 1 января - это первый день года (а не нулевой), первая «неделя» будет шестидневным периодом. Код должен (??) быть

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

NB: вы используете week(...) в пакете data.table, который является тем же кодом, что и lubridate::week, за исключением того, что для эффективности он переводит все в целое число, а не в число. Значит, у этой функции та же проблема (??).

person jlhoward    schedule 16.03.2014

Я думаю, проблема в том, что при вычислении 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, но, пожалуйста, не слишком полагайтесь на него, потому что он, вероятно, изменится и не очень документирован.

person geneorama    schedule 01.08.2014

Я понимаю необходимость пакетов в определенных ситуациях, но базовый язык настолько элегантен и так хорошо зарекомендовал себя (а также отлажен и оптимизирован).

Почему нет:

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

И затем вы выбираете, будет ли первая неделя года нулевая (как при индексации в C) или 1 (как в индексировании в R).

Нет пакетов, которые нужно изучать, обновлять, беспокоиться об ошибках.

person user3229754    schedule 16.03.2014
comment
Я всегда стараюсь сначала решить проблемы с базой R. Итак, я с вами. Но в вашем ответе отсутствует номер (календарной) недели, который я ищу !? (dt2$yday-1)%/%7 +1, например, работает правильно, только если 1 января был понедельник. - person Christian Borck; 16.03.2014
comment
@ChristianBorck - Чтобы не путать еще больше, но все зависит от вашего определения недели. Стандарт ISO-8601 определяет начало недели в понедельник, хотя нумерация недель зависит от того, на какой день выпадает 1 января. Функция week(...) не претендует на использование этого стандарта. Моя точка зрения заключалась в том, что week(...), похоже, не придерживается своего собственного определения. Если вам нужны недели ISO-8601 (кстати, хорошая практика), используйте isoweek(...). - person jlhoward; 16.03.2014
comment
Вышеупомянутое решение от user3229754 возвращает номера дней, начиная с index = 0, я думаю, вы можете попробовать: ( dt$yday ) %/%7 +1 - person Manoj Kumar; 26.05.2017

Если вы хотите получить номер недели с годом, решение Гранта Шеннона с использованием strftime работает, но вам нужно внести некоторые исправления в даты около 1 января. Например, 2016-01-03 (гггг-мм-дд) - это 53-я неделя 2015 года, а не 2016. А 2018-12-31 - это первая неделя 2019 года, а не 2018 года. Эти коды содержат некоторые примеры и решения. В столбце «годовая неделя» иногда неверно указаны годы, в «годовая неделя2» - исправлены (строки 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

person Erik Volkers    schedule 08.03.2019

Используя только базу, я написал следующую функцию.

Примечание:

  1. Предполагается, что понедельник - это первый день недели.
  2. Первая неделя - это неделя 1
  3. Возвращает 0, если неделя 52 с прошлого года.

Точная настройка в соответствии с вашими потребностями.

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
person Wael Hussein    schedule 09.05.2017
comment
Ваш код не совсем его сокращает. Например, findWeekNo("2015-01-01") возвращает 0, и это должна быть неделя 1. - person ekstroem; 09.05.2017
comment
@ekstroem: Зависит от того, как вы хотите пронумеровать свои недели. См. Примечание над кодом. В этом коде предполагается, что неделя 1 начинается с первого понедельника года, аналогично стандарту, используемому в календарях timeanddate.com. Дни в году перед первым понедельником относятся к последней неделе предыдущего года. Я намеренно не закодировал его так, чтобы показывать 52, чтобы не перепутать его с 52 неделей рассматриваемого года. - person Wael Hussein; 10.05.2017
comment
Стандарт ISO определяет первую неделю в первый четверг: недели начинаются с понедельника. Год каждой недели - это год по григорианскому календарю, на который приходится четверг. Следовательно, в первую неделю года всегда приходится 4 января. Поэтому нумерация годов недели по ISO немного отличается от григорианского для некоторых дней, близких к 1 января.. На странице, на которую вы ссылаетесь, timeanddate.com также указано 1 января 2015 года как неделя 1. - person ekstroem; 10.05.2017

person    schedule
comment
2014-01-01 и 2014-12-29 получат оба 01. - person giordano; 06.09.2019
comment
@giordano Это верно, как определено в ISO 8601. Если в неделе (начиная с понедельника), содержащей 1 января, четыре или более дней в новом году, то она считается неделей 1. Вы можете дважды проверить это в любом из ISO 8601 недельный калькулятор онлайн. - person mpalanco; 06.09.2019